Add per-step resilience and notifications across all video pipeline scripts

Each pipeline now saves intermediate files permanently before the step most
likely to fail, so a partial failure leaves a recoverable artifact:
- daily_sunrise_video.sh: speed-adjusted video (*-sped.mp4) survives if the
  drawtext overlay step fails; deleted automatically on success
- montage-mvt.sh: speed-adjusted video (*-Sped.mp4) survives if the
  music-mux + attribution overlay step fails; deleted on success
- 4-seasons.sh / montage-mvt.sh / year-end-join.sh: explicit ffmpeg error
  handling with targeted notify.sh calls naming the failed step and the
  surviving artifact path
- montage-mvt.sh / 4-seasons.sh / year-end-join.sh: clip-duration-sum vs
  music-duration drift check warns when cumulative float error exceeds 2 s

https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
This commit is contained in:
Claude
2026-04-19 17:35:26 +00:00
parent 62c787fc0b
commit 9c929f28d8
4 changed files with 130 additions and 23 deletions
+39 -8
View File
@@ -51,6 +51,8 @@ mkdir -p "$output_dir"
music_file=$(find "$music_base_dir" -type f -iname "*${SEASON}*Mvt*${MVT_NUM}*" | sort | head -n 1)
if [ -z "$music_file" ]; then
"$SCRIPT_DIR/notify.sh" "FAILED: montage $SEASON Mvt$MVT_NUM ($ASTRO_YEAR)" \
"Music file not found in $music_base_dir" || true
echo "ERROR: Music file not found for $SEASON Mvt $MVT_NUM in $music_base_dir"
exit 1
fi
@@ -60,11 +62,28 @@ echo "Music: $(basename "$music_file") (${music_duration}s)"
# ── Collect sorted daily clips ────────────────────────────────────────────────
mapfile -d '' daily_clips < <(find "$video_dir" -maxdepth 1 -name "*-final.mp4" -print0 | sort -z)
if [ "${#daily_clips[@]}" -eq 0 ]; then
"$SCRIPT_DIR/notify.sh" "FAILED: montage $SEASON Mvt$MVT_NUM ($ASTRO_YEAR)" \
"No *-final.mp4 clips found in $video_dir" || true
echo "ERROR: No *-final.mp4 clips in $video_dir"
exit 1
fi
echo "Found ${#daily_clips[@]} daily clips"
# ── Validate clip duration sum against music ──────────────────────────────────
total_clip_dur=0
for clip in "${daily_clips[@]}"; do
d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$clip")
total_clip_dur=$(echo "scale=3; $total_clip_dur + $d" | bc)
done
clip_drift=$(echo "scale=3; $total_clip_dur - $music_duration" | bc | sed 's/^-//')
echo "Sum of clips: ${total_clip_dur}s Music: ${music_duration}s Drift: ${clip_drift}s"
if awk "BEGIN{exit !($clip_drift > 2.0)}"; then
echo "WARNING: clip total differs from music by ${clip_drift}s — speed pass will compensate"
"$SCRIPT_DIR/notify.sh" \
"WARNING: $SEASON Mvt$MVT_NUM clip drift ${clip_drift}s ($ASTRO_YEAR)" \
"Clips sum ${total_clip_dur}s vs music ${music_duration}s" || true
fi
# ── Source resolution ─────────────────────────────────────────────────────────
VIDEO_W=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=p=0 "${daily_clips[0]}")
VIDEO_H=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "${daily_clips[0]}")
@@ -105,18 +124,25 @@ pad=${VIDEO_W}:${VIDEO_H}:(ow-iw)/2:(oh-ih)/2,setsar=1" \
-c:v libx264 -pix_fmt yuv420p -crf "$CRF_MONTAGE" -an \
-y "$temp_concat"
# ── Step 2: Speed-adjust to match music duration exactly ─────────────────────
# ── Step 2: Speed-adjust — saved permanently so music/overlay failure is recoverable
# Deleted automatically if step 3 succeeds.
concat_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$temp_concat")
speed_factor=$(echo "scale=6; $concat_dur / $music_duration" | bc)
echo "Step 2/3: speed $speed_factor (raw=${concat_dur}s → music=${music_duration}s)"
temp_sped=$(mktemp --suffix=.mp4); TMPFILES+=("$temp_sped")
ffmpeg -loglevel warning \
sped_file="$output_dir/${MVT_START}_${SEASON}_Mvt${MVT_NUM}-Sped.mp4"
if ! ffmpeg -loglevel warning \
-i "$temp_concat" \
-vf "setpts=PTS/$speed_factor" \
-c:v libx264 -pix_fmt yuv420p -crf "$CRF_MONTAGE" -an \
-t "$music_duration" -y "$temp_sped"
-t "$music_duration" -y "$sped_file"; then
rm -f "$sped_file"
"$SCRIPT_DIR/notify.sh" "FAILED: montage step 2/3 (speed) $SEASON Mvt$MVT_NUM ($ASTRO_YEAR)" \
"speed-adjust failed — concat temp lost; re-run montage-mvt.sh" || true
exit 1
fi
rm "$temp_concat"
echo "Speed-adjusted: $sped_file (saved — music+overlay still pending)"
# ── Step 3: Music + fades + attribution overlay ───────────────────────────────
# Attribution overlay: semi-transparent bar across the top for the first
@@ -152,16 +178,21 @@ DT="${DT}:alpha='${ALPHA}':enable='${ENABLE}'"
output_file="$output_dir/${MVT_START}_${SEASON}_Mvt${MVT_NUM}-Montage.mp4"
ffmpeg -loglevel warning \
-i "$temp_sped" -i "$music_file" \
# If this fails, $sped_file survives for manual recovery (re-run step 3 only).
if ! ffmpeg -loglevel warning \
-i "$sped_file" -i "$music_file" \
-filter_complex \
"[0:v]fade=t=in:st=0:d=${FADE_DUR},fade=t=out:st=${fade_out_start}:d=${FADE_DUR},${DT}[vout];\
[1:a]afade=t=in:st=0:d=${FADE_DUR},afade=t=out:st=${fade_out_start}:d=${FADE_DUR}[aout]" \
-map "[vout]" -map "[aout]" \
-c:v libx264 -pix_fmt yuv420p -c:a aac -b:a "$AUDIO_BITRATE" \
-t "$music_duration" -y "$output_file"
rm "$temp_sped"
-t "$music_duration" -y "$output_file"; then
"$SCRIPT_DIR/notify.sh" "FAILED: montage step 3/3 (music+overlay) $SEASON Mvt$MVT_NUM ($ASTRO_YEAR)" \
"Music/overlay failed — sped video saved: $(basename "$sped_file")" || true
exit 1
fi
rm -f "$sped_file"
echo "Done: $output_file"
# ── Notify ────────────────────────────────────────────────────────────────────