feat: handle keyboard interruption more gracefully

This commit is contained in:
Jan Meyer
2026-02-10 19:16:06 +01:00
parent d08b139af6
commit f9a4af1c94
4 changed files with 100 additions and 65 deletions

View File

@@ -3,17 +3,21 @@
DVD/Blu-ray ripper using HandBrakeCLI with scene-style naming.
Scans a disc, selects the best audio & subtitle track per language
(passthrough), encodes with H.265 10-bit AMD VCE (falling back to
x265_10bit on CPU), and generates an NFO file via pymediainfo.
(passthrough), encodes with H.265/H.264/AV1 (auto-selecting HW
acceleration), and generates an NFO file via pymediainfo.
Usage:
python ripper.py --imdb tt6977338
python ripper.py --name 'Game Night' --year 2018
python ripper.py --codec av1 --quality 30
python ripper.py --scan-only
python ripper.py --list
"""
import sys
from ripper.cli import main
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
sys.exit(130)

View File

@@ -1,5 +1,10 @@
"""Allow running as `python -m ripper`."""
import sys
from .cli import main
try:
main()
except KeyboardInterrupt:
sys.exit(130)

View File

@@ -136,6 +136,15 @@ def main():
)
console.print()
try:
_run_pipeline(args, quality)
except KeyboardInterrupt:
console.print("\n [bold yellow]⚠ Interrupted[/] — exiting.\n")
sys.exit(130)
def _run_pipeline(args, quality: int) -> None:
"""Core ripping pipeline, separated for clean interrupt handling."""
# ── 1. Detect encoder ────────────────────────────────────────────────
available = discover_encoders()
print_encoder_table(available)
@@ -183,7 +192,6 @@ def main():
# Determine codec tag and bit depth from the selected encoder
codec_tag = CODEC_SCENE_TAG.get(args.codec, args.codec.upper())
# Check if we're using a 10-bit encoder
is_10bit = "10bit" in encoder or "10" in encoder
scene = build_scene_name(
@@ -220,6 +228,9 @@ def main():
)
returncode = run_encode(cmd, input_path=args.input)
if returncode == 130:
# User cancelled — re-raise so outer handler prints message
raise KeyboardInterrupt
if returncode != 0:
console.print(f"\n[bold red]ERROR:[/] HandBrakeCLI exited with code {returncode}")
sys.exit(returncode)

View File

@@ -116,6 +116,7 @@ def run_encode(cmd: list[str], input_path: str | None = None) -> int:
last_progress_time = time.monotonic()
stall_warned = False
try:
with progress:
for line in process.stdout:
line = line.rstrip()
@@ -182,6 +183,20 @@ def run_encode(cmd: list[str], input_path: str | None = None) -> int:
# Ensure we reach 100%
progress.update(task_id, completed=100, phase="Complete")
except KeyboardInterrupt:
# Gracefully stop HandBrakeCLI on Ctrl+C
progress.update(task_id, phase="[bold red]Cancelled[/]", fps="")
progress.stop()
console.print("\n [bold yellow]⚠ Interrupted[/] — stopping HandBrakeCLI…")
process.terminate()
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
console.print(" [dim]HandBrakeCLI stopped.[/]")
return 130 # Standard exit code for SIGINT
process.wait()
return process.returncode