#!/bin/bash
# daily_sunrise_video.sh — collect today's sunrise images, speed-adjust to
# SUNRISE_TARGET_SECS, burn the local sunrise time vertically in the lower-right
# corner, and write the final video.  Upload is handled by a separate systemd
# service (sky-cam-sunrise-upload) triggered via OnSuccess= so the two jobs
# have independent log entries and failure states.
#
# Resilience: the speed-adjusted video is saved permanently before the overlay
# step.  If overlay fails, the sped video survives at:
#   <output_dir>/<date>-daily-sunrise-sped.mp4
# Re-run this script once the issue is resolved; the sped file is deleted
# automatically when the overlay step succeeds.
#
# Usage:
#   ./daily_sunrise_video.sh [cam_name]
#   ./daily_sunrise_video.sh [cam_name] --test [MINUTES]
#
# --test MINUTES  skips sunrise.py and the capture-window wait; uses the last
#                 MINUTES minutes of captured frames (default 2), grabs live
#                 RTSP audio, writes <date>-daily-sunrise-test.mp4.

set -euo pipefail

SCRIPT_DIR="$(dirname "$(realpath "$0")")"
source "$SCRIPT_DIR/sky-cam.conf"
source "$SCRIPT_DIR/.env" 2>/dev/null || true
export TZ="$TIMEZONE"

CAM_NAME="${1:-$SUNRISE_CAM}"
TEST_MODE=false
TEST_MINUTES=2
if [[ "${2:-}" == "--test" ]]; then
    TEST_MODE=true
    TEST_MINUTES="${3:-2}"
fi

time_to_seconds() { IFS='-' read -r h m s <<< "$1"; echo $((10#$h * 3600 + 10#$m * 60 + 10#$s)); }

# ── Date / paths ──────────────────────────────────────────────────────────────
current_date=$(date +%Y-%m-%d)
output_date=$(date +%Y-%m)
image_dir="$BASE_DIR/$CAM_NAME/$current_date"
output_dir="$MOVIES_DIR/$CAM_NAME/$output_date/sunrise-only"
mkdir -p "$output_dir"

# ── Sunrise time (skipped in test mode) ───────────────────────────────────────
SR_TIME=""
sunrise_time_local=""
if ! $TEST_MODE; then
    sunrise_time=$(python3 "$SCRIPT_DIR/sunrise.py")
    if [[ -z "$sunrise_time" ]]; then
        "$SCRIPT_DIR/notify.sh" "FAILED: sunrise $current_date" \
            "Could not retrieve sunrise time from sunrise.py" || true
        echo "Error: Failed to retrieve sunrise time."
        exit 1
    fi
    echo "Sunrise (local): $sunrise_time"
    sunrise_time_local=$(TZ="$TIMEZONE" date -d "$sunrise_time" +"%H-%M-%S")
    SR_TIME=$(echo "$sunrise_time_local" | cut -d'-' -f1,2 | tr '-' ':')
fi

# ── Capture window (skipped in test mode) ─────────────────────────────────────
if ! $TEST_MODE; then
    sunrise_sec=$(time_to_seconds "$sunrise_time_local")
    start_sec=$((sunrise_sec - SUNRISE_PRE_MIN * 60))
    end_sec=$((sunrise_sec + SUNRISE_POST_MIN * 60))
    echo "Window: $((start_sec/3600)):$(printf '%02d' $(((start_sec%3600)/60))) → $((end_sec/3600)):$(printf '%02d' $(((end_sec%3600)/60)))"
    midnight=$(date -d "today 00:00:00" +%s)
    now_day_sec=$(( $(date +%s) - midnight ))
    if [ "$now_day_sec" -lt "$end_sec" ]; then
        wait_sec=$(( end_sec - now_day_sec + 30 ))
        echo "Waiting ${wait_sec}s for capture window to finish (ends $(date -d "@$(( midnight + end_sec ))" '+%H:%M:%S'))..."
        sleep "$wait_sec"
    else
        echo "Capture window already closed at $(date -d "@$(( midnight + end_sec ))" '+%H:%M:%S') — processing available images"
    fi
fi

# ── Collect images ────────────────────────────────────────────────────────────
if [ ! -d "$image_dir" ]; then
    $TEST_MODE || "$SCRIPT_DIR/notify.sh" "FAILED: sunrise $current_date" \
        "Image directory not found: $image_dir" || true
    echo "Error: image directory not found: $image_dir"
    exit 1
fi

temp_list=$(mktemp --suffix=.txt)
raw_video=""
temp_audio=""
temp_text=""
temp_live_audio=""
trap 'rm -f "$temp_list" "$raw_video" "$temp_audio" "$temp_text" "$temp_live_audio" 2>/dev/null || true' EXIT

if $TEST_MODE; then
    test_frames=$(( TEST_MINUTES * 60 / ${CAPTURE_INTERVAL:-10} ))
    mapfile -t images < <(find "$image_dir" -name "*.jpg" | sort | tail -n "$test_frames")
    echo "Test mode: last ${TEST_MINUTES} min  (${#images[@]} frames)"
else
    mapfile -t images < <(
        find "$image_dir" -type f -name "*.jpg" | sort | while read -r img; do
            img_sec=$(time_to_seconds "$(basename "$img" .jpg)")
            [[ $img_sec -ge $start_sec && $img_sec -le $end_sec ]] && echo "$img"
        done
    )
fi

if [ "${#images[@]}" -lt 1 ]; then
    $TEST_MODE || "$SCRIPT_DIR/notify.sh" "FAILED: sunrise $current_date" \
        "No images found in sunrise window" || true
    echo "No images found."
    exit 1
fi
first_t=$(basename "${images[0]}"  .jpg | tr '-' ':')
last_t=$(basename  "${images[-1]}" .jpg | tr '-' ':')
echo "Images: ${#images[@]}  (${first_t} → ${last_t}  overlay: ${SR_TIME})"

if ! $TEST_MODE; then
    last_sec=$(time_to_seconds "$(basename "${images[-1]}" .jpg)")
    gap=$(( end_sec - last_sec ))
    if [ "$gap" -gt $(( ${CAPTURE_INTERVAL:-10} * 3 )) ]; then
        echo "WARNING: last frame is $(( gap / 60 ))min ${gap}s before window end — capture gap near sunrise"
        echo "         Check: systemctl --user status sky-cam-capture-${CAM_NAME}.service"
    fi
fi

# Concat list with explicit frame duration so each frame represents real elapsed
# time.  Without this, all frames collapse to timestamp 0 → one-frame video.
# The final entry needs a duplicate without duration (ffmpeg concat requirement).
for img in "${images[@]}"; do
    printf "file '%s'\nduration %s\n" "$img" "${CAPTURE_INTERVAL:-10}"
done > "$temp_list"
printf "file '%s'\n" "${images[-1]}" >> "$temp_list"

# ── Set overlay time ──────────────────────────────────────────────────────────
if $TEST_MODE; then
    mid_idx=$(( ${#images[@]} / 2 ))
    mid_hms=$(basename "${images[$mid_idx]}" .jpg)
    SR_TIME=$(echo "$mid_hms" | cut -c1-5 | tr '-' ':')
    echo "Mid-point frame: $mid_hms  →  overlay time: $SR_TIME"
fi

# ── Font detection ────────────────────────────────────────────────────────────
# Picks a TrueType font file for ffmpeg's drawtext filter.  drawtext needs an
# absolute path to a .ttf/.otf file — it cannot use a font *name* like Arial.
#
# To use a different font:
#   1. Find the .ttf path on this machine, e.g.:
#        fc-match "Liberation Mono" --format='%{file}\n'
#        find /usr/share/fonts -name '*.ttf' | grep -i mono
#   2. Either (a) hard-code it below by replacing the fc-match line with:
#        FONT="/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf"
#      or (b) change the fc-match query string ("DejaVu Sans:style=Regular")
#      to your preferred face, e.g. "Liberation Mono:style=Bold".
#   3. Add the chosen path to the fallback list below so the script still works
#      if fc-match is missing.
# Bold faces (e.g. DejaVuSans-Bold.ttf) read better at small sizes against sky.
FONT=""
if command -v fc-match &>/dev/null; then
    FONT=$(fc-match "DejaVu Sans:style=Regular" --format="%{file}" 2>/dev/null || true)
fi
if [ -z "$FONT" ]; then
    for f in \
        /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf \
        /usr/share/fonts/dejavu/DejaVuSans.ttf \
        /usr/share/fonts/TTF/DejaVuSans.ttf \
        /usr/share/fonts/truetype/freefont/FreeSans.ttf; do
        [ -f "$f" ] && { FONT="$f"; break; }
    done
fi

# ── Step 1: Raw video from images ─────────────────────────────────────────────
raw_video=$(mktemp --suffix=.mp4)
echo "Step 1/3: encoding raw video from ${#images[@]} frames..."
if ! ffmpeg -loglevel warning \
    -f concat -safe 0 -i "$temp_list" \
    -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" -vsync 2 -an \
    -y "$raw_video"; then
    $TEST_MODE || "$SCRIPT_DIR/notify.sh" "FAILED: sunrise step 1/3 (encode) $current_date" \
        "ffmpeg raw encode from images failed — check: journalctl -u sky-cam-sunrise.service" || true
    exit 1
fi

raw_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$raw_video")
speed_factor=$(echo "scale=3; $raw_dur / $SUNRISE_TARGET_SECS" | bc)
echo "Raw: ${raw_dur}s  speed factor: ${speed_factor}x"

# ── Step 2: Speed-adjust ──────────────────────────────────────────────────────
# Production: saved permanently so overlay failure is recoverable.
# Test: temporary file, deleted on exit.
# fps=25 forces proper frame rate so all source frames appear in the output.
if $TEST_MODE; then
    sped_video=$(mktemp --suffix=-sped.mp4)
    echo "Step 2/3: speed-adjust (test)..."
else
    sped_video="$output_dir/$CAM_NAME-$current_date-daily-sunrise-sped.mp4"
    echo "Step 2/3: speed-adjust → $sped_video"
fi
if ! ffmpeg -loglevel warning \
    -i "$raw_video" \
    -vf "setpts=PTS/${speed_factor},fps=25" \
    -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" -an \
    -t "$SUNRISE_TARGET_SECS" \
    -y "$sped_video"; then
    rm -f "$sped_video"
    $TEST_MODE || "$SCRIPT_DIR/notify.sh" "FAILED: sunrise step 2/3 (speed) $current_date" \
        "speed-adjust failed — no video saved" || true
    exit 1
fi

sped_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$sped_video")
$TEST_MODE || echo "Speed-adjusted: ${sped_dur}s  (saved — overlay still pending)"
$TEST_MODE && echo "Speed-adjusted: ${sped_dur}s"

# ── Build overlay filter ──────────────────────────────────────────────────────
# Burns the sunrise time onto the video using ffmpeg's drawtext filter.
# Layout is VERTICAL — each character of "HH:MM" is on its own line, stacked
# top-to-bottom in the lower-right corner.  This is achieved by writing each
# char to a separate line of $temp_text, then drawtext renders that file
# verbatim (newlines = line breaks).
#
# To switch to a HORIZONTAL "HH:MM" overlay, replace the printf below with:
#     printf '%s' "$SR_TIME" > "$temp_text"
# and bump fontsize (e.g. h/18) since horizontal text needs less vertical room.
#
# ── Quick-edit cheatsheet for every drawtext parameter ──────────────────────
#
#   FONT FILE   (set above in the "Font detection" block, not here).
#               drawtext requires a .ttf path, not a font name.
#
#   fontcolor   Text color.  Accepts named colors (yellow, white, red, cyan,
#               orange, lime, magenta, gray) or hex (0xFFCC00, 0xFF8800).
#               The "@N" suffix is opacity 0.0–1.0; comes from
#               SUNRISE_OVERLAY_OPACITY in sky-cam.conf.  Examples:
#                 fontcolor=white@0.7         # softer white, 70% opaque
#                 fontcolor=0xFF8800@0.9      # warm orange, 90% opaque
#
#   fontsize    Pixel height of glyphs.  "h/22" = video-height / 22, so it
#               scales with resolution (1080p → ~49px, 720p → ~33px).  Use a
#               smaller divisor for BIGGER text:
#                 fontsize=h/30   → small      fontsize=h/22  → current
#                 fontsize=h/18   → medium     fontsize=h/14  → large
#               Or set an absolute pixel size: fontsize=48
#
#   line_spacing  Pixels of gap between the stacked characters.  Increase for
#                 a more spaced look (e.g. 8 or 12); 0 packs them tight.
#
#   x , y       Position of the text box.  ffmpeg exposes:
#                 w  = video width      h  = video height
#                 tw = text width       th = text height
#               Current values place the box in the BOTTOM-RIGHT with an
#               18px right margin and a bottom margin of 18px + 5% of height.
#               Recipes for the other corners (keep ~18px breathing room):
#                 Top-left:      x=18                   y=18
#                 Top-right:     x=w-tw-18              y=18
#                 Bottom-left:   x=18                   y=h-th-18-h*0.05
#                 Bottom-right:  x=w-tw-18              y=h-th-18-h*0.15   (current)
#                 Centered:      x=(w-tw)/2             y=(h-th)/2
#                 Centered top:  x=(w-tw)/2             y=24
#               To nudge the current spot, change the "18" margins or the
#               "h*0.15" lift (15% above the bottom edge — lower to 0.05 to
#               sit closer to the bottom, raise to 0.20 for more clearance).
#
#   shadowcolor / shadowx / shadowy
#               A 1-pixel black drop-shadow @55% opacity for legibility against
#               bright sky.  Set shadowx=shadowy=0 to disable.  For a thicker
#               outline, swap the shadow* lines for:
#                 :borderw=2:bordercolor=black@0.7
#               (borderw = outline thickness in pixels.)
#
#   Other useful drawtext options you can append with ":name=value":
#               box=1:boxcolor=black@0.4:boxborderw=6   # solid background pill
#               alpha='if(lt(t,1),t,1)'                 # 1-second fade-in
#               enable='between(t,2,8)'                 # only show 2s–8s
#
# Full reference: https://ffmpeg.org/ffmpeg-filters.html#drawtext
DT=""
if [ "${SUNRISE_OVERLAY_ENABLED:-true}" = "true" ]; then
    # Vertical layout: write each char of "HH:MM" on its own line.
    # SR_TIME is exactly 5 chars (e.g. "06:42"); indices 0..4 below.
    # Switch to horizontal by replacing this printf with: printf '%s' "$SR_TIME" > "$temp_text"
    temp_text=$(mktemp --suffix=.txt)
    printf '%s\n%s\n%s\n%s\n%s' \
        "${SR_TIME:0:1}" "${SR_TIME:1:1}" "${SR_TIME:2:1}" "${SR_TIME:3:1}" "${SR_TIME:4:1}" \
        > "$temp_text"

    # Source the glyph file + the text. fontfile is omitted if FONT detection
    # failed above, in which case ffmpeg falls back to its built-in font.
    if [ -n "$FONT" ]; then
        DT="drawtext=fontfile='${FONT}':textfile='${temp_text}'"
    else
        DT="drawtext=textfile='${temp_text}'"
    fi

    # Color + opacity.  Change "yellow" to any color name or 0xRRGGBB.
    # Opacity comes from SUNRISE_OVERLAY_OPACITY in sky-cam.conf.
    DT="${DT}:fontcolor=yellow@${SUNRISE_OVERLAY_OPACITY}"

    # Size + spacing.  fontsize is relative to video height (h/22).
    DT="${DT}:fontsize=h/22:line_spacing=4"

    # Position — bottom-right corner.  See the corner recipes in the comment
    # block above.  The "18" values are pixel margins; "h*0.15" lifts the box
    # 15% of the height off the bottom to clear the lower edge.
    DT="${DT}:x=w-tw-18:y=h-th-18-h*0.15"

    # Drop-shadow for legibility.  Set shadowx=shadowy=0 to disable, or swap
    # for :borderw=2:bordercolor=black@0.7 for a thicker outline.
    DT="${DT}:shadowcolor=black@0.55:shadowx=1:shadowy=1"
fi

# ── Pick audio source ─────────────────────────────────────────────────────────
audio_src=""
audio_offset="0"

if $TEST_MODE; then
    # Test: grab live RTSP audio now
    rtsp_var="CAM_RTSP_${CAM_NAME}"
    RTSP_URL="${!rtsp_var:-}"
    if [ -n "$RTSP_URL" ]; then
        temp_live_audio=$(mktemp --suffix=.m4a)
        echo "Test: recording ${SUNRISE_TARGET_SECS}s of live audio..."
        if ffmpeg -loglevel warning \
            -rtsp_transport tcp \
            -i "$RTSP_URL" \
            -t "$SUNRISE_TARGET_SECS" \
            -vn -c:a aac -b:a 64k \
            -y "$temp_live_audio"; then
            audio_src="$temp_live_audio"
            echo "Audio: live RTSP captured OK"
        else
            echo "Audio: live RTSP failed — continuing without audio"
            rm -f "$temp_live_audio"; temp_live_audio=""
        fi
    else
        echo "Audio: CAM_RTSP_${CAM_NAME} not set — skipping"
    fi
else
    # New path: AUDIO_DIR/sunrise/<cam>/<date>/sunrise-audio.m4a
    # Fallback to old path (BASE_DIR/<cam>/<date>/sunrise-audio.m4a) for migration
    cam_audio="${AUDIO_DIR:-$BASE_DIR/audio}/sunrise/$CAM_NAME/$current_date/sunrise-audio.m4a"
    [ ! -f "$cam_audio" ] && cam_audio="$image_dir/sunrise-audio.m4a"
    if [ "${AUDIO_ENABLED:-false}" = "true" ]; then
        if [ -f "$cam_audio" ]; then
            audio_src="$cam_audio"
            echo "Audio: camera recording (centred on sunrise)"
        fi
        # 2nd fallback: continuous ambient recording — extract the sunrise window
        # Looks in AUDIO_DIR/<cam>/<date>/ for chunk files named HH-MM-SS[.m4a|-tag.m4a]
        # and extracts SUNRISE_TARGET_SECS centred on sunrise_sec from the right chunk.
        if [ -z "$audio_src" ]; then
            _amb_dir="${AUDIO_DIR:-$BASE_DIR/audio}/$CAM_NAME/$current_date"
            if [ -d "$_amb_dir" ]; then
                _best_chunk=""
                _best_sec=-1
                while IFS= read -r _f; do
                    _hms=$(basename "$_f" | cut -c1-8)   # first 8 chars = HH-MM-SS
                    IFS='-' read -r _h _m _s <<< "$_hms"
                    [[ "$_h" =~ ^[0-9]{2}$ ]] || continue
                    _cs=$(( 10#$_h * 3600 + 10#$_m * 60 + 10#$_s ))
                    if [ "$_cs" -le "$sunrise_sec" ] && [ "$_cs" -gt "$_best_sec" ]; then
                        _best_chunk="$_f"; _best_sec=$_cs
                    fi
                done < <(find "$_amb_dir" -maxdepth 1 -name "*.m4a" 2>/dev/null | sort)
                if [ -n "$_best_chunk" ]; then
                    _offset=$(( sunrise_sec - _best_sec - SUNRISE_TARGET_SECS / 2 ))
                    [ "$_offset" -lt 0 ] && _offset=0
                    temp_live_audio=$(mktemp --suffix=.m4a)
                    echo "Audio: extracting sunrise window from ambient chunk $(basename "$_best_chunk") at +${_offset}s..."
                    if ffmpeg -loglevel warning \
                        -ss "$_offset" -i "$_best_chunk" \
                        -t "$SUNRISE_TARGET_SECS" -vn -c:a copy \
                        -y "$temp_live_audio"; then
                        audio_src="$temp_live_audio"
                        echo "Audio: ambient chunk extracted OK"
                    else
                        rm -f "$temp_live_audio"; temp_live_audio=""
                        echo "Audio: ambient chunk extraction failed"
                    fi
                fi
            fi
        fi
        # 3rd fallback: live RTSP — captures current ambient sound right now
        if [ -z "$audio_src" ]; then
            _rtsp_var="CAM_RTSP_${CAM_NAME}"
            _rtsp_url="${!_rtsp_var:-}"
            if [ -n "$_rtsp_url" ]; then
                temp_live_audio=$(mktemp --suffix=.m4a)
                echo "Audio: no saved file — capturing ${SUNRISE_TARGET_SECS}s live from RTSP..."
                if ffmpeg -loglevel warning \
                    -rtsp_transport tcp \
                    -i "$_rtsp_url" \
                    -t "$SUNRISE_TARGET_SECS" \
                    -vn -c:a aac -b:a "${CAPTURE_AUDIO_BITRATE:-96k}" \
                    -y "$temp_live_audio"; then
                    audio_src="$temp_live_audio"
                    echo "Audio: live RTSP captured OK"
                else
                    rm -f "$temp_live_audio"; temp_live_audio=""
                    echo "Audio: live RTSP capture failed — trying yesterday"
                fi
            fi
        fi
        # 4th fallback: yesterday's recording
        if [ -z "$audio_src" ]; then
            yesterday_date=$(date -d "yesterday" +%Y-%m-%d)
            yday_audio="${AUDIO_DIR:-$BASE_DIR/audio}/sunrise/$CAM_NAME/$yesterday_date/sunrise-audio.m4a"
            [ ! -f "$yday_audio" ] && yday_audio="$BASE_DIR/$CAM_NAME/$yesterday_date/sunrise-audio.m4a"
            if [ -f "$yday_audio" ]; then
                audio_src="$yday_audio"
                echo "Audio: yesterday's recording ($yesterday_date)"
            fi
        fi
        # 5th fallback: library
        if [ -z "$audio_src" ]; then
            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)
                if [ -n "$random_file" ]; then
                    audio_src="$random_file"
                    echo "Audio: library fallback  $(basename "$audio_src")"
                fi
            fi
        fi
    fi
fi

# ── 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 (production); test just continues:
#   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)
if $TEST_MODE; then
    demo_dir="${DEMO_DIR:-$MOVIES_DIR/demoio}/$CAM_NAME"
    mkdir -p "$demo_dir"
    final_video="$demo_dir/$CAM_NAME-$current_date-daily-sunrise-test.mp4"
else
    final_video="$output_dir/$CAM_NAME-$current_date-daily-sunrise.mp4"
fi
fade_dur=$(echo "scale=3; $SUNRISE_TARGET_SECS * 0.085" | bc | sed 's/^\./0./')
fade_out=$(echo "scale=3; $SUNRISE_TARGET_SECS - $fade_dur" | bc | sed 's/^\./0./')

# ── Step 3a: mix audio (video copied, not re-encoded) ────────────────────────
work_video="$sped_video"
has_audio=false

if [ -n "$audio_src" ]; then
    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 "[1:a]afade=t=in:st=0:d=${fade_dur},afade=t=out:st=${fade_out}:d=${fade_dur}[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
        rm -f "$temp_audio"; temp_audio=""
        # Retry once with a fresh live RTSP capture before giving up on audio
        _mix_ok=false
        if ! $TEST_MODE; then
            _rtsp_var="CAM_RTSP_${CAM_NAME}"
            _rtsp_url="${!_rtsp_var:-}"
            if [ -n "$_rtsp_url" ]; then
                _live_retry=$(mktemp --suffix=.m4a)
                echo "Audio mix failed — retrying with live RTSP capture..."
                if ffmpeg -loglevel warning \
                    -rtsp_transport tcp -i "$_rtsp_url" \
                    -t "$SUNRISE_TARGET_SECS" -vn \
                    -c:a aac -b:a "${CAPTURE_AUDIO_BITRATE:-96k}" \
                    -y "$_live_retry"; then
                    temp_audio=$(mktemp --suffix=.mp4)
                    if ffmpeg -loglevel warning \
                        -i "$sped_video" \
                        -ss "$audio_offset" -t "$SUNRISE_TARGET_SECS" -i "$_live_retry" \
                        -filter_complex "[1:a]afade=t=in:st=0:d=${fade_dur},afade=t=out:st=${fade_out}:d=${fade_dur}[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: live RTSP retry mix OK"
                        _mix_ok=true
                    else
                        rm -f "$temp_audio"; temp_audio=""
                    fi
                fi
                rm -f "$_live_retry"
            fi
        fi
        if ! $_mix_ok; then
            echo "Audio mix failed — step 3b will be overlay-only"
            if ! $TEST_MODE; then
                _jlog=$(journalctl --user -u sky-cam-audio-capture.service -n 30 --no-pager 2>/dev/null \
                    || journalctl -u sky-cam-audio-capture.service -n 30 --no-pager 2>/dev/null \
                    || echo "(journal unavailable)")
                "$SCRIPT_DIR/notify.sh" "WARNING: sunrise audio mix failed $current_date" \
                    "$(printf 'Audio could not be mixed — continuing with overlay only\n\naudio-capture log:\n%s' "$_jlog")" || true
                [ "$audio_src" = "${cam_audio:-}" ] && rm -f "${cam_audio:-}"
            fi
        fi
    fi
fi

# ── Step 3b: burn overlay (or promote work_video directly if disabled) ────────
if $has_audio; then audio_out_flags=(-c:a copy); else audio_out_flags=(-an); fi

step3b_ok=false
if [ -n "$DT" ]; then
    echo "Step 3b/3: overlay → $final_video"
    if ffmpeg -loglevel warning \
        -i "$work_video" \
        -vf "${DT}" \
        -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" \
        "${audio_out_flags[@]}" \
        -y "$final_video"; then
        step3b_ok=true
    else
        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=""
        if ! $TEST_MODE; then
            [ -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 failed — uploading ${promote_label} video (no timestamp)" || true
        fi
        step3b_ok=true  # degraded but recoverable — upload still fires
    fi
else
    echo "Step 3b/3: overlay disabled — promoting as final"
    mv "$work_video" "$final_video"
    [ "$work_video" != "$sped_video" ] && rm -f "$sped_video"
    temp_audio=""
    step3b_ok=true
fi

if $step3b_ok; then
    actual_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$final_video")
    echo "Done: $final_video  (${actual_dur}s, time: ${SR_TIME})"
    rm -f "$sped_video" "$temp_audio" 2>/dev/null || true; temp_audio=""

    if $TEST_MODE; then
        # ── Demo retention ────────────────────────────────────────────────────
        demo_retain="${DEMO_RETENTION_DAYS:-8}"
        if [ "$demo_retain" -gt 0 ]; then
            deleted_demo=$(find "${DEMO_DIR:-$MOVIES_DIR/demoio}" \
                -name "*-daily-sunrise-test.mp4" \
                -mtime +"$demo_retain" -print -delete 2>/dev/null | wc -l)
            [ "$deleted_demo" -gt 0 ] && \
                echo "Demo retention: deleted $deleted_demo test file(s) older than ${demo_retain} days" || true
        fi
    else
        [ -f "${cam_audio:-}" ] && rm -f "${cam_audio:-}" || true
        "$SCRIPT_DIR/notify.sh" "Sunrise ready: $current_date" \
            "$(basename "$final_video") — ${actual_dur}s, sunrise at ${SR_TIME} | $final_video" || true

        # ── Rolling retention ─────────────────────────────────────────────────
        retain="${SUNRISE_RETENTION_DAYS:-10}"
        if [ "$retain" -gt 0 ]; then
            deleted_old=$(find "$MOVIES_DIR/$CAM_NAME" \
                -path "*/sunrise-only/*-daily-sunrise.mp4" \
                -mtime +"$retain" -print -delete 2>/dev/null | wc -l)
            [ "$deleted_old" -gt 0 ] && \
                echo "Retention: deleted $deleted_old sunrise video(s) older than ${retain} days" || true
        fi
    fi
fi

exit 0
