#! /usr/bin/python """Call ffmpeg with youtube-dl to convert files or youtube videos to mp4 or webp files.""" import subprocess, argparse, re def get_video_title(input_path: str) -> str: if "https" in input_path: title = subprocess.run(["yt-dlp", "--get-title", input_path], capture_output=True, encoding='utf-8') return re.sub(r'[\W]+', '_', title.stdout.strip()) + ".mp4" else: return "vc"+input_path def get_input_args(input_path: str) -> list[str]: """Generate ffmpeg input arguments for either local files or YouTube URLs.""" f_args: list[str] = [] if "https" in input_path: urls = subprocess.run(["yt-dlp", "--get-url", input_path], capture_output=True, encoding='utf-8') for url in urls.stdout.split('\n'): if len(url) > 10: f_args += ["-i", url] else: f_args += ["-i", input_path] return f_args def get_webp_args(quality: str | None) -> list[str]: """Generate ffmpeg arguments for WebP output format. Args: quality (str): Output quality percentage, defaults to 70 if not specified """ args: list[str] = [] args += ["-vcodec", "libwebp"] args += ["-preset", "default"] args += ["-loop", "0", "-an", "-vsync", "0"] args += ["-qscale", quality if quality else "70"] return args def get_mp4_args() -> list[str]: """Generate ffmpeg arguments for MP4 output format.""" args: list[str] = [] args += ["-c:v", "libx264"] args += ["-preset", "veryfast"] args += ["-crf", "26"] args += ["-c:a", "aac"] args += ["-b:a", "128k"] args += ["-pix_fmt", "yuv420p"] return args def get_video_filter(crop_arg: str | None) -> str: """Generate ffmpeg video filter string for rescaling and optional cropping. Rescales video height to 720p or original height if smaller, maintaining aspect ratio. Supports standard, percentage-based, and edge-based cropping formats.""" vfilter = "scale=-2:'min(720,ih)'" if crop_arg: if crop_arg.endswith('%'): # Percentage-based centered cropping (single percentage value) percent = float(crop_arg.replace('%', '')) vfilter += f",crop='in_w*{percent/100}:in_h*{percent/100}:(in_w-in_w*{percent/100})/2:(in_h-in_h*{percent/100})/2'" elif crop_arg.startswith('edge:'): # Edge-based cropping (edge:left:right:top:bottom or edge:width:height) parts = crop_arg[5:].split(':') if len(parts) == 1: l = r = t = b = int(parts[0]) vfilter += f",crop='in_w-{l+r}:in_h-{t+b}:{l}:{t}'" elif len(parts) == 2: w, h = map(int, parts) vfilter += f",crop='{w}:{h}:(in_w-{w})/2:(in_h-{h})/2'" else: l, r, t, b = map(int, parts) vfilter += f",crop='in_w-{l+r}:in_h-{t+b}:{l}:{t}'" else: # Standard cropping (out_w:out_h:x:y or out_w:out_h for centered) parts = crop_arg.split(':') if len(parts) == 2: w, h = parts vfilter += f",crop={w}:{h}:(in_w-{w})/2:(in_h-{h})/2" else: vfilter += f",crop={crop_arg}" return vfilter def main(): parser = argparse.ArgumentParser(description='Call ffmpeg for converting small video files to mp4 or webp') parser.add_argument('input', help='input file or url') parser.add_argument('output', nargs='?', default="", help='output filename') parser.add_argument('-s', '--start', help='start time, if not 00:00') parser.add_argument('-t', '--to', help='end time, if not end of file') parser.add_argument('-q', '--quality', help='quality of output, as a percentage') parser.add_argument('-S', '--silent', action='store_true', help='no audio') parser.add_argument('-c', '--crop', help=""" Crop formats supported: - Standard: "out_w:out_h:x:y" or "out_w:out_h" for centered - Percentage: "NN%" for centered crop to NN percent - Edge: "edge:left:right:top:bottom" or "edge:width:height" or single value for all""") args = parser.parse_args() if not args.output: args.output = get_video_title(args.input) f_args = get_input_args(args.input) f_args += ["-vf", get_video_filter(args.crop)] if args.start: f_args += ["-ss", args.start] # -ss specifies the start timestamp if args.to: f_args += ["-to", args.to] # -to specifies the end timestamp if args.silent: f_args += ["-an"] # -an disables audio output f_args += ["-sn"] # -sn removes subtitles f_args += ["-dn"] # -dn removes data streams f_args += ["-map_metadata:c", "-1"] # Remove metadata from the output output_ext = args.output.split('.')[-1] if output_ext == 'webp': f_args += get_webp_args(args.quality) elif output_ext == 'mp4': f_args += get_mp4_args() else: print(f"Error: Output file must have either .mp4 or .webp extension, found: .{output_ext}") exit(1) print(f_args) subprocess.run(["ffmpeg", "-y"] + f_args + [args.output if args.output else "output.mp4"]) if __name__ == "__main__": main()