Showing 1 changed files with 50 additions and 14 deletions
+50 -14
garmin_varia_transcode.sh
@@ -234,26 +234,54 @@ handle_interrupt() {
234 234
   INTERRUPT_COUNT=$((INTERRUPT_COUNT + 1))
235 235
   STOP_AFTER_CURRENT=true
236 236
 
237
+  # If quiet-mode progress is mid-line, break it before interrupt logs.
238
+  if [[ "$PROGRESS_LINE_OPEN" == true ]]; then
239
+    printf '\n'
240
+    PROGRESS_LINE_OPEN=false
241
+  fi
242
+
237 243
   if [[ "$INTERRUPT_COUNT" -eq 1 ]]; then
238
-    if [[ -n "$CURRENT_FFMPEG_PID" ]]; then
239
-      log_msg "WARN" "Stop requested. Will stop after current file. Press Ctrl+C again to abort current encode immediately."
240
-    else
241
-      log_msg "WARN" "Stop requested. Exiting before next file."
242
-    fi
243
-    return
244
+    # ffmpeg is in the same process group and already received SIGINT; it will
245
+    # stop gracefully on its own.  A second Ctrl+C force-kills it if needed.
246
+    log_msg "WARN" "Interrupted; stopping after current encode. Ctrl+C again to force-kill."
247
+  elif [[ -n "$CURRENT_FFMPEG_PID" ]]; then
248
+    log_msg "WARN" "Force-stopping encode (pid=$CURRENT_FFMPEG_PID)."
249
+    kill -9 "$CURRENT_FFMPEG_PID" 2>/dev/null || true
244 250
   fi
251
+}
245 252
 
246
-  if [[ -n "$CURRENT_FFMPEG_PID" ]]; then
247
-    log_msg "WARN" "Force-stopping current encode (pid=$CURRENT_FFMPEG_PID)."
248
-    kill -INT "$CURRENT_FFMPEG_PID" 2>/dev/null || true
253
+# Drain all pending keystrokes from /dev/tty (non-blocking) and set
254
+# STOP_AFTER_CURRENT if any of them is 'q'/'Q'.  Self-contained: saves/restores
255
+# stty settings so it works regardless of the terminal's current mode.
256
+# Uses min=0 time=0 so the terminal's read() returns immediately when the
257
+# buffer is empty (bash treats a 0-byte read as EOF and exits the loop).
258
+# -t 1 is a safety net for platforms where select() on a VMIN=0 fd does not
259
+# report readable-when-empty; in that case we block at most 1s then exit.
260
+check_for_quit_key() {
261
+  [[ -e /dev/tty ]] || return 0
262
+  local key old_stty found=false
263
+  old_stty=$(stty -g </dev/tty 2>/dev/null) || return 0
264
+  stty -echo -icanon min 0 time 0 </dev/tty 2>/dev/null \
265
+    || { stty "$old_stty" </dev/tty 2>/dev/null || true; return 0; }
266
+  while IFS= read -r -s -n 1 -t 1 key </dev/tty 2>/dev/null; do
267
+    if [[ "$key" == "q" || "$key" == "Q" ]]; then
268
+      found=true
269
+      break
270
+    fi
271
+  done
272
+  stty "$old_stty" </dev/tty 2>/dev/null || true
273
+  if [[ "$found" == true ]]; then
274
+    STOP_AFTER_CURRENT=true
275
+    log_msg "INFO" "Quit key pressed; stopping before next file"
249 276
   fi
250 277
 }
251 278
 
252 279
 run_ffmpeg_with_signal_guard() {
253
-  (
254
-    trap '' INT TERM
255
-    exec "$@"
256
-  ) &
280
+  # ffmpeg runs in the same process group as the shell so Ctrl+C (SIGINT)
281
+  # reaches it directly and triggers its own graceful shutdown.  stdin is
282
+  # /dev/null so ffmpeg disables keyboard control and avoids SIGTTIN.
283
+  # CURRENT_FFMPEG_PID is tracked for the force-kill second-Ctrl+C path.
284
+  "$@" </dev/null &
257 285
 
258 286
   CURRENT_FFMPEG_PID=$!
259 287
   if wait "$CURRENT_FFMPEG_PID"; then
@@ -1395,7 +1423,6 @@ process_video_file() {
1395 1423
       if [[ "$warmup_elapsed_sec" -lt 0 ]]; then
1396 1424
         warmup_elapsed_sec=0
1397 1425
       fi
1398
-      log_msg "INFO" "Staging RAM disk warm-up before first encode: ${warmup_elapsed_sec}s / $(format_seconds "$warmup_elapsed_sec")"
1399 1426
     fi
1400 1427
   fi
1401 1428
   encode_started_at="$(date +%s)"
@@ -1431,6 +1458,14 @@ process_video_file() {
1431 1458
   vlog_msg "CHECKPOINT" "encode_end: $input_file (encode_elapsed=${encode_elapsed_sec}s)"
1432 1459
 
1433 1460
   if [[ "$ffmpeg_rc" -ne 0 ]]; then
1461
+    # Ctrl+C reached ffmpeg directly (same process group); it stopped mid-encode.
1462
+    # Treat any user-triggered abort as a clean stop — no error counters, no noise.
1463
+    if [[ "$STOP_AFTER_CURRENT" == true && "$INTERRUPT_COUNT" -gt 0 ]]; then
1464
+      cleanup_transcode_artifacts "$temp_output_file" "$output_file"
1465
+      cleanup_repacked_input "$repacked_input_file"
1466
+      return 4
1467
+    fi
1468
+
1434 1469
     local failure_rc=1
1435 1470
     if destination_cannot_accept_file "$input_file" "$out_dir" "$ffmpeg_log"; then
1436 1471
       failure_rc=3
@@ -1616,6 +1651,7 @@ main() {
1616 1651
 
1617 1652
   local f
1618 1653
   for f in ${VIDEO_FILES+"${VIDEO_FILES[@]}"}; do
1654
+    check_for_quit_key
1619 1655
     if [[ "$STOP_AFTER_CURRENT" == true ]]; then
1620 1656
       log_msg "INFO" "Stop requested; ending before next file"
1621 1657
       break