From dbdd59677fac1f8c179b493b40415754b22d9c47 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Apr 2026 21:08:10 +0000 Subject: [PATCH] Per-camera capture interval; ambient recorder; fix 4-seasons one-frame bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-camera capture interval: CAPTURE_INTERVAL_ in sky-cam.conf overrides the global value for a specific camera. capture.sh and 4-seasons.sh both respect it. Example: south at 30s saves storage; north at 5s gives smoother motion. 4-seasons.sh one-frame bug fix: Same root cause as the sunrise scripts — concat list had no 'duration' entries, collapsing all JPEG frames to timestamp 0. Fixed by writing 'duration INTERVAL' per frame. Added fps=25 to the speed-adjust step so the VFR source resamples to a standard frame rate. ambient-record.sh (new): Continuous AAC recorder for natural sounds (birds, rain, wind). Saves AMBIENT_CHUNK_SECS chunks to AMBIENT_DIR//YYYY-MM-DD/HH-MM-SS.m4a. Reconnects automatically on stream failure. Rolling retention via AMBIENT_RETENTION_DAYS. Enabled by setting AMBIENT_ENABLED=true and listing cameras in AMBIENT_CAMS in sky-cam.conf, then re-running install.sh. https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ --- 4-seasons.sh | 16 +++++++--- ambient-record.sh | 79 +++++++++++++++++++++++++++++++++++++++++++++++ capture.sh | 3 +- install.sh | 32 +++++++++++++++++++ sky-cam.conf | 21 +++++++++++++ 5 files changed, 145 insertions(+), 6 deletions(-) create mode 100755 ambient-record.sh diff --git a/4-seasons.sh b/4-seasons.sh index cc52443..7d97ee9 100755 --- a/4-seasons.sh +++ b/4-seasons.sh @@ -72,6 +72,10 @@ if [ "$total_images" -lt 1 ]; then fi echo "Images found: $total_images" +# ── Per-camera capture interval ─────────────────────────────────────────────── +interval_var="CAPTURE_INTERVAL_${CAM_NAME}" +INTERVAL="${!interval_var:-${CAPTURE_INTERVAL:-10}}" + # ── Prepare output directory ────────────────────────────────────────────────── output_dir="$MOVIES_DIR/$CAM_NAME/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM" mkdir -p "$output_dir" @@ -82,12 +86,14 @@ temp_video="$output_dir/${yesterday}_Mvt${MVT_NUM}-temp.mp4" cleanup() { rm -f "$temp_file" "$temp_video" 2>/dev/null || true; } trap cleanup EXIT -find "$image_dir" -type f -name "*.jpg" | sort | while read -r img; do - echo "file '$img'" +mapfile -t images < <(find "$image_dir" -type f -name "*.jpg" | sort) +for img in "${images[@]}"; do + printf "file '%s'\nduration %s\n" "$img" "$INTERVAL" done > "$temp_file" +printf "file '%s'\n" "${images[-1]}" >> "$temp_file" -# ── Step 1: Build raw video at default frame rate ───────────────────────────── -echo "Step 1/2: encoding raw video..." +# ── Step 1: Build raw video ─────────────────────────────────────────────────── +echo "Step 1/2: encoding raw video from ${#images[@]} frames..." ffmpeg -loglevel warning \ -f concat -safe 0 -i "$temp_file" \ -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SEASONS_RAW" -vsync 2 -an \ @@ -103,7 +109,7 @@ echo "Step 2/2: speed factor $speed_factor..." final_file="$output_dir/${yesterday}_Mvt${MVT_NUM}-Day${DAY_OF_MVT}of${DAYS_IN_MVT}-final.mp4" if ! ffmpeg -loglevel warning \ -i "$temp_video" \ - -vf "setpts=PTS/$speed_factor" \ + -vf "setpts=PTS/$speed_factor,fps=25" \ -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SEASONS_FINAL" \ -t "$target_per_clip" -an \ -y "$final_file"; then diff --git a/ambient-record.sh b/ambient-record.sh new file mode 100755 index 0000000..7aed9d4 --- /dev/null +++ b/ambient-record.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# ambient-record.sh — continuous ambient audio recorder. +# +# Records RTSP audio in AMBIENT_CHUNK_SECS chunks, saves to: +# AMBIENT_DIR//YYYY-MM-DD/HH-MM-SS.m4a +# +# Files are named by the recording start time so the collection is easy to +# browse and pick from for white-noise / nature sound playback. +# +# Runs as a permanent systemd service (Type=simple) alongside capture.sh. +# Reconnects automatically if the camera stream drops. +# Applies rolling retention (AMBIENT_RETENTION_DAYS). +# +# Camera audio is typically 8 kHz mono (phone quality) — suitable for bird +# song, rain, wind, and ambient nature sounds; check your camera's web +# interface for a higher sample rate setting if available. + +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="${1:-}" +if [ -z "$CAM" ]; then + echo "Usage: $0 " + exit 1 +fi + +rtsp_var="CAM_RTSP_${CAM}" +RTSP_URL="${!rtsp_var:-}" +if [ -z "$RTSP_URL" ]; then + echo "ERROR: CAM_RTSP_${CAM} not set in .env" + exit 1 +fi + +CHUNK="${AMBIENT_CHUNK_SECS:-1800}" +BITRATE="${AMBIENT_BITRATE:-96k}" +RETAIN="${AMBIENT_RETENTION_DAYS:-30}" +SAVE_DIR="${AMBIENT_DIR:-$BASE_DIR/ambient}" + +echo "Ambient recorder started: cam=$CAM chunk=${CHUNK}s bitrate=$BITRATE retain=${RETAIN}d" +echo "Saving to: $SAVE_DIR/$CAM/" + +while true; do + today=$(date +%Y-%m-%d) + dir="$SAVE_DIR/$CAM/$today" + mkdir -p "$dir" + + timestamp=$(date +%H-%M-%S) + outfile="$dir/${timestamp}.m4a" + + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Recording → $outfile (${CHUNK}s)" + + ffmpeg -loglevel warning \ + -rtsp_transport tcp \ + -i "$RTSP_URL" \ + -t "$CHUNK" \ + -vn \ + -c:a aac -b:a "$BITRATE" \ + -y "$outfile" || true + + if [ ! -s "$outfile" ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$CAM] recording failed or empty — retrying in 30s" + rm -f "$outfile" + sleep 30 + continue + fi + + actual=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$outfile" 2>/dev/null || echo "?") + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Saved: $(basename "$outfile") (${actual}s)" + + # Rolling retention + if [ "${RETAIN}" -gt 0 ]; then + deleted=$(find "$SAVE_DIR/$CAM" -name "*.m4a" -mtime +"$RETAIN" -print -delete 2>/dev/null | wc -l) + [ "$deleted" -gt 0 ] && echo "Retention: deleted $deleted file(s) older than ${RETAIN}d" + fi +done diff --git a/capture.sh b/capture.sh index a4d9eda..1f6f75b 100755 --- a/capture.sh +++ b/capture.sh @@ -31,7 +31,8 @@ if [ -z "$RTSP_URL" ]; then exit 1 fi -INTERVAL="${CAPTURE_INTERVAL:-10}" +interval_var="CAPTURE_INTERVAL_${CAM}" +INTERVAL="${!interval_var:-${CAPTURE_INTERVAL:-10}}" echo "Starting capture: cam=$CAM interval=${INTERVAL}s" while true; do diff --git a/install.sh b/install.sh index a21c911..12f3e2f 100755 --- a/install.sh +++ b/install.sh @@ -187,6 +187,38 @@ if [ "${AUDIO_ENABLED:-false}" = "true" ]; then timers+=("sky-cam-audio-capture") fi +# ── Ambient audio recording services ───────────────────────────────────────── +# One long-running service per camera in AMBIENT_CAMS. +# Enabled alongside the capture services so install.sh restarts them on update. +if [ "${AMBIENT_ENABLED:-false}" = "true" ]; then + for cam in "${AMBIENT_CAMS[@]}"; do + rtsp_var="CAM_RTSP_${cam}" + if [ -n "${!rtsp_var:-}" ]; then + cat > "$UNIT_DIR/sky-cam-ambient-${cam}.service" < from sky-cam.conf. # Script receives the camera name as $1 so it knows which camera to process. diff --git a/sky-cam.conf b/sky-cam.conf index 2c32b3a..fdf6bc0 100644 --- a/sky-cam.conf +++ b/sky-cam.conf @@ -287,8 +287,16 @@ MONTAGE_MUSIC_LOOPS_Spring_2=2 # CAPTURE_STALE_SECS should always be at least 2× CAPTURE_INTERVAL. CAPTURE_INTERVAL=10 +# Per-camera overrides — uncomment and set to override the global value above. +# Useful when cameras serve different purposes (e.g. south records ambient audio +# so fewer frames is fine; north faces a busy scene and benefits from 5s). +#CAPTURE_INTERVAL_east=10 +#CAPTURE_INTERVAL_north=10 +#CAPTURE_INTERVAL_south=30 + # Seconds without a new frame before the watchdog sends a stall notification. # A recovery notification fires automatically when capture resumes. +# Must always be at least 2× the effective CAPTURE_INTERVAL for that camera. CAPTURE_STALE_SECS=30 # ── Sunrise audio capture ────────────────────────────────────────────────────── @@ -302,6 +310,19 @@ CAPTURE_AUDIO_BITRATE=96k # bitrate for the sunrise audio recording # Audio is recorded for exactly SUNRISE_TARGET_SECS, centred on actual sunrise: # floor(TARGET/2) seconds before, ceil(TARGET/2) after (odd second → post-sunrise). +# ── Ambient audio library ────────────────────────────────────────────────────── +# Continuously records natural sounds (birds, rain, wind) from AMBIENT_CAMS to +# build a reusable audio library. Runs as a permanent systemd service alongside +# capture.sh. Camera audio is 8 kHz mono (phone quality) — enough for nature +# sounds and white-noise style playback. +# +AMBIENT_ENABLED=false +AMBIENT_CAMS=(south) # cameras to record from +AMBIENT_DIR="${BASE_DIR}/ambient" # root storage directory +AMBIENT_CHUNK_SECS=1800 # seconds per file (default 30 min) +AMBIENT_BITRATE=96k # AAC bitrate; 64–96k is plenty at 8 kHz +AMBIENT_RETENTION_DAYS=30 # delete files older than this; 0 = keep forever + # ── Mattermost — daily sunrise upload ───────────────────────────────────────── # mattermost_url, access_token, channel_id go in .env (see bottom of this file).