Add pure-ffmpeg RTSP capture and camera audio for sunrise videos

Removes dependency on MotionEye or any NVR software.  Everything is now
CLI/ffmpeg configured entirely from sky-cam.conf.

capture.sh
  Long-running systemd service (one per camera) that pulls frames from the
  camera's RTSP stream at CAPTURE_INTERVAL seconds, writing HH-MM-SS.jpg
  into BASE_DIR/<cam>/YYYY-MM-DD/.  Restarts at midnight for the new date
  directory; auto-reconnects on camera disconnect.

sunrise-audio-capture.sh
  One-shot service triggered at 03:00.  Waits until (sunrise minus
  SUNRISE_PRE_MIN minus AUDIO_PRE_BUFFER_MIN), then records the RTSP audio
  stream for the full sunrise window.  Output deleted after mixing.

daily_sunrise_video.sh
  Step 3 now mixes in sunrise-audio.m4a when AUDIO_ENABLED=true and the
  file exists.  Audio is centred on actual sunrise time at natural speed
  while the video plays as the sped-up timelapse.  Uses filter_complex
  when audio is present (can't combine -vf with -filter_complex).

install.sh
  Generates sky-cam-capture-<cam>.service for each camera with CAM_RTSP_<cam>
  set, and sky-cam-audio-capture.{service,timer} when AUDIO_ENABLED=true.

sky-cam.conf
  New settings: CAM_RTSP_<cam>, CAPTURE_INTERVAL, AUDIO_ENABLED,
  AUDIO_PRE_BUFFER_MIN, CAPTURE_AUDIO_BITRATE.

https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
This commit is contained in:
Claude
2026-04-19 20:59:40 +00:00
parent 6629f91219
commit 9fe0051f70
6 changed files with 284 additions and 13 deletions
+31 -9
View File
@@ -139,19 +139,41 @@ 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 — sunrise time burned in ──────────────────────────────────
# If overlay fails, the sped video is promoted to the upload target so
# OnSuccess still fires and the upload happens (without timestamp overlay).
# ── Step 3: Overlay + optional audio ─────────────────────────────────────────
# With audio: DT must go into -filter_complex (can't mix -vf and -filter_complex)
# Without audio: plain -vf is simpler and avoids any filter_complex overhead.
# If overlay fails either way, the sped video is promoted so upload still fires.
final_video="$output_dir/$current_date-daily-sunrise.mp4"
echo "Step 3/3: overlay → $final_video"
if ffmpeg -loglevel warning \
-i "$sped_video" \
-vf "${DT}" \
-c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" -an \
-y "$final_video"; then
audio_file="$image_dir/sunrise-audio.m4a"
step3_ok=true
if [ "${AUDIO_ENABLED:-false}" = "true" ] && [ -f "$audio_file" ]; then
buffer_sec=$(( ${AUDIO_PRE_BUFFER_MIN:-2} * 60 ))
audio_offset=$(echo "scale=1; $SUNRISE_PRE_MIN * 60 + $buffer_sec - $SUNRISE_TARGET_SECS / 2" | bc)
fade_out=$(echo "scale=1; $SUNRISE_TARGET_SECS - 0.5" | bc)
echo "Step 3/3: overlay + camera audio (offset ${audio_offset}s) → $final_video"
ffmpeg -loglevel warning \
-i "$sped_video" \
-ss "$audio_offset" -t "$SUNRISE_TARGET_SECS" -i "$audio_file" \
-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" || step3_ok=false
else
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
if $step3_ok; 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"
[ -f "$audio_file" ] && rm -f "$audio_file"
"$SCRIPT_DIR/notify.sh" "Sunrise ready: $current_date" \
"$(basename "$final_video")${actual_dur}s, sunrise at ${SR_TIME}" || true
else