Showing 1 changed files with 313 additions and 13 deletions
+313 -13
garmin_varia_transcode.sh
@@ -18,6 +18,7 @@ DEFAULT_MODE="hardware"
18 18
 DEFAULT_EXTENSIONS="mp4,mov,avi,m4v"
19 19
 DEFAULT_CRF_HEVC=20
20 20
 DEFAULT_CRF_H264=19
21
+DEFAULT_STAGING_RAMDISK_MB=8192
21 22
 
22 23
 # Garmin publicly declares a 140-degree field of view.
23 24
 # We infer practical focal metadata from this single spec.
@@ -41,6 +42,15 @@ MOVE_SOURCE=false
41 42
 SOURCE_PROVIDED=false
42 43
 DEST_PROVIDED=false
43 44
 STAGING_PROVIDED=false
45
+STAGING_RAMDISK_MB="$DEFAULT_STAGING_RAMDISK_MB"
46
+AUTO_CREATED_STAGING_RAMDISK=false
47
+AUTO_CREATED_STAGING_PATH=""
48
+STAGING_RAMDISK_CREATED_AT=0
49
+RUN_STARTED_AT=0
50
+FIRST_ENCODE_STARTED_AT=0
51
+DEBUG_TIMING_LIMIT=0
52
+DEBUG_TIMING_FILES=0
53
+DEBUG_TIMING_STOPPED=false
44 54
 FAIL_FAST=true
45 55
 SOURCE_READABLE_MODE="normal"
46 56
 APPLE_REPACK_FALLBACK=true
@@ -68,6 +78,8 @@ INVALID_SOURCES_SKIPPED=0
68 78
 DESTINATION_FAILURES=0
69 79
 TOTAL_VIDEO_TIME_SEC=0
70 80
 TOTAL_FILE_REAL_TIME_SEC=0
81
+INPUT_BYTES_PROCESSED=0
82
+OUTPUT_BYTES_PROCESSED=0
71 83
 
72 84
 DURATION_TOLERANCE_SEC=1.0
73 85
 STOP_AFTER_CURRENT=false
@@ -85,6 +97,9 @@ Options:
85 97
   -d, --destination, --output DIR Destination directory (default: current directory)
86 98
   --staging-dir DIR               Temporary staging directory for intermediate output files
87 99
                                   (falls back to destination if staging cannot be used)
100
+  --staging-ramdisk-mb N          RAM disk size in MB when auto-creating missing /Volumes staging
101
+                                  directory on macOS (default: 8192)
102
+  --debug-timing N                Process at most N video files, then stop and print timing stats
88 103
   --mode MODE                     Encoding mode (default: hardware); see Encoding Modes below
89 104
   --crf N                         CRF value for quality/compat modes (lower = better; default: 20/19)
90 105
   --no-overwrite                  Skip files that already exist at destination (default: overwrite)
@@ -161,7 +176,7 @@ log_progress_start() {
161 176
   local input_file="$1"
162 177
   local ts
163 178
   ts="$(date '+%Y-%m-%d %H:%M:%S')"
164
-  printf '%s : Transcoding %s ...' "$ts" "$input_file"
179
+  printf '[%s] [INFO] Transcoding %s ...' "$ts" "$input_file"
165 180
   PROGRESS_LINE_OPEN=true
166 181
 }
167 182
 
@@ -177,7 +192,7 @@ log_progress_done() {
177 192
   local input_file="$1"
178 193
   local ts
179 194
   ts="$(date '+%Y-%m-%d %H:%M:%S')"
180
-  echo "$ts : Transcoding $input_file ... done in ${real_elapsed_sec}s (encode ${encode_elapsed_sec}s)"
195
+  echo "[$ts] [INFO] Transcoding $input_file ... done in ${real_elapsed_sec}s (encode ${encode_elapsed_sec}s)"
181 196
 }
182 197
 
