feat: handle keyboard interruption more gracefully
This commit is contained in:
10
ripper.py
10
ripper.py
@@ -3,17 +3,21 @@
|
|||||||
DVD/Blu-ray ripper using HandBrakeCLI with scene-style naming.
|
DVD/Blu-ray ripper using HandBrakeCLI with scene-style naming.
|
||||||
|
|
||||||
Scans a disc, selects the best audio & subtitle track per language
|
Scans a disc, selects the best audio & subtitle track per language
|
||||||
(passthrough), encodes with H.265 10-bit AMD VCE (falling back to
|
(passthrough), encodes with H.265/H.264/AV1 (auto-selecting HW
|
||||||
x265_10bit on CPU), and generates an NFO file via pymediainfo.
|
acceleration), and generates an NFO file via pymediainfo.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python ripper.py --imdb tt6977338
|
python ripper.py --imdb tt6977338
|
||||||
python ripper.py --name 'Game Night' --year 2018
|
python ripper.py --name 'Game Night' --year 2018
|
||||||
|
python ripper.py --codec av1 --quality 30
|
||||||
python ripper.py --scan-only
|
python ripper.py --scan-only
|
||||||
python ripper.py --list
|
python ripper.py --list
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
from ripper.cli import main
|
from ripper.cli import main
|
||||||
|
|
||||||
if __name__ == "__main__":
|
try:
|
||||||
main()
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit(130)
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
"""Allow running as `python -m ripper`."""
|
"""Allow running as `python -m ripper`."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
from .cli import main
|
from .cli import main
|
||||||
|
|
||||||
main()
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit(130)
|
||||||
|
|||||||
@@ -136,6 +136,15 @@ def main():
|
|||||||
)
|
)
|
||||||
console.print()
|
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 ────────────────────────────────────────────────
|
# ── 1. Detect encoder ────────────────────────────────────────────────
|
||||||
available = discover_encoders()
|
available = discover_encoders()
|
||||||
print_encoder_table(available)
|
print_encoder_table(available)
|
||||||
@@ -183,7 +192,6 @@ def main():
|
|||||||
|
|
||||||
# Determine codec tag and bit depth from the selected encoder
|
# Determine codec tag and bit depth from the selected encoder
|
||||||
codec_tag = CODEC_SCENE_TAG.get(args.codec, args.codec.upper())
|
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
|
is_10bit = "10bit" in encoder or "10" in encoder
|
||||||
|
|
||||||
scene = build_scene_name(
|
scene = build_scene_name(
|
||||||
@@ -220,6 +228,9 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
returncode = run_encode(cmd, input_path=args.input)
|
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:
|
if returncode != 0:
|
||||||
console.print(f"\n[bold red]ERROR:[/] HandBrakeCLI exited with code {returncode}")
|
console.print(f"\n[bold red]ERROR:[/] HandBrakeCLI exited with code {returncode}")
|
||||||
sys.exit(returncode)
|
sys.exit(returncode)
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ def run_encode(cmd: list[str], input_path: str | None = None) -> int:
|
|||||||
last_progress_time = time.monotonic()
|
last_progress_time = time.monotonic()
|
||||||
stall_warned = False
|
stall_warned = False
|
||||||
|
|
||||||
|
try:
|
||||||
with progress:
|
with progress:
|
||||||
for line in process.stdout:
|
for line in process.stdout:
|
||||||
line = line.rstrip()
|
line = line.rstrip()
|
||||||
@@ -182,6 +183,20 @@ def run_encode(cmd: list[str], input_path: str | None = None) -> int:
|
|||||||
# Ensure we reach 100%
|
# Ensure we reach 100%
|
||||||
progress.update(task_id, completed=100, phase="Complete")
|
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()
|
process.wait()
|
||||||
return process.returncode
|
return process.returncode
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user