#!/usr/bin/env python3 import argparse import os import subprocess import re import sys SUPPORTED_EXTENSIONS = (".mp4", ".mkv", ".mov", ".avi") def get_duration(file_path): result = subprocess.run([ "ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", file_path ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) return float(result.stdout.strip()) def encode(input_file, codec): if codec == "x265": ffmpeg_codec = "libx265" crf = 26 folder = "h265" elif codec == "av1": ffmpeg_codec = "libaom-av1" crf = 32 folder = "av1" else: raise ValueError("Unsupported codec") try: duration = get_duration(input_file) except Exception: print(f"\n❌ Could not read duration for {input_file}") return filename = os.path.basename(input_file) name, _ = os.path.splitext(filename) outdir = os.path.join(os.path.dirname(input_file), folder) os.makedirs(outdir, exist_ok=True) output_file = os.path.join(outdir, f"{name}.mkv") cmd = [ "ffmpeg", "-i", input_file, "-map", "0", "-c:v", ffmpeg_codec, "-crf", str(crf), "-c:a", "copy", "-c:s", "copy", "-y", output_file ] print(f"\n🎬 Encoding {filename} → {folder}/{name}.mkv") process = subprocess.Popen(cmd, stderr=subprocess.PIPE, text=True) time_re = re.compile(r"time=(\d+):(\d+):([\d.]+)") last_percent = -1 for line in process.stderr: match = time_re.search(line) if match: h, m, s = map(float, match.groups()) current = h * 3600 + m * 60 + s percent = int((current / duration) * 100) if percent != last_percent: print(f"\r⏳ Progress: {percent}%", end='', flush=True) last_percent = percent process.wait() print("\n✅ Done.") def encode_batch(directory, codec): if not os.path.isdir(directory): print(f"❌ Not a valid directory: {directory}") return for root, _, files in os.walk(directory): for file in files: if file.lower().endswith(SUPPORTED_EXTENSIONS): filepath = os.path.join(root, file) encode(filepath, codec) def main(): parser = argparse.ArgumentParser(description="Encode video(s) to x265 or AV1.") parser.add_argument("input", nargs="?", help="Path to input file") parser.add_argument("-d", "--directory", help="Path to a directory for batch encoding") parser.add_argument("--codec", choices=["x265", "av1"], default="x265", help="Codec to use (default: x265)") args = parser.parse_args() if args.directory: encode_batch(args.directory, args.codec) elif args.input: if not os.path.isfile(args.input): print(f"❌ File not found: {args.input}") sys.exit(1) encode(args.input, args.codec) else: print("❌ Please provide a file path or use -d for a directory.") parser.print_help() if __name__ == "__main__": main()