Fix overlay vertical text; test-sunrise captures fresh RTSP frames
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
This commit is contained in:
+12
-7
@@ -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
|
||||
|
||||
+62
-48
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user