feat: switch to GPU-accelerated AV1 encoding with logs

This commit is contained in:
Rémi Heredero 2025-04-27 14:15:51 +02:00
parent 1e7d3d521e
commit f10d1208f8
Signed by: Klagarge
GPG Key ID: 735B36B074A65F0F

View File

@ -19,10 +19,12 @@ def encode(input_file, codec):
ffmpeg_codec = "libx265" ffmpeg_codec = "libx265"
crf = 26 crf = 26
folder = "h265" folder = "h265"
extra_params = ["-crf", str(crf)]
elif codec == "av1": elif codec == "av1":
ffmpeg_codec = "libaom-av1" ffmpeg_codec = "av1_nvenc"
crf = 32 cq = 32
folder = "av1" folder = "av1"
extra_params = ["-pix_fmt", "yuv420p", "-preset", "p4", "-cq", str(cq), "-rc", "vbr", "-b:v", "0"]
else: else:
raise ValueError("Unsupported codec") raise ValueError("Unsupported codec")
@ -37,36 +39,49 @@ def encode(input_file, codec):
outdir = os.path.join(os.path.dirname(input_file), folder) outdir = os.path.join(os.path.dirname(input_file), folder)
os.makedirs(outdir, exist_ok=True) os.makedirs(outdir, exist_ok=True)
output_file = os.path.join(outdir, f"{name}.mkv") output_file = os.path.join(outdir, f"{name}.mkv")
log_file = os.path.join(outdir, f"{name}.log")
cmd = [ cmd = [
"ffmpeg", "ffmpeg",
"-i", input_file, "-i", input_file,
"-map", "0", "-map", "0",
] + extra_params + [
"-c:v", ffmpeg_codec, "-c:v", ffmpeg_codec,
"-crf", str(crf),
"-c:a", "copy", "-c:a", "copy",
"-c:s", "copy", "-c:s", "copy",
"-y", output_file "-y", output_file
] ]
print(f"\n🎬 Encoding {filename}{folder}/{name}.mkv") print(f"\n🎬 Encoding {filename}{folder}/{name}.mkv")
process = subprocess.Popen(cmd, stderr=subprocess.PIPE, text=True) with open(log_file, "w") as log:
process = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
time_re = re.compile(r"time=(\d+):(\d+):([\d.]+)") time_re = re.compile(r"time=(\d+):(\d+):([\d.]+)")
last_percent = -1 last_percent = -1
for line in process.stderr: while True:
match = time_re.search(line) line = process.stderr.readline()
if match: if not line:
h, m, s = map(float, match.groups()) break
current = h * 3600 + m * 60 + s log.write(line)
percent = int((current / duration) * 100) match = time_re.search(line)
if percent != last_percent: if match:
print(f"\r⏳ Progress: {percent}%", end='', flush=True) h, m, s = map(float, match.groups())
last_percent = percent 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() process.wait()
print("\n✅ Done.")
if process.returncode != 0:
print(f"\n❌ [Error] FFmpeg failed for {filename}. Check the log: {log_file}")
# Clean broken output
if os.path.exists(output_file):
os.remove(output_file)
else:
print("\n✅ Done.")
def encode_batch(directory, codec): def encode_batch(directory, codec):
if not os.path.isdir(directory): if not os.path.isdir(directory):
@ -77,10 +92,14 @@ def encode_batch(directory, codec):
for file in files: for file in files:
if file.lower().endswith(SUPPORTED_EXTENSIONS): if file.lower().endswith(SUPPORTED_EXTENSIONS):
filepath = os.path.join(root, file) filepath = os.path.join(root, file)
encode(filepath, codec) try:
encode(filepath, codec)
except Exception as e:
print(f"\n❌ Error with file {file}: {e}")
continue
def main(): def main():
parser = argparse.ArgumentParser(description="Encode video(s) to x265 or AV1.") parser = argparse.ArgumentParser(description="Encode video(s) to x265 or AV1 with GPU.")
parser.add_argument("input", nargs="?", help="Path to input file") 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("-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)") parser.add_argument("--codec", choices=["x265", "av1"], default="x265", help="Codec to use (default: x265)")
@ -99,4 +118,3 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()