2025-10-03 23:44:35 -04:00

146 lines
5.1 KiB
Python

import os
import shutil
import subprocess
from pathlib import Path
import argparse
def get_audio_info(filepath):
"""Return (stream_index, channels, bitrate_in_bits)."""
cmd = [
"ffprobe", "-v", "error", "-select_streams", "a:0",
"-show_entries", "stream=index,channels,bit_rate",
"-of", "csv=p=0", filepath
]
result = subprocess.run(cmd, capture_output=True, text=True)
if not result.stdout:
return None, 2, 128000
parts = result.stdout.strip().split(',')
if len(parts) < 3:
return None, 2, 128000
_, channels, bitrate = parts
channels = int(channels)
bitrate = int(bitrate) // 1000 # kbps approx
# Bucket logic
if channels == 2:
if bitrate < 80:
br = 64000
elif bitrate < 112:
br = 96000
else:
br = 128000
else:
if bitrate < 176:
br = 160000
else:
br = 192000
return 0, channels, br
def get_language_tag(filepath):
cmd = [
"ffprobe", "-v", "error", "-select_streams", "a:0",
"-show_entries", "stream_tags=language",
"-of", "default=nokey=1:noprint_wrappers=1", filepath
]
result = subprocess.run(cmd, capture_output=True, text=True)
return result.stdout.strip() or None
def main():
parser = argparse.ArgumentParser(
description="Batch AV1 transcoder (FFmpeg-based) for anime/TV folders."
)
parser.add_argument("origination", help="Path to origination folder (e.g., P:\\Anime\\Show)")
parser.add_argument("-p", "--processing", default=r"C:\Users\Tyler\Videos\Video Conversion\temp",
help="Temporary processing folder (default: %(default)s)")
parser.add_argument("-c", "--completed", default=None,
help="Completed output folder (default: back to original)")
parser.add_argument("-r", "--resolution", type=int, choices=[720, 1080], default=1080,
help="Output resolution height (default: %(default)s)")
parser.add_argument("-cq", type=int, default=None,
help="Constant quality value for AV1_NVENC (auto if not specified)")
args = parser.parse_args()
orig = args.origination
processing = args.processing
completed = args.completed
res_choice = str(args.resolution)
# Auto-detect filter & default CQ
filter_flags = "lanczos"
if "\\tv\\" in orig.lower() or "/tv/" in orig.lower():
filter_flags = "bicubic"
cq_default = 28 if res_choice == "1080" else 32
else:
cq_default = 32 if res_choice == "1080" else 34
cq = args.cq if args.cq is not None else cq_default
print("\n=== Using These Settings ===")
print(f"Origination: {orig}")
print(f"Processing: {processing}")
print(f"Completed: {completed if completed else '[Return to original folder]'}")
print(f"Resolution: {res_choice}")
print(f"Filter: {filter_flags}")
print(f"CQ: {cq}")
print("=============================\n")
os.makedirs(processing, exist_ok=True)
if completed:
os.makedirs(completed, exist_ok=True)
suffix = " -EHX"
for root, dirs, files in os.walk(orig):
for f in files:
if not f.lower().endswith((".mkv", ".mp4")):
continue
if "ehx" in f.lower() or "megusta" in f.lower():
print(f"Skipping {f} (contains 'EHX' or 'MeGusta')")
continue
print("=" * 60)
print(f"Processing: {f}")
src = Path(root) / f
tmp = Path(processing) / f
shutil.copy2(src, tmp)
# Detect audio info
_, channels, abr = get_audio_info(str(tmp))
lang = get_language_tag(str(tmp))
lang_metadata = []
if not lang:
lang_metadata = ["-metadata:s:a:0", "language=eng"]
width, height = ("1920", "1080") if res_choice == "1080" else ("1280", "720")
out_file = Path(processing) / f"{Path(f).stem}{suffix}.mkv"
ffmpeg_cmd = [
"ffmpeg", "-y", "-i", str(tmp),
"-vf", f"scale={width}:{height}:flags={filter_flags}:force_original_aspect_ratio=decrease",
"-map", "0:v", "-map", "0:a", "-map", "0:s?",
"-c:v", "av1_nvenc", "-preset", "p1", "-cq", str(cq), "-pix_fmt", "p010le",
"-c:a", "aac", "-b:a", str(abr), "-ac", str(channels),
*lang_metadata,
"-metadata:s:a:0", f"bit_rate={abr}",
"-c:s", "copy",
str(out_file)
]
subprocess.run(ffmpeg_cmd)
target = Path(completed) / out_file.name if completed else Path(root) / out_file.name
shutil.move(out_file, target)
print(f"Moved file to {target}")
if target.exists():
print("Conversion confirmed. Deleting originals...")
os.remove(src)
os.remove(tmp)
else:
print("ERROR: Converted file not found. Skipping deletion of originals.")
if __name__ == "__main__":
main()