diff --git a/daily_sunrise_video.sh b/daily_sunrise_video.sh index 4b45c70..cba0e87 100644 --- a/daily_sunrise_video.sh +++ b/daily_sunrise_video.sh @@ -56,7 +56,8 @@ fi temp_list=$(mktemp --suffix=.txt) raw_video="" -trap 'rm -f "$temp_list" "$raw_video" 2>/dev/null || true' EXIT +temp_audio="" +trap 'rm -f "$temp_list" "$raw_video" "$temp_audio" 2>/dev/null || true' EXIT find "$image_dir" -type f -name "*.jpg" | sort | while read -r img; do img_sec=$(time_to_seconds "$(basename "$img" .jpg)") @@ -139,11 +140,15 @@ DT="${DT}:line_spacing=4" DT="${DT}:x=w-tw-18:y=(h-th)/2" DT="${DT}:shadowcolor=black@0.55:shadowx=1:shadowy=1" -# ── Step 3: Overlay + optional audio ───────────────────────────────────────── -# Priority: (1) camera mic recording, (2) library fallback, (3) no audio. -# If audio mixing fails, step 3 retries with overlay-only so the video is -# always made. If overlay itself fails, the sped video is promoted so upload -# still fires. +# ── Step 3: Audio mix + overlay — each independently optional/fallible ──────── +# Step 3a: mixes audio into a temp copy of the sped video (video stream is +# copied, not re-encoded — fast, and lets step 3b fail independently). +# Step 3b: burns the overlay onto whatever 3a produced. +# Failure matrix → upload always fires: +# audio ✓ overlay ✓ → final has audio + overlay +# audio ✓ overlay ✗ → final has audio, no overlay +# audio ✗ overlay ✓ → final has overlay, no audio +# audio ✗ overlay ✗ → sped video promoted (no audio, no overlay) final_video="$output_dir/$current_date-daily-sunrise.mp4" fade_out=$(echo "scale=1; $SUNRISE_TARGET_SECS - 0.5" | bc) @@ -154,13 +159,11 @@ audio_offset="0" cam_audio="$image_dir/sunrise-audio.m4a" if [ "${AUDIO_ENABLED:-false}" = "true" ]; then if [ -f "$cam_audio" ]; then - # Camera recording: offset centres the clip on actual sunrise buffer_sec=$(( ${AUDIO_PRE_BUFFER_MIN:-2} * 60 )) audio_offset=$(echo "scale=1; $SUNRISE_PRE_MIN * 60 + $buffer_sec - $SUNRISE_TARGET_SECS / 2" | bc) audio_src="$cam_audio" echo "Audio: camera recording offset=${audio_offset}s" else - # Library fallback: random file from sunrise-sounds/ library_dir="$SCRIPT_DIR/sunrise-sounds" if [ -d "$library_dir" ]; then random_file=$(find "$library_dir" -name "*.mp3" | shuf -n 1 2>/dev/null || true) @@ -172,48 +175,55 @@ if [ "${AUDIO_ENABLED:-false}" = "true" ]; then fi fi -# ── Attempt overlay + audio (falls back to overlay-only if audio fails) ─────── -audio_mixed=false -step3_ok=true +# ── Step 3a: mix audio (video copied, not re-encoded) ──────────────────────── +work_video="$sped_video" +has_audio=false if [ -n "$audio_src" ]; then - echo "Step 3/3: overlay + audio → $final_video" + temp_audio=$(mktemp --suffix=.mp4) + echo "Step 3a/3: mixing audio → temp" if ffmpeg -loglevel warning \ -i "$sped_video" \ -ss "$audio_offset" -t "$SUNRISE_TARGET_SECS" -i "$audio_src" \ - -filter_complex "[0:v]${DT}[vout];[1:a]afade=t=in:st=0:d=0.5,afade=t=out:st=${fade_out}:d=0.5[aout]" \ - -map "[vout]" -map "[aout]" \ - -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" \ - -c:a aac -b:a 128k \ - -y "$final_video"; then - audio_mixed=true + -filter_complex "[1:a]afade=t=in:st=0:d=0.5,afade=t=out:st=${fade_out}:d=0.5[aout]" \ + -map "0:v" -map "[aout]" \ + -c:v copy -c:a aac -b:a 128k \ + -y "$temp_audio"; then + work_video="$temp_audio" + has_audio=true + echo "Audio: mixed OK" else - echo "Audio mix failed — retrying with overlay only" + rm -f "$temp_audio"; temp_audio="" + echo "Audio mix failed — step 3b will be overlay-only" "$SCRIPT_DIR/notify.sh" "WARNING: sunrise audio mix failed $current_date" \ - "Audio could not be mixed — falling back to overlay-only" || true + "Audio could not be mixed — continuing with overlay only" || true [ "$audio_src" = "$cam_audio" ] && rm -f "$cam_audio" fi fi -if ! $audio_mixed; then - echo "Step 3/3: overlay (no audio) → $final_video" - ffmpeg -loglevel warning \ - -i "$sped_video" \ - -vf "${DT}" \ - -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" -an \ - -y "$final_video" || step3_ok=false -fi +# ── Step 3b: burn overlay ───────────────────────────────────────────────────── +echo "Step 3b/3: overlay → $final_video" +if $has_audio; then audio_out_flags=(-c:a copy); else audio_out_flags=(-an); fi -if $step3_ok; then +if ffmpeg -loglevel warning \ + -i "$work_video" \ + -vf "${DT}" \ + -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" \ + "${audio_out_flags[@]}" \ + -y "$final_video"; then 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" + rm -f "$sped_video" "$temp_audio"; temp_audio="" [ -f "$cam_audio" ] && rm -f "$cam_audio" "$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" + if $has_audio; then promote_label="audio-mixed"; else promote_label="speed-only"; fi + mv "$work_video" "$final_video" + [ "$work_video" != "$sped_video" ] && rm -f "$sped_video" + temp_audio="" + [ -f "$cam_audio" ] && rm -f "$cam_audio" + echo "Overlay failed — promoting ${promote_label} video as upload target" "$SCRIPT_DIR/notify.sh" "WARNING: sunrise overlay failed $current_date" \ - "Overlay step failed — uploading speed-only video (no timestamp)" || true + "Overlay failed — uploading ${promote_label} video (no timestamp)" || true fi