transcode.py: handle Ctrl+C better

This commit is contained in:
2025-12-06 18:55:19 +01:00
parent a3dea4eaba
commit 158688ec44
+51 -22
View File
@@ -12,6 +12,7 @@ import json
import logging import logging
import os import os
import re import re
import signal
import subprocess import subprocess
import sys import sys
import time import time
@@ -200,7 +201,6 @@ def get_ffmpeg_command(input_path, output_path):
def transcode_file(input_file, output_file=None, skip_av1=True, replace_mode=False): def transcode_file(input_file, output_file=None, skip_av1=True, replace_mode=False):
exit_after_next = False
input_path = Path(input_file) input_path = Path(input_file)
logging.debug(f"Processing request for: {input_path}") logging.debug(f"Processing request for: {input_path}")
@@ -285,6 +285,8 @@ def transcode_file(input_file, output_file=None, skip_av1=True, replace_mode=Fal
cmd = get_ffmpeg_command(input_path, transcode_output_path) cmd = get_ffmpeg_command(input_path, transcode_output_path)
logging.debug(f"Executing FFmpeg command: {' '.join(cmd)}") logging.debug(f"Executing FFmpeg command: {' '.join(cmd)}")
graceful_exit = False
try: try:
with open(ffmpeg_log_file, "w", encoding="utf-8", errors="replace") as f_log: with open(ffmpeg_log_file, "w", encoding="utf-8", errors="replace") as f_log:
# Write the command itself to the top of the log # Write the command itself to the top of the log
@@ -292,12 +294,15 @@ def transcode_file(input_file, output_file=None, skip_av1=True, replace_mode=Fal
f_log.flush() f_log.flush()
# Run FFmpeg and process output in real-time with timestamps # Run FFmpeg and process output in real-time with timestamps
# NOTE: start_new_session=True ensures Ctrl+C doesn't kill ffmpeg immediately
# allowing us to handle the first press gracefully in Python.
process = subprocess.Popen( process = subprocess.Popen(
cmd, cmd,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
text=True, text=True,
bufsize=1, bufsize=1,
start_new_session=True,
) )
regex_elapsed = re.compile(r"elapsed=([0-9:.]+)") regex_elapsed = re.compile(r"elapsed=([0-9:.]+)")
@@ -305,24 +310,44 @@ def transcode_file(input_file, output_file=None, skip_av1=True, replace_mode=Fal
# Write FFmpeg output with timestamps as it's generated # Write FFmpeg output with timestamps as it's generated
try: try:
while True: while True:
output = process.stdout.readline() # pyright: ignore[reportOptionalMemberAccess] try:
if output == "" and process.poll() is not None: output = process.stdout.readline() # pyright: ignore[reportOptionalMemberAccess]
break if output == "" and process.poll() is not None:
if output: break
# capture the elapsed time for later use, only keep the last occurence if output:
if regex_result := re.findall(regex_elapsed, output): # capture the elapsed time for later use, only keep the last occurence
transcoding_duration = regex_result[0] if regex_result := re.findall(regex_elapsed, output):
transcoding_duration = regex_result[0]
timestamp = datetime.datetime.now().strftime( timestamp = datetime.datetime.now().strftime(
"%Y-%m-%d %H:%M:%S" "%Y-%m-%d %H:%M:%S"
) )
f_log.write(f"[{timestamp}] {output.rstrip()}\n") f_log.write(f"[{timestamp}] {output.rstrip()}\n")
f_log.flush() f_log.flush()
except KeyboardInterrupt:
if not graceful_exit:
logging.info(
" > [GRACEFUL EXIT] Signal received. Finishing current file, then exiting script. Press Ctrl+C again to FORCE QUIT."
)
graceful_exit = True
# Continue loop, process is still running because of start_new_session=True
continue
else:
logging.warning(
" >> [FORCE QUIT] Signal received again. Terminating process..."
)
# Explicitly terminate the process group or process
process.terminate()
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
raise # Re-raise to trigger outer cleanup
# Wait for process to complete and get return code # Wait for process to complete and get return code
result = process.wait() result = process.wait()
except Exception as e: except Exception as e:
# Ensure we clean up temporary files on error # Ensure we clean up temporary files on error (e.g. if we forced kill inside loop)
if replace_mode and use_temp_file and transcode_output_path.exists(): if replace_mode and use_temp_file and transcode_output_path.exists():
try: try:
transcode_output_path.unlink() transcode_output_path.unlink()
@@ -410,19 +435,23 @@ def transcode_file(input_file, output_file=None, skip_av1=True, replace_mode=Fal
except Exception: except Exception:
pass pass
if exit_after_next: # If a graceful exit was requested, we exit now that the file is fully processed
logging.info("Quitting early due to user interrupt.") if graceful_exit:
logging.info("Exiting script gracefully as requested.")
sys.exit(0) sys.exit(0)
except Exception as e: except Exception as e:
logging.exception(f"Unexpected error during transcoding of {input_path}: {e}") logging.exception(f"Unexpected error during transcoding of {input_path}: {e}")
except KeyboardInterrupt: except KeyboardInterrupt:
logging.info( # This catches the re-raised exception from the inner loop (2nd Ctrl+C / Force Quit)
"Will quit after the current file is transcoded. Press Ctrl+C again to force quit." logging.info("Transcoding aborted by user.")
) # Ensure cleanup happened (redundant check but safe)
if exit_after_next: if replace_mode and use_temp_file and transcode_output_path.exists():
sys.exit(0) try:
exit_after_next = True transcode_output_path.unlink()
except Exception:
pass
sys.exit(1)
class NewFileHandler(FileSystemEventHandler): class NewFileHandler(FileSystemEventHandler):