183 198
 log_progress_failed() {
@@ -195,7 +210,7 @@ log_progress_failed() {
195 210
 
196 211
   local ts
197 212
   ts="$(date '+%Y-%m-%d %H:%M:%S')"
198
-  echo "$ts : Transcoding $input_file ... FAILED after ${real_elapsed_sec}s (encode ${encode_elapsed_sec}s, rc=$ffmpeg_rc, log=${ffmpeg_log:-n/a})"
213
+  echo "[$ts] [ERROR] Transcoding $input_file ... FAILED after ${real_elapsed_sec}s (encode ${encode_elapsed_sec}s, rc=$ffmpeg_rc, log=${ffmpeg_log:-n/a})"
199 214
 }
200 215
 
201 216
 log_progress_skipped_unreadable() {
@@ -209,7 +224,7 @@ log_progress_skipped_unreadable() {
209 224
 
210 225
   local ts
211 226
   ts="$(date '+%Y-%m-%d %H:%M:%S')"
212
-  echo "$ts : Transcoding $input_file ... SKIPPED (unreadable/corrupted source)"
227
+  echo "[$ts] [INFO] Transcoding $input_file ... SKIPPED (unreadable/corrupted source)"
213 228
 }
214 229
 
215 230
 die() {
@@ -262,6 +277,23 @@ format_seconds() {
262 277
   printf '%02d:%02d:%02d' "$h" "$m" "$s"
263 278
 }
264 279
 
280
+format_bytes_human() {
281
+  local bytes="$1"
282
+  awk -v b="$bytes" 'BEGIN {
283
+    split("B KiB MiB GiB TiB PiB", u, " ");
284
+    i = 1;
285
+    while (b >= 1024 && i < 6) {
286
+      b = b / 1024;
287
+      i++;
288
+    }
289
+    if (i == 1) {
290
+      printf "%d %s", b, u[i];
291
+    } else {
292
+      printf "%.2f %s", b, u[i];
293
+    }
294
+  }'
295
+}
296
+
265 297
 print_final_report() {
266 298
   local total_elapsed_sec="$1"
267 299
   local total_elapsed_fmt="$2"
@@ -273,6 +305,12 @@ print_final_report() {
273 305
   local avg_file_real_time_fmt="$8"
274 306
   local avg_video_time_sec="$9"
275 307
   local avg_video_time_fmt="${10}"
308
+  local startup_warmup_sec="${11}"
309
+  local startup_warmup_fmt="${12}"
310
+  local staging_warmup_sec="${13}"
311
+  local staging_warmup_fmt="${14}"
312
+  local input_bytes_processed="${15}"
313
+  local output_bytes_processed="${16}"
276 314
 
277 315
   printf '\n'
278 316
   printf 'Run Summary\n'
@@ -297,8 +335,22 @@ print_final_report() {
297 335
   printf '| %-25s | %7s | %-8s |\n' "Encode time total" "${TOTAL_VIDEO_TIME_SEC}s" "$(format_seconds "$TOTAL_VIDEO_TIME_SEC")"
298 336
   printf '| %-25s | %7s | %-8s |\n' "Post-processing total" "${file_post_total_sec}s" "$file_post_total_fmt"
299 337
   printf '| %-25s | %7s | %-8s |\n' "Non-file overhead" "${run_non_file_overhead_sec}s" "$run_non_file_overhead_fmt"
338
+  if [[ "$startup_warmup_sec" -ge 0 ]]; then
339
+    printf '| %-25s | %7s | %-8s |\n' "Startup warm-up" "${startup_warmup_sec}s" "$startup_warmup_fmt"
340
+  fi
341
+  if [[ "$staging_warmup_sec" -ge 0 ]]; then
342
+    printf '| %-25s | %7s | %-8s |\n' "Staging warm-up" "${staging_warmup_sec}s" "$staging_warmup_fmt"
343
+  fi
300 344
   printf '+---------------------------+---------+----------+\n'
301 345
 
346
+  printf '\nData Volume\n'
347
+  printf '+---------------------------+----------------+-----------------+\n'
348
+  printf '| %-25s | %-14s | %-15s |\n' "Metric" "Bytes" "Human"
349
+  printf '+---------------------------+----------------+-----------------+\n'
350
+  printf '| %-25s | %14s | %-15s |\n' "Input processed" "${input_bytes_processed} B" "$(format_bytes_human "$input_bytes_processed")"
351
+  printf '| %-25s | %14s | %-15s |\n' "Output written" "${output_bytes_processed} B" "$(format_bytes_human "$output_bytes_processed")"
352
+  printf '+---------------------------+----------------+-----------------+\n'
353
+
302 354
   printf '\nPer-File Averages\n'
303 355
   printf '+---------------------------+---------+----------+\n'
304 356
   printf '| %-25s | %-7s | %-8s |\n' "Metric" "Seconds" "Elapsed"
@@ -416,6 +468,55 @@ path_is_within() {
416 468
   [[ "$child" == "$parent" || "$child" == "$parent"/* ]]
417 469
 }
418 470
 
471
+file_size_bytes_or_zero() {
472
+  local file_path="$1"
473
+  local file_size="0"
474
+
475
+  file_size="$(stat -f%z "$file_path" 2>/dev/null || true)"
476
+  if [[ ! "$file_size" =~ ^[0-9]+$ ]]; then
477
+    file_size="$(stat -c%s "$file_path" 2>/dev/null || true)"
478
+  fi
479
+
480
+  if [[ "$file_size" =~ ^[0-9]+$ ]]; then
481
+    printf '%s\n' "$file_size"
482
+  else
483
+    printf '0\n'
484
+  fi
485
+}
486
+
487
+dir_available_bytes_or_zero() {
488
+  local dir_path="$1"
489
+  local avail_kb=""
490
+
491
+  avail_kb="$(df -Pk "$dir_path" 2>/dev/null | awk 'NR==2 {print $4}' || true)"
492
+  if [[ "$avail_kb" =~ ^[0-9]+$ ]]; then
493
+    printf '%s\n' $((avail_kb * 1024))
494
+  else
495
+    printf '0\n'
496
+  fi
497
+}
498
+
499
+staging_has_space_for_input() {
500
+  local staging_dir="$1"
501
+  local input_file="$2"
502
+  local input_size_bytes=0
503
+  local needed_bytes=0
504
+  local available_bytes=0
505
+
506
+  [[ -n "$staging_dir" && -d "$staging_dir" && -w "$staging_dir" ]] || return 1
507
+
508
+  input_size_bytes="$(file_size_bytes_or_zero "$input_file")"
509
+  if [[ "$input_size_bytes" -le 0 ]]; then
510
+    return 1
511
+  fi
512
+
513
+  # Keep one simple rule: need roughly 2x input size in staging.
514
+  # This covers temp output plus metadata rewrite overhead.
515
+  needed_bytes=$((input_size_bytes * 2))
516
+  available_bytes="$(dir_available_bytes_or_zero "$staging_dir")"
517
+  [[ "$available_bytes" -ge "$needed_bytes" ]]
518
+}
519
+
419 520
 join_cmd_for_log() {
420 521
   local out=""
421 522
   local arg
@@ -446,6 +547,16 @@ parse_args() {
446 547
         STAGING_PROVIDED=true
447 548
         shift 2
448 549
         ;;
550
+      --staging-ramdisk-mb)
551
+        require_value "$1" "${2:-}"
552
+        STAGING_RAMDISK_MB="$2"
553
+        shift 2
554
+        ;;
555
+      --debug-timing)
556
+        require_value "$1" "${2:-}"
557
+        DEBUG_TIMING_LIMIT="$2"
558
+        shift 2
559
+        ;;
449 560
       --mode)
450 561
         require_value "$1" "${2:-}"
451 562
         MODE="$2"
@@ -541,6 +652,22 @@ parse_args() {
541 652
   if [[ -n "$CRF_OVERRIDE" && ! "$CRF_OVERRIDE" =~ ^[0-9]+$ ]]; then
542 653
     die "--crf must be an integer"
543 654
   fi
655
+
656
+  if [[ "$DEBUG_TIMING_LIMIT" != "0" ]]; then
657
+    if ! [[ "$DEBUG_TIMING_LIMIT" =~ ^[0-9]+$ ]]; then
658
+      die "--debug-timing must be a positive integer"
659
+    fi
660
+    if [[ "$DEBUG_TIMING_LIMIT" -le 0 ]]; then
661
+      die "--debug-timing must be greater than 0"
662
+    fi
663
+  fi
664
+
665
+  if ! [[ "$STAGING_RAMDISK_MB" =~ ^[0-9]+$ ]]; then
666
+    die "--staging-ramdisk-mb must be a positive integer"
667
+  fi
668
+  if [[ "$STAGING_RAMDISK_MB" -le 0 ]]; then
669
+    die "--staging-ramdisk-mb must be greater than 0"
670
+  fi
544 671
 }
545 672
 
546 673
 check_tools() {
@@ -957,6 +1084,96 @@ normalize_dest_dir() {
957 1084
   DEST_DIR="$(to_abs_path "$DEST_DIR")"
958 1085
 }
959 1086
 
1087
+create_missing_staging_ramdisk_if_needed() {
1088
+  local staging_path="$1"
1089
+  local ramdisk_name=""
1090
+  local sectors=0
1091
+  local dev=""
1092
+  local mount_point=""
1093
+
1094
+  staging_path="${staging_path%/}"
1095
+
1096
+  if [[ -d "$staging_path" ]]; then
1097
+    printf '%s\n' "$staging_path"
1098
+    return
1099
+  fi
1100
+
1101
+  if [[ "$(uname -s)" != "Darwin" ]]; then
1102
+    return
1103
+  fi
1104
+
1105
+  case "$staging_path" in
1106
+    /Volumes/*)
1107
+      ;;
1108
+    *)
1109
+      return
1110
+      ;;
1111
+  esac
1112
+
1113
+  ramdisk_name="$(basename "$staging_path")"
1114
+  if [[ -z "$ramdisk_name" || "$ramdisk_name" == "." || "$ramdisk_name" == ".." ]]; then
1115
+    return
1116
+  fi
1117
+
1118
+  if [[ "$staging_path" != "/Volumes/$ramdisk_name" ]]; then
1119
+    # Only auto-create for top-level /Volumes/<Name>, not nested paths.
1120
+    return
1121
+  fi
1122
+
1123
+  [[ -x "/usr/bin/hdiutil" ]] || die "hdiutil not found; cannot auto-create RAM disk"
1124
+  [[ -x "/usr/sbin/diskutil" ]] || die "diskutil not found; cannot auto-create RAM disk"
1125
+
1126
+  sectors=$((STAGING_RAMDISK_MB * 2048))
1127
+  vlog_msg "INFO" "Creating RAM disk for staging: /Volumes/$ramdisk_name (${STAGING_RAMDISK_MB}MB)"
1128
+  dev="$(/usr/bin/hdiutil attach -nomount "ram://$sectors" 2>/dev/null | /usr/bin/awk 'NR==1 {print $1}' || true)"
1129
+  if [[ -z "$dev" ]]; then
1130
+    return
1131
+  fi
1132
+
1133
+  if ! /usr/sbin/diskutil eraseVolume APFS "$ramdisk_name" "$dev" >/dev/null 2>&1; then
1134
+    /usr/bin/hdiutil detach "$dev" >/dev/null 2>&1 || true
1135
+    return
1136
+  fi
1137
+
1138
+  mount_point="$(/usr/sbin/diskutil info "$dev" 2>/dev/null | /usr/bin/awk -F': *' '/Mount Point/ {print $2; exit}')"
1139
+  if [[ -n "$mount_point" && -d "$mount_point" ]]; then
1140
+    AUTO_CREATED_STAGING_RAMDISK=true
1141
+    AUTO_CREATED_STAGING_PATH="$mount_point"
1142
+    STAGING_RAMDISK_CREATED_AT="$(date +%s)"
1143
+    printf '%s\n' "$mount_point"
1144
+    return
1145
+  fi
1146
+
1147
+  if [[ -d "$staging_path" ]]; then
1148
+    AUTO_CREATED_STAGING_RAMDISK=true
1149
+    AUTO_CREATED_STAGING_PATH="$staging_path"
1150
+    STAGING_RAMDISK_CREATED_AT="$(date +%s)"
1151
+    printf '%s\n' "$staging_path"
1152
+    return
1153
+  fi
1154
+}
1155
+
1156
+attempt_unmount_auto_staging_ramdisk() {
1157
+  if [[ "$AUTO_CREATED_STAGING_RAMDISK" != true || -z "$AUTO_CREATED_STAGING_PATH" ]]; then
1158
+    return
1159
+  fi
1160
+
1161
+  if [[ "$(uname -s)" != "Darwin" ]]; then
1162
+    return
1163
+  fi
1164
+
1165
+  if /usr/sbin/diskutil eject "$AUTO_CREATED_STAGING_PATH" >/dev/null 2>&1; then
1166
+    log_msg "INFO" "Auto-created staging RAM disk unmounted: $AUTO_CREATED_STAGING_PATH"
1167
+    return
1168
+  fi
1169
+
1170
+  if [[ -d "$AUTO_CREATED_STAGING_PATH" ]]; then
1171
+    log_msg "WARN" "Could not unmount auto-created staging RAM disk; it remains mounted: $AUTO_CREATED_STAGING_PATH"
1172
+  else
1173
+    log_msg "WARN" "Could not confirm unmount status for auto-created staging RAM disk: $AUTO_CREATED_STAGING_PATH"
1174
+  fi
1175
+}
1176
+
960 1177
 normalize_staging_dir() {
961 1178
   if [[ "$STAGING_PROVIDED" != true ]]; then
962 1179
     STAGING_DIR=""
@@ -964,6 +1181,16 @@ normalize_staging_dir() {
964 1181
   fi
965 1182
 
966 1183
   STAGING_DIR="$(to_abs_path "$STAGING_DIR")"
1184
+  if [[ ! -d "$STAGING_DIR" ]]; then
1185
+    local created_staging_dir=""
1186
+    created_staging_dir="$(create_missing_staging_ramdisk_if_needed "$STAGING_DIR")"
1187
+    if [[ -n "$created_staging_dir" && -d "$created_staging_dir" ]]; then
1188
+      STAGING_DIR="$created_staging_dir"
1189
+      log_msg "INFO" "Created staging RAM disk: $STAGING_DIR"
1190
+    else
1191
+      die "Staging directory not found: $STAGING_DIR"
1192
+    fi
1193
+  fi
967 1194
   [[ -d "$STAGING_DIR" ]] || die "Staging directory not found: $STAGING_DIR"
968 1195
   STAGING_DIR="$(cd "$STAGING_DIR" && pwd)"
969 1196
   [[ -w "$STAGING_DIR" ]] || die "Staging directory not writable: $STAGING_DIR"
@@ -1047,7 +1274,9 @@ process_video_file() {
1047 1274
   local rel_path output_file temp_output_file out_dir
1048 1275
   local display_path
1049 1276
   local encode_input_file
1277
+  local preferred_temp_dir=""
1050 1278
   local repacked_input_file=""
1279
+  local input_size_bytes=0 output_size_bytes=0
1051 1280
   local file_started_at file_ended_at file_real_elapsed_sec
1052 1281
   local encode_started_at encode_ended_at encode_elapsed_sec=0
1053 1282
   local post_elapsed_sec
@@ -1101,14 +1330,22 @@ process_video_file() {
1101 1330
 
1102 1331
   build_video_args
1103 1332
 
1104
-  temp_output_file="$(make_temp_output_file "$output_file" "$STAGING_DIR")"
1333
+  preferred_temp_dir="$STAGING_DIR"
1334
+  if [[ "$STAGING_PROVIDED" == true && -n "$STAGING_DIR" ]]; then
1335
+    if ! staging_has_space_for_input "$STAGING_DIR" "$encode_input_file"; then
1336
+      preferred_temp_dir=""
1337
+      log_msg "WARN" "Insufficient staging space; using destination temp path for: $input_file"
1338
+    fi
1339
+  fi
1340
+
1341
+  temp_output_file="$(make_temp_output_file "$output_file" "$preferred_temp_dir")"
1105 1342
   if [[ -z "$temp_output_file" ]]; then
1106 1343
     ERRORS=$((ERRORS + 1))
1107 1344
     DESTINATION_FAILURES=$((DESTINATION_FAILURES + 1))
1108 1345
     log_msg "ERROR" "Could not create temporary output file: $output_file"
1109 1346
     return 3
1110 1347
   fi
1111
-  if [[ "$STAGING_PROVIDED" == true ]] && ! path_is_within "$temp_output_file" "$STAGING_DIR"; then
1348
+  if [[ "$STAGING_PROVIDED" == true && -n "$STAGING_DIR" ]] && ! path_is_within "$temp_output_file" "$STAGING_DIR"; then
1112 1349
     log_msg "WARN" "Staging unavailable for temp output; using destination directory: $temp_output_file"
1113 1350
   fi
1114 1351
 
@@ -1144,6 +1381,18 @@ process_video_file() {
1144 1381
   fi
1145 1382
 
1146 1383
   vlog_msg "INFO" "Encoding: $input_file -> $output_file (temp: $temp_output_file)"
1384
+  if [[ "$FIRST_ENCODE_STARTED_AT" -eq 0 ]]; then
1385
+    local startup_warmup_sec=0
1386
+    local warmup_elapsed_sec=0
1387
+    FIRST_ENCODE_STARTED_AT="$(date +%s)"
1388
+    if [[ "$AUTO_CREATED_STAGING_RAMDISK" == true && "$STAGING_RAMDISK_CREATED_AT" -gt 0 ]]; then
1389
+      warmup_elapsed_sec=$((FIRST_ENCODE_STARTED_AT - STAGING_RAMDISK_CREATED_AT))
1390
+      if [[ "$warmup_elapsed_sec" -lt 0 ]]; then
1391
+        warmup_elapsed_sec=0
1392
+      fi
1393
+      log_msg "INFO" "Staging RAM disk warm-up before first encode: ${warmup_elapsed_sec}s / $(format_seconds "$warmup_elapsed_sec")"
1394
+    fi
1395
+  fi
1147 1396
   encode_started_at="$(date +%s)"
1148 1397
   vlog_msg "CHECKPOINT" "encode_start: $input_file"
1149 1398
 
@@ -1251,10 +1500,6 @@ process_video_file() {
1251 1500
   if [[ "$MOVE_SOURCE" == true ]]; then
1252 1501
     if ! validate_transcoded_output "$encode_input_file" "$temp_output_file"; then
1253 1502
       cleanup_transcode_artifacts "$temp_output_file" "$output_file"
1254
-      if [[ "$STOP_AFTER_CURRENT" == true && "$INTERRUPT_COUNT" -gt 0 ]]; then
1255
-        cleanup_repacked_input "$repacked_input_file"
1256
-        return 4
1257
-      fi
1258 1503
       ERRORS=$((ERRORS + 1))
1259 1504
       if destination_cannot_accept_file "$input_file" "$out_dir" ""; then
1260 1505
         DESTINATION_FAILURES=$((DESTINATION_FAILURES + 1))
@@ -1263,6 +1508,7 @@ process_video_file() {
1263 1508
       fi
1264 1509
       # Validation failed but destination is healthy: the source or encoder produced
1265 1510
       # a corrupt/truncated output. Treat as source error so --keep-going can skip it.
1511
+      INVALID_SOURCES_SKIPPED=$((INVALID_SOURCES_SKIPPED + 1))
1266 1512
       log_msg "ERROR" "Encode produced invalid output (source may be corrupt): $input_file"
1267 1513
       cleanup_repacked_input "$repacked_input_file"
1268 1514
       return 2
@@ -1291,6 +1537,11 @@ process_video_file() {
1291 1537
     fi
1292 1538
   fi
1293 1539
 
1540
+  input_size_bytes="$(file_size_bytes_or_zero "$input_file")"
1541
+  output_size_bytes="$(file_size_bytes_or_zero "$output_file")"
1542
+  INPUT_BYTES_PROCESSED=$((INPUT_BYTES_PROCESSED + input_size_bytes))
1543
+  OUTPUT_BYTES_PROCESSED=$((OUTPUT_BYTES_PROCESSED + output_size_bytes))
1544
+
1294 1545
   if [[ "$MOVE_SOURCE" == true ]]; then
1295 1546
     if rm -f "$input_file"; then
1296 1547
       vlog_msg "INFO" "Removed source after successful validation: $input_file"
@@ -1434,6 +1685,7 @@ EOF
1434 1685
 main() {
1435 1686
   local total_started_at total_ended_at total_elapsed_sec total_elapsed_fmt
1436 1687
   total_started_at="$(date +%s)"
1688
+  RUN_STARTED_AT="$total_started_at"
1437 1689
 
1438 1690
   trap 'handle_interrupt' INT TERM
1439 1691
 
@@ -1472,13 +1724,33 @@ main() {
1472 1724
 
1473 1725
     local process_rc=0
1474 1726
     if process_video_file "$f"; then
1475
-      continue
1727
+      process_rc=0
1476 1728
     else
1477 1729
       process_rc=$?
1478 1730
     fi
1479 1731
 
1732
+    if [[ "$DEBUG_TIMING_LIMIT" -gt 0 ]]; then
1733
+      DEBUG_TIMING_FILES=$((DEBUG_TIMING_FILES + 1))
1734
+      if [[ "$DEBUG_TIMING_FILES" -ge "$DEBUG_TIMING_LIMIT" ]]; then
1735
+        DEBUG_TIMING_STOPPED=true
1736
+      fi
1737
+    fi
1738
+
1739
+    if [[ "$DEBUG_TIMING_STOPPED" == true ]]; then
1740
+      log_msg "INFO" "Debug timing limit reached after $DEBUG_TIMING_FILES file(s); stopping before next file"
1741
+      break
1742
+    fi
1743
+
1744
+    if [[ "$process_rc" -eq 0 ]]; then
1745
+      continue
1746
+    fi
1747
+
1480 1748
     case "$process_rc" in
1481 1749
       2)
1750
+        if [[ "$STOP_AFTER_CURRENT" == true && "$INTERRUPT_COUNT" -gt 0 ]]; then
1751
+          log_msg "INFO" "Stopped by user after current file"
1752
+          break
1753
+        fi
1482 1754
         log_msg "INFO" "Continuing after unreadable/corrupted source file"
1483 1755
         continue
1484 1756
         ;;
@@ -1501,7 +1773,9 @@ main() {
1501 1773
     esac
1502 1774
   done
1503 1775
 
1504
-  if [[ "$STOP_AFTER_CURRENT" == true ]]; then
1776
+  if [[ "$DEBUG_TIMING_STOPPED" == true ]]; then
1777
+    log_msg "INFO" "Skipping sidecar copy and manifest because debug timing mode requested early stop"
1778
+  elif [[ "$STOP_AFTER_CURRENT" == true ]]; then
1505 1779
     log_msg "INFO" "Skipping sidecar copy and manifest because run was stopped by user"
1506 1780
   elif [[ "$ERRORS" -eq 0 ]]; then
1507 1781
     copy_sidecars_json
@@ -1518,6 +1792,8 @@ main() {
1518 1792
   local avg_file_real_time_sec=0 avg_file_real_time_fmt="00:00:00"
1519 1793
   local file_post_total_sec=0 file_post_total_fmt="00:00:00"
1520 1794
   local run_non_file_overhead_sec=0 run_non_file_overhead_fmt="00:00:00"
1795
+  local startup_warmup_sec=-1 startup_warmup_fmt="n/a"
1796
+  local staging_warmup_sec=-1 staging_warmup_fmt="n/a"
1521 1797
 
1522 1798
   if [[ "$VIDEOS_PROCESSED" -gt 0 ]]; then
1523 1799
     avg_video_time_sec=$((TOTAL_VIDEO_TIME_SEC / VIDEOS_PROCESSED))
@@ -1538,6 +1814,22 @@ main() {
1538 1814
   fi
1539 1815
   run_non_file_overhead_fmt="$(format_seconds "$run_non_file_overhead_sec")"
1540 1816
 
1817
+  if [[ "$RUN_STARTED_AT" -gt 0 && "$FIRST_ENCODE_STARTED_AT" -gt 0 ]]; then
1818
+    startup_warmup_sec=$((FIRST_ENCODE_STARTED_AT - RUN_STARTED_AT))
1819
+    if [[ "$startup_warmup_sec" -lt 0 ]]; then
1820
+      startup_warmup_sec=0
1821
+    fi
1822
+    startup_warmup_fmt="$(format_seconds "$startup_warmup_sec")"
1823
+  fi
1824
+
1825
+  if [[ "$AUTO_CREATED_STAGING_RAMDISK" == true && "$STAGING_RAMDISK_CREATED_AT" -gt 0 && "$FIRST_ENCODE_STARTED_AT" -gt 0 ]]; then
1826
+    staging_warmup_sec=$((FIRST_ENCODE_STARTED_AT - STAGING_RAMDISK_CREATED_AT))
1827
+    if [[ "$staging_warmup_sec" -lt 0 ]]; then
1828
+      staging_warmup_sec=0
1829
+    fi
1830
+    staging_warmup_fmt="$(format_seconds "$staging_warmup_sec")"
1831
+  fi
1832
+
1541 1833
   print_final_report \
1542 1834
     "$total_elapsed_sec" \
1543 1835
     "$total_elapsed_fmt" \
@@ -1548,7 +1840,15 @@ main() {
1548 1840
     "$avg_file_real_time_sec" \
1549 1841
     "$avg_file_real_time_fmt" \
1550 1842
     "$avg_video_time_sec" \
1551
-    "$avg_video_time_fmt"
1843
+    "$avg_video_time_fmt" \
1844
+    "$startup_warmup_sec" \
1845
+    "$startup_warmup_fmt" \
1846
+    "$staging_warmup_sec" \
1847
+    "$staging_warmup_fmt" \
1848
+    "$INPUT_BYTES_PROCESSED" \
1849
+    "$OUTPUT_BYTES_PROCESSED"
1850
+
1851
+  attempt_unmount_auto_staging_ramdisk
1552 1852
 
1553 1853
   if [[ "$ERRORS" -gt 0 ]]; then
1554 1854
     exit 1