mirror of
https://github.com/vinefeeder/TwinVine.git
synced 2026-07-15 18:10:04 +02:00
new feature
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Recursively convert form .mkv files to mp4 using ffmpeg.
|
||||
|
||||
Default behavior:
|
||||
- Find all *.mkv under a given root
|
||||
- Convert to mp4 with the same base name, in the same folder
|
||||
e.g. "Taggart S01E02.mkv" -> "Taggart S01E02.mp4"
|
||||
|
||||
Requires:
|
||||
- ffmpeg available on PATH
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(
|
||||
description="Convert mkv files to mp4 (recursively) using ffmpeg."
|
||||
)
|
||||
p.add_argument(
|
||||
"root",
|
||||
nargs="?",
|
||||
default=".",
|
||||
help="Root directory to scan (default: current directory).",
|
||||
)
|
||||
p.add_argument(
|
||||
"--ext",
|
||||
default=".mkv",
|
||||
help="Input extension to scan for (default: .mkv).",
|
||||
)
|
||||
p.add_argument(
|
||||
"--out-ext",
|
||||
default=".mp4",
|
||||
help="Output extension to write (default: .mp4).",
|
||||
)
|
||||
p.add_argument(
|
||||
"--overwrite",
|
||||
action="store_true",
|
||||
help="Overwrite existing output files.",
|
||||
)
|
||||
p.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Print what would be done, but don't run ffmpeg.",
|
||||
)
|
||||
p.add_argument(
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Print ffmpeg output for each file.",
|
||||
)
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
def run_convert(
|
||||
ffmpeg_path: str,
|
||||
in_file: Path,
|
||||
out_file: str,
|
||||
overwrite: bool,
|
||||
dry_run: bool,
|
||||
verbose: bool,
|
||||
) -> bool:
|
||||
|
||||
out_file = Path(out_file)
|
||||
in_file = Path(in_file)
|
||||
|
||||
if out_file.exists() and not overwrite:
|
||||
print(f"SKIP (exists): {out_file}")
|
||||
return True
|
||||
|
||||
cmd = [
|
||||
ffmpeg_path,
|
||||
"-i",
|
||||
in_file,
|
||||
"-c",
|
||||
"copy",
|
||||
out_file,
|
||||
]
|
||||
|
||||
if dry_run:
|
||||
print("DRY:", " ".join(map(str, cmd)))
|
||||
return True
|
||||
|
||||
# Ensure parent exists (it should, but just in case you change output logic later)
|
||||
out_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
print("ERROR: ffmpeg not found (is MKVToolNix installed and on PATH?)", file=sys.stderr)
|
||||
return False
|
||||
|
||||
if verbose and proc.stdout:
|
||||
print(proc.stdout.rstrip())
|
||||
|
||||
if proc.returncode == 0 and out_file.exists():
|
||||
print(f"OK : {in_file.name} -> {out_file.name}")
|
||||
return True
|
||||
|
||||
print(f"FAIL: {in_file}", file=sys.stderr)
|
||||
if proc.stdout:
|
||||
print(proc.stdout.rstrip(), file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
|
||||
ffmpeg_path = shutil.which("ffmpeg")
|
||||
if not ffmpeg_path:
|
||||
print("ERROR: ffmpeg not found on PATH. Install MKVToolNix.", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
root = Path(args.root).expanduser().resolve()
|
||||
if not root.exists():
|
||||
print(f"ERROR: Root path does not exist: {root}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
in_ext = args.ext if args.ext.startswith(".") else f".{args.ext}"
|
||||
out_ext = args.out_ext if args.out_ext.startswith(".") else f".{args.out_ext}"
|
||||
|
||||
files = sorted(p for p in root.rglob(f"*{in_ext}") if p.is_file())
|
||||
if not files:
|
||||
print(f"No {in_ext} files found under {root}")
|
||||
return 0
|
||||
|
||||
ok = 0
|
||||
fail = 0
|
||||
|
||||
for in_file in files:
|
||||
out_file = in_file.with_suffix(out_ext)
|
||||
success = run_convert(
|
||||
ffmpeg_path=ffmpeg_path,
|
||||
in_file=in_file,
|
||||
out_file=out_file,
|
||||
overwrite=args.overwrite,
|
||||
dry_run=args.dry_run,
|
||||
verbose=args.verbose,
|
||||
)
|
||||
if success:
|
||||
ok += 1
|
||||
else:
|
||||
fail += 1
|
||||
|
||||
print(f"\nDone. OK={ok}, FAIL={fail}, TOTAL={ok+fail}")
|
||||
return 0 if fail == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user