transcode.py: handle Ctrl+C better
This commit is contained in:
+51
-22
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user