Files
sky-cam/test-sunrise.sh
T
Claude eba8ddd550 Fix sunrise overlay vertical text and test-sunrise.sh pipeline bugs
- Overlay text was showing characters run together (e.g. '1n2n') because
  inside ffmpeg single-quoted filter text, \n is consumed as just 'n'.
  Fix: use \\\\n in bash double-quotes which stores \\n in the variable,
  which ffmpeg then parses as \n, which drawtext renders as a newline.
  Applied to both test-sunrise.sh (MID_VERT) and daily_sunrise_video.sh
  (SR_VERT, replacing the awk one-liner with the same bash approach).

- test-sunrise.sh concat list was missing per-frame duration entries,
  causing 12 frames to encode as 0.48s (25fps default) and then slow
  down instead of compress.  Fixed by writing 'duration INTERVAL' per
  frame so 12 frames × 10s = 120s raw → 12× compression to 10s.

- Removed 2>/dev/null from audio ffmpeg call in test-sunrise.sh so
  capture failures are visible for diagnosis.

https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
2026-04-21 16:35:25 +00:00

166 lines
6.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# test-sunrise.sh — smoke-test the sunrise pipeline using recent 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)
# 2. speed-adjust to SUNRISE_TARGET_SECS
# 3. grab 10s of live RTSP audio centred on the mid-point frame
# 4. mix audio + burn vertical time overlay
#
# Usage:
# ./test-sunrise.sh [output_path]
# ./test-sunrise.sh ~/drives/data2-main/test2.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"
ENCODE_PRESET="${ENCODE_PRESET:-slow}"
CRF_SUNRISE="${CRF_SUNRISE:-28}"
SUNRISE_TARGET_SECS="${SUNRISE_TARGET_SECS:-10}"
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"
exit 1
fi
mapfile -t frames < <(find "$image_dir" -name "*.jpg" | sort | tail -n "$NUM_FRAMES")
if [ "${#frames[@]}" -lt 1 ]; then
echo "ERROR: no frames found in $image_dir"
exit 1
fi
echo "Frames: ${#frames[@]} most-recent from $image_dir"
# ── Mid-point frame → overlay time ───────────────────────────────────────────
mid_idx=$(( ${#frames[@]} / 2 ))
mid_frame="${frames[$mid_idx]}"
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}"
# ── Font detection ────────────────────────────────────────────────────────────
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
# ── 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.
# 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}"
done >> "$TMP_LIST"
printf "file '%s'\n" "${frames[-1]}" >> "$TMP_LIST"
# ── Step 1: raw video from frames ─────────────────────────────────────────────
echo ""
echo "Step 1/4: encoding raw video from ${#frames[@]} frames..."
ffmpeg -loglevel warning \
-f concat -safe 0 -i "$TMP_LIST" \
-c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" -vsync 2 -an \
-y "$TMP_RAW"
raw_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$TMP_RAW")
speed=$(echo "scale=3; $raw_dur / $SUNRISE_TARGET_SECS" | bc)
echo " Raw: ${raw_dur}s speed: ${speed}x"
# ── Step 2: speed-adjust to SUNRISE_TARGET_SECS ───────────────────────────────
echo "Step 2/4: speed-adjust to ${SUNRISE_TARGET_SECS}s..."
ffmpeg -loglevel warning \
-i "$TMP_RAW" \
-vf "setpts=PTS/$speed" \
-c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" -an \
-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.
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
else
echo " Audio: CAM_RTSP_${CAM} not set — skipping"
fi
# ── Step 4: mix audio + burn overlay ─────────────────────────────────────────
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}'"
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"
DT="${DT}:shadowcolor=black@0.55:shadowx=1:shadowy=1"
if $HAS_AUDIO; then
ffmpeg -loglevel warning \
-i "$TMP_SPED" -i "$TMP_AUDIO" \
-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 -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" \
-c:a aac -b:a 128k \
-y "$OUTPUT"
else
ffmpeg -loglevel warning \
-i "$TMP_SPED" \
-vf "$DT" \
-c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" -an \
-y "$OUTPUT"
fi
actual=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$OUTPUT")
echo ""
echo "Done: $OUTPUT (${actual}s, overlay: $MID_TIME)"