From b911b228bbd5e7e96e1d5e94afe4f73ce2a18f0c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 19 Apr 2026 18:42:46 +0000 Subject: [PATCH] Degrade gracefully on overlay/audio failure; notify on upload success Sunrise: - Overlay failure no longer exits 1; the speed-only video is renamed to the expected final path so OnSuccess still fires and the upload still happens - sunrise2mm.py now notifies on every upload outcome (success, post failure, file-not-found) via notify.sh so the user always knows whether the video reached Mattermost Montage: - Music+overlay failure no longer exits 1; the sped video is promoted to the *-Montage.mp4 path so year-end-join can still include this movement (as a silent, no-overlay clip) and the user is notified of the degraded output - Year-end join trigger is now outside the success/failure branch so it fires in both cases https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ --- daily_sunrise_video.sh | 25 +++++++++++++------------ montage-mvt.sh | 29 +++++++++++++++-------------- sunrise2mm.py | 25 +++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/daily_sunrise_video.sh b/daily_sunrise_video.sh index 97e0869..0bfd3a4 100644 --- a/daily_sunrise_video.sh +++ b/daily_sunrise_video.sh @@ -140,22 +140,23 @@ DT="${DT}:x=w-tw-18:y=(h-th)/2" DT="${DT}:shadowcolor=black@0.55:shadowx=1:shadowy=1" # ── Step 3: Overlay — sunrise time burned in ────────────────────────────────── -# If this fails, $sped_video survives at its permanent path for manual recovery. +# If overlay fails, the sped video is promoted to the upload target so +# OnSuccess still fires and the upload happens (without timestamp overlay). final_video="$output_dir/$current_date-daily-sunrise.mp4" echo "Step 3/3: overlay → $final_video" -if ! ffmpeg -loglevel warning \ +if ffmpeg -loglevel warning \ -i "$sped_video" \ -vf "${DT}" \ -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" -an \ -y "$final_video"; then - "$SCRIPT_DIR/notify.sh" "FAILED: sunrise overlay $current_date" \ - "Overlay failed — speed-only video saved: $(basename "$sped_video")" || true - exit 1 + actual_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$final_video") + echo "Done: $final_video (${actual_dur}s, sunrise at ${SR_TIME})" + rm -f "$sped_video" + "$SCRIPT_DIR/notify.sh" "Sunrise ready: $current_date" \ + "$(basename "$final_video") — ${actual_dur}s, sunrise at ${SR_TIME}" || true +else + mv "$sped_video" "$final_video" + echo "Overlay failed — promoting speed-only video as upload target" + "$SCRIPT_DIR/notify.sh" "WARNING: sunrise overlay failed $current_date" \ + "Overlay step failed — uploading speed-only video (no timestamp)" || true fi - -actual_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$final_video") -echo "Done: $final_video (${actual_dur}s, sunrise at ${SR_TIME})" - -rm -f "$sped_video" -"$SCRIPT_DIR/notify.sh" "Sunrise ready: $current_date" \ - "$(basename "$final_video") — ${actual_dur}s, sunrise at ${SR_TIME}" || true diff --git a/montage-mvt.sh b/montage-mvt.sh index 5929ce2..d55b10c 100755 --- a/montage-mvt.sh +++ b/montage-mvt.sh @@ -178,8 +178,9 @@ DT="${DT}:alpha='${ALPHA}':enable='${ENABLE}'" output_file="$output_dir/${MVT_START}_${SEASON}_Mvt${MVT_NUM}-Montage.mp4" -# If this fails, $sped_file survives for manual recovery (re-run step 3 only). -if ! ffmpeg -loglevel warning \ +# If music/overlay fails, the sped video is promoted to the output path so +# year-end-join can still include this movement (as a silent, no-overlay clip). +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];\ @@ -187,20 +188,20 @@ if ! ffmpeg -loglevel warning \ -map "[vout]" -map "[aout]" \ -c:v libx264 -pix_fmt yuv420p -c:a aac -b:a "$AUDIO_BITRATE" \ -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 + rm -f "$sped_file" + echo "Done: $output_file" + "$SCRIPT_DIR/notify.sh" \ + "Montage ready: $SEASON Mvt $MVT_NUM ($ASTRO_YEAR)" \ + "$(basename "$output_file") — ${music_duration}s" \ + || true +else + mv "$sped_file" "$output_file" + echo "Music+overlay failed — silent video promoted to: $output_file" + "$SCRIPT_DIR/notify.sh" \ + "WARNING: montage audio+overlay failed — $SEASON Mvt$MVT_NUM ($ASTRO_YEAR)" \ + "Silent video saved as $(basename "$output_file") — re-run montage-mvt.sh to retry" || true fi -rm -f "$sped_file" -echo "Done: $output_file" - -# ── Notify ──────────────────────────────────────────────────────────────────── -"$SCRIPT_DIR/notify.sh" \ - "Montage ready: $SEASON Mvt $MVT_NUM ($ASTRO_YEAR)" \ - "$(basename "$output_file") — ${music_duration}s" \ - || true # notifications are best-effort - # ── Trigger year-end join on last Autumn movement ──────────────────────────── if [ "$SEASON" = "Autumn" ] && [ "$MVT_NUM" = "3" ]; then echo "Last movement of astronomical year $ASTRO_YEAR — triggering year-end join..." diff --git a/sunrise2mm.py b/sunrise2mm.py index 403111e..5419fc1 100755 --- a/sunrise2mm.py +++ b/sunrise2mm.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import requests import os +import subprocess from datetime import datetime import pathlib @@ -91,9 +92,33 @@ if os.path.isfile(final_video_file): if message_response.status_code == 201: print("Message posted successfully.") + subprocess.run( + [str(_here / 'notify.sh'), + f"Sunrise uploaded: {today_date_str}", + f"Video posted to Mattermost successfully"], + check=False + ) else: print(f"Failed to post message: {message_response.text}") + subprocess.run( + [str(_here / 'notify.sh'), + f"FAILED: sunrise Mattermost post {today_date_str}", + f"File uploaded but post failed: {message_response.status_code}"], + check=False + ) else: print(f"Failed to upload {final_video_file}: {response.text}") + subprocess.run( + [str(_here / 'notify.sh'), + f"FAILED: sunrise upload {today_date_str}", + f"Mattermost file upload failed: {response.status_code}"], + check=False + ) else: print(f"No video file found at {final_video_file}.") + subprocess.run( + [str(_here / 'notify.sh'), + f"FAILED: sunrise upload {today_date_str}", + f"Video file not found: {final_video_file}"], + check=False + )