From f3839ce1cf9e1bbe399036c1095af2ca6b53be45 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Apr 2026 17:14:17 +0000 Subject: [PATCH] Fix overlay vertical text; test-sunrise captures fresh RTSP frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Overlay fix (both scripts): Switch from text='...\n...' to drawtext's textfile= option. Writing each character on its own line eliminates the \n escape ambiguity in ffmpeg's filter-string single-quote parser, which was causing 'n' to appear literally instead of as a line break. test-sunrise.sh redesign: Now captures NUM_FRAMES fresh RTSP frames (120s at default interval) at run time instead of reading pre-existing frames from the capture directory. The RTSP URL must be set in .env — the script fails early with a clear message if it is not. https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ --- daily_sunrise_video.sh | 19 ++++--- test-sunrise.sh | 110 +++++++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 55 deletions(-) diff --git a/daily_sunrise_video.sh b/daily_sunrise_video.sh index 1118685..9463ae2 100644 --- a/daily_sunrise_video.sh +++ b/daily_sunrise_video.sh @@ -72,7 +72,8 @@ fi temp_list=$(mktemp --suffix=.txt) raw_video="" temp_audio="" -trap 'rm -f "$temp_list" "$raw_video" "$temp_audio" 2>/dev/null || true' EXIT +temp_text="" +trap 'rm -f "$temp_list" "$raw_video" "$temp_audio" "$temp_text" 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)") @@ -143,13 +144,17 @@ echo "Speed-adjusted: ${sped_dur}s (saved — overlay still pending)" SR_TIME=$(echo "$sunrise_time_local" | cut -d'-' -f1,2 | tr '-' ':') DT="" if [ "${SUNRISE_OVERLAY_ENABLED:-true}" = "true" ]; then - SR_VERT="${SR_TIME:0:1}\\\\n${SR_TIME:1:1}\\\\n${SR_TIME:2:1}\\\\n${SR_TIME:3:1}\\\\n${SR_TIME:4:1}" - DT="drawtext" - [ -n "$FONT" ] && DT="${DT}=fontfile='${FONT}'" || DT="${DT}" - DT="${DT}:text='${SR_VERT}'" + 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" + if [ -n "$FONT" ]; then + DT="drawtext=fontfile='${FONT}':textfile='${temp_text}'" + else + DT="drawtext=textfile='${temp_text}'" + fi DT="${DT}:fontcolor=white@${SUNRISE_OVERLAY_OPACITY}" - DT="${DT}:fontsize=h/22" - DT="${DT}:line_spacing=4" + DT="${DT}:fontsize=h/22:line_spacing=4" DT="${DT}:x=w-tw-18:y=(h-th)/2" DT="${DT}:shadowcolor=black@0.55:shadowx=1:shadowy=1" fi diff --git a/test-sunrise.sh b/test-sunrise.sh index d603001..188fe2b 100755 --- a/test-sunrise.sh +++ b/test-sunrise.sh @@ -1,10 +1,11 @@ #!/bin/bash -# test-sunrise.sh — smoke-test the sunrise pipeline using recent captured frames. +# test-sunrise.sh — smoke-test the sunrise pipeline using freshly captured frames. # -# Uses the same steps as daily_sunrise_video.sh: -# 1. raw video from the most recent ~2 min of JPEGs (12 frames @ 10s interval) +# Captures NUM_FRAMES × CAPTURE_INTERVAL seconds of RTSP frames from SUNRISE_CAM, +# then runs the same 4-step pipeline as daily_sunrise_video.sh: +# 1. raw video from captured JPEGs # 2. speed-adjust to SUNRISE_TARGET_SECS -# 3. grab 10s of live RTSP audio centred on the mid-point frame +# 3. grab SUNRISE_TARGET_SECS of live RTSP audio # 4. mix audio + burn vertical time overlay # # Usage: @@ -24,23 +25,48 @@ SUNRISE_OVERLAY_OPACITY="${SUNRISE_OVERLAY_OPACITY:-0.45}" OUTPUT="${1:-/tmp/sunrise-test.mp4}" CAM="$SUNRISE_CAM" -TODAY=$(date +%Y-%m-%d) -image_dir="$BASE_DIR/$CAM/$TODAY" NUM_FRAMES=12 # ~2 minutes at 10s interval -# ── Collect frames ──────────────────────────────────────────────────────────── -if [ ! -d "$image_dir" ]; then - echo "ERROR: no frames today at $image_dir" - echo " Is capture.sh running? systemctl --user status sky-cam-capture-${CAM}.service" +# ── RTSP URL ────────────────────────────────────────────────────────────────── +rtsp_var="CAM_RTSP_${CAM}" +RTSP_URL="${!rtsp_var:-}" +if [ -z "$RTSP_URL" ]; then + echo "ERROR: CAM_RTSP_${CAM} not set in .env" + echo " Add it: CAM_RTSP_${CAM}='rtsp://user:pass@ip:554/stream'" exit 1 fi -mapfile -t frames < <(find "$image_dir" -name "*.jpg" | sort | tail -n "$NUM_FRAMES") +# ── Temp files ──────────────────────────────────────────────────────────────── +TMP_FRAME_DIR=$(mktemp -d --suffix=-sunrise-frames) +TMP_LIST=$(mktemp --suffix=.txt) +TMP_RAW=$(mktemp --suffix=.mp4) +TMP_SPED=$(mktemp --suffix=.mp4) +TMP_AUDIO=$(mktemp --suffix=.m4a) +TMP_TEXT=$(mktemp --suffix=.txt) +cleanup() { + rm -rf "$TMP_FRAME_DIR" + rm -f "$TMP_LIST" "$TMP_RAW" "$TMP_SPED" "$TMP_AUDIO" "$TMP_TEXT" 2>/dev/null || true +} +trap cleanup EXIT + +# ── Capture fresh frames ───────────────────────────────────────────────────── +CAPTURE_SECS=$(( NUM_FRAMES * ${CAPTURE_INTERVAL:-10} )) +echo "Capturing ${NUM_FRAMES} frames over ${CAPTURE_SECS}s from $CAM..." +ffmpeg -loglevel warning \ + -rtsp_transport tcp \ + -i "$RTSP_URL" \ + -t "$CAPTURE_SECS" \ + -vf "fps=1/${CAPTURE_INTERVAL:-10}" \ + -f image2 -strftime 1 \ + -q:v 2 \ + "${TMP_FRAME_DIR}/%H-%M-%S.jpg" || true + +mapfile -t frames < <(find "$TMP_FRAME_DIR" -name "*.jpg" | sort) if [ "${#frames[@]}" -lt 1 ]; then - echo "ERROR: no frames found in $image_dir" + echo "ERROR: no frames captured from $CAM — check RTSP URL in .env" exit 1 fi -echo "Frames: ${#frames[@]} most-recent from $image_dir" +echo "Captured: ${#frames[@]} frames" # ── Mid-point frame → overlay time ─────────────────────────────────────────── mid_idx=$(( ${#frames[@]} / 2 )) @@ -49,11 +75,11 @@ mid_hms=$(basename "$mid_frame" .jpg) # HH-MM-SS MID_TIME=$(echo "$mid_hms" | cut -c1-5 | tr '-' ':') # HH:MM echo "Mid-point frame: $mid_hms → overlay time: $MID_TIME" -# Vertical text: each character on its own line. -# Inside ffmpeg single-quoted text values, \ is an escape prefix so \n → n. -# We need \\n so ffmpeg parses it as \n which drawtext renders as a newline. -# In bash double-quotes \\\\n → \\n (the value stored in the variable). -MID_VERT="${MID_TIME:0:1}\\\\n${MID_TIME:1:1}\\\\n${MID_TIME:2:1}\\\\n${MID_TIME:3:1}\\\\n${MID_TIME:4:1}" +# Write each character on its own line for drawtext's textfile option. +# Avoids \n escape ambiguity across ffmpeg filter-string escaping levels. +printf '%s\n%s\n%s\n%s\n%s' \ + "${MID_TIME:0:1}" "${MID_TIME:1:1}" "${MID_TIME:2:1}" "${MID_TIME:3:1}" "${MID_TIME:4:1}" \ + > "$TMP_TEXT" # ── Font detection ──────────────────────────────────────────────────────────── FONT="" @@ -69,16 +95,9 @@ if [ -z "$FONT" ]; then done fi -# ── Temp files ──────────────────────────────────────────────────────────────── -TMP_LIST=$(mktemp --suffix=.txt) -TMP_RAW=$(mktemp --suffix=.mp4) -TMP_SPED=$(mktemp --suffix=.mp4) -TMP_AUDIO=$(mktemp --suffix=.m4a) -cleanup() { rm -f "$TMP_LIST" "$TMP_RAW" "$TMP_SPED" "$TMP_AUDIO" 2>/dev/null || true; } -trap cleanup EXIT - -# Write concat list with explicit duration per frame so raw video represents -# real elapsed time: 12 frames × CAPTURE_INTERVAL seconds = ~2 minutes. +# ── Write concat list ───────────────────────────────────────────────────────── +# Explicit duration per frame so raw video represents real elapsed time: +# 12 frames × CAPTURE_INTERVAL seconds = ~2 minutes. # The final entry needs a duplicate without duration (ffmpeg concat requirement). for f in "${frames[@]}"; do printf "file '%s'\nduration %s\n" "$f" "${CAPTURE_INTERVAL:-10}" @@ -106,28 +125,20 @@ ffmpeg -loglevel warning \ -t "$SUNRISE_TARGET_SECS" \ -y "$TMP_SPED" -# ── Step 3: grab 10s of live audio centred on mid-point ────────────────────── -# Connects to the RTSP stream briefly — audio from "right now" stands in for -# the real sunrise-audio.m4a that capture.sh would record at sunrise. +# ── Step 3: grab audio centred on mid-point ──────────────────────────────────── echo "Step 3/4: recording ${SUNRISE_TARGET_SECS}s of live audio from camera..." -rtsp_var="CAM_RTSP_${CAM}" -RTSP_URL="${!rtsp_var:-}" HAS_AUDIO=false -if [ -n "$RTSP_URL" ]; then - if ffmpeg -loglevel warning \ - -rtsp_transport tcp \ - -i "$RTSP_URL" \ - -t "$SUNRISE_TARGET_SECS" \ - -vn -c:a aac -b:a 64k \ - -y "$TMP_AUDIO"; then - HAS_AUDIO=true - echo " Audio: captured OK" - else - echo " Audio: capture failed — continuing without audio" - fi +if ffmpeg -loglevel warning \ + -rtsp_transport tcp \ + -i "$RTSP_URL" \ + -t "$SUNRISE_TARGET_SECS" \ + -vn -c:a aac -b:a 64k \ + -y "$TMP_AUDIO"; then + HAS_AUDIO=true + echo " Audio: captured OK" else - echo " Audio: CAM_RTSP_${CAM} not set — skipping" + echo " Audio: capture failed — continuing without audio" fi # ── Step 4: mix audio + burn overlay ───────────────────────────────────────── @@ -135,8 +146,11 @@ echo "Step 4/4: mix audio + overlay → $OUTPUT" mkdir -p "$(dirname "$OUTPUT")" FADE_OUT=$(echo "scale=1; $SUNRISE_TARGET_SECS - 0.5" | bc) -DT="drawtext=fontfile='${FONT}'" -DT="${DT}:text='${MID_VERT}'" +if [ -n "$FONT" ]; then + DT="drawtext=fontfile='${FONT}':textfile='${TMP_TEXT}'" +else + DT="drawtext=textfile='${TMP_TEXT}'" +fi DT="${DT}:fontcolor=white@${SUNRISE_OVERLAY_OPACITY}" DT="${DT}:fontsize=h/22:line_spacing=4" DT="${DT}:x=w-tw-18:y=(h-th)/2"