#! /usr/bin/env python3 import youtube_dl import json import ffmpeg import subprocess import glob import sys import argparse import os def generate_thumbnail(in_filename, out_filename, time, height): try: ( ffmpeg .input(in_filename, ss=time) .filter('scale', -2, height) .output(out_filename, vframes=1) .overwrite_output() .run(capture_stdout=True, capture_stderr=True) ) except ffmpeg.Error as e: print(e.stderr.decode(), file=sys.stderr) sys.exit(1) # setting width and height from hardcoded defaults -> configured defaults -> clip def getDimensions(config, clip): h = config['height'] if 'height' in config else 480 w = config['width'] if 'width' in config else -2 if 'scale' in clip: h = clip['scale']['h'] if 'h' in clip['scale'] else h w = clip['scale']['w'] if 'w' in clip['scale'] else w return h, w config = { 'vcodec': "vp9", 'acodec': "libopus", 'height': 480, 'quality': "best", } argParser = argparse.ArgumentParser() jsonFileName = sys.argv[1] clipDict = {} with open(jsonFileName) as jf: clipDict = json.load(jf) ydl_opts = {"outtmpl": "%(id)s"} for clip in clipDict: # create the directories so ffmpeg doesn't complain try: outputDir = os.path.dirname(clip['target']) os.makedirs(outputDir) except: print(f"Couldn't create {outputDir}") infoDict = None with youtube_dl.YoutubeDL(ydl_opts) as ydl: infoDict = ydl.extract_info(clip['source'], download=False) ydl.download([clip['source']]) if infoDict is not None: # @todo This is a very bad hack because the outtmpl options doesn't seem to be working if the file gets reencoded inputFilename = glob.glob(infoDict['id']+"*")[0] # generate preview image for the video if 'poster' in clip: generate_thumbnail(inputFilename, os.path.splitext(clip['target'])[0]+".jpg", clip['poster']['timeIndex'], h ) kwArgs = {} if 'from' in clip: kwArgs['ss'] = clip['from'] if 'to' in clip: kwArgs['to'] = clip['to'] stream = ffmpeg.input( inputFilename, **kwArgs) video, audio = stream.video, stream.audio if 'crop' in clip: stream = ffmpeg.filter(stream, "crop", x=clip['crop']['x'], y=clip['crop']['y'], w=clip['crop']['w'], h=clip['crop']['h'] ) h, w = getDimensions(config, clip) stream = ffmpeg.output(stream, clip['target'], vcodec=config['vcodec'], **{ # "an":None, "y":None, "pass":"1", # "b:v":"276k", "minrate":"138k", "maxrate":"400k", # x360 "b:v":"512k", "minrate":"256k", "maxrate":"742k", # x480 LQ # "b:v":"750k", "minrate":"375k", "maxrate":"1088k", # x480 MQ "quality": config['quality'] if 'quality' in config else "best", } ) try: ffmpeg.run(stream) except: print(infoDict) if 'from' in clip and 'to' in clip: stream = ffmpeg.input( glob.glob(infoDict['id']+"*")[0], ss=clip['from'], to=clip['to'], ) else: stream = ffmpeg.input( glob.glob(infoDict['id']+"*")[0] ) if 'crop' in clip: stream = ffmpeg.filter(stream, "crop", x=clip['crop']['x'], y=clip['crop']['y'], w=clip['crop']['w'], h=clip['crop']['h'] ) h, w = getDimensions(config, clip) stream = ffmpeg.output(stream, audio, clip['target'], vcodec=config['vcodec'], **{ # "an":None, "y":None, "pass":"2", # "b:v":"276k", "minrate":"138k", "maxrate":"400k", # x360 "b:v":"512k", "minrate":"256k", "maxrate":"742k", # x480 LQ # "b:v":"750k", "minrate":"375k", "maxrate":"1088k", # x480 MQ "quality": config['quality'] if 'quality' in config else "best", "acodec": config['acodec'], } ) try: ffmpeg.run(stream) except: print(infoDict) exit(-1)