Files
ripper/ripper/cli.py
2026-02-10 15:40:12 +01:00

188 lines
6.9 KiB
Python

"""CLI entry point and main orchestration logic."""
import argparse
import os
import sys
from rich.panel import Panel
from .config import INPUT_PATH, OUTPUT_BASE, CONFIG_FILE, init_config, console
from .scanner import detect_encoder, scan_disc, select_title
from .tracks import best_tracks_per_language, print_track_tables
from .naming import build_scene_name, get_source_tag, get_volume_label
from .encode import build_handbrake_cmd, run_encode
from .metadata import lookup_imdb, generate_nfo
from .library import list_library
from .disc import check_disc_readable, unstuck_disc
def main():
parser = argparse.ArgumentParser(
description="Rip DVD/Blu-ray to MKV using HandBrakeCLI (H.265 10-bit).",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" %(prog)s --imdb tt6977338\n"
" %(prog)s --name 'Game Night' --year 2018\n"
" %(prog)s --scan-only\n"
" %(prog)s --list\n"
" %(prog)s --init-config\n"
),
)
parser.add_argument(
"--name", "-n",
help="Movie name (spaces OK, will be dotted). "
"If omitted, the disc volume label is used.",
)
parser.add_argument("--year", "-y", help="Release year for the filename.")
parser.add_argument(
"--imdb",
help="IMDB ID (e.g. tt6977338) — fetches name and year from TMDB. "
"Requires TMDB_API_KEY env var or config.",
)
parser.add_argument(
"--input", "-i",
default=INPUT_PATH,
help=f"Input path (default: {INPUT_PATH}).",
)
parser.add_argument(
"--output-base",
default=OUTPUT_BASE,
help=f"Base output directory (default: {OUTPUT_BASE}).",
)
parser.add_argument(
"--scan-only",
action="store_true",
help="Only scan the disc and print track info, don't encode.",
)
parser.add_argument(
"--list", "-l",
action="store_true",
help="List all ripped movies in the output directory.",
)
parser.add_argument(
"--check-disc",
action="store_true",
help="Check disc readability and attempt recovery if stuck.",
)
parser.add_argument(
"--init-config",
action="store_true",
help="Create a default config.toml in the config directory.",
)
args = parser.parse_args()
# ── Generate config file ─────────────────────────────────────────────
if args.init_config:
init_config()
return
# ── Library listing (no disc needed) ─────────────────────────────────
if args.list:
list_library(args.output_base)
return
# ── Disc health check ────────────────────────────────────────────────
if args.check_disc:
if not unstuck_disc(args.input):
sys.exit(1)
return
# ── Banner ───────────────────────────────────────────────────────────
console.print()
console.print(
Panel(
"[bold white]DVD / Blu-ray Ripper[/]\n"
"[dim]H.265 10-bit · HandBrakeCLI · Scene Naming[/]",
border_style="cyan",
padding=(0, 2),
)
)
console.print()
# ── 1. Detect encoder ────────────────────────────────────────────────
encoder = detect_encoder()
# ── 2. Scan disc ─────────────────────────────────────────────────────
scan = scan_disc(args.input)
title = select_title(scan)
# ── 3. Select tracks ─────────────────────────────────────────────────
audio_all = title.get("AudioList", [])
subtitle_all = title.get("SubtitleList", [])
audio_sel = best_tracks_per_language(audio_all, "audio")
subtitle_sel = best_tracks_per_language(subtitle_all, "subtitle")
print_track_tables(audio_sel, subtitle_sel)
if args.scan_only:
console.print("\n [dim](scan-only mode, exiting)[/]\n")
return
# ── 4. Build filename ────────────────────────────────────────────────
movie_name = args.name
year = args.year
# IMDB lookup overrides --name and --year
if args.imdb:
movie_name, imdb_year = lookup_imdb(args.imdb)
if not year:
year = imdb_year
if not movie_name:
label = get_volume_label(args.input)
if label:
movie_name = label.replace("_", " ").title()
console.print(f" [green]✓[/] Using volume label: [bold]{movie_name}[/]")
else:
movie_name = console.input(" [cyan]Enter movie name:[/] ").strip()
if not movie_name:
console.print("[bold red]ERROR:[/] No movie name provided.")
sys.exit(1)
source_tag = get_source_tag(args.input)
scene = build_scene_name(movie_name, year, title, audio_sel, source_tag)
# Create output directory
out_dir = os.path.join(args.output_base, scene)
os.makedirs(out_dir, exist_ok=True)
output_file = os.path.join(out_dir, f"{scene}.mkv")
console.print()
console.print(
Panel(
f"[bold]{scene}.mkv[/]\n"
f"[dim]{out_dir}[/]",
title="[bold cyan]Output[/]",
border_style="dim",
padding=(0, 2),
)
)
console.print()
# ── 5. Encode ────────────────────────────────────────────────────────
cmd = build_handbrake_cmd(
args.input, output_file, title, audio_sel, subtitle_sel, encoder,
)
returncode = run_encode(cmd, input_path=args.input)
if returncode != 0:
console.print(f"\n[bold red]ERROR:[/] HandBrakeCLI exited with code {returncode}")
sys.exit(returncode)
console.print(f"\n [green]✓[/] Encode complete")
# ── 6. Generate NFO ──────────────────────────────────────────────────
generate_nfo(output_file)
console.print()
console.print(
Panel(
f"[green]✓ All done![/]\n[dim]{out_dir}[/]",
border_style="green",
padding=(0, 2),
)
)
console.print()