diff --git a/4-seasons.sh b/4-seasons.sh index 03c825e..9ed36f6 100755 --- a/4-seasons.sh +++ b/4-seasons.sh @@ -14,7 +14,7 @@ source "$SCRIPT_DIR/sky-cam.conf" export TIMEZONE # make it visible to season_info.py subprocess # Camera name: first argument overrides conf (systemd passes it via ExecStart). -CAM_NAME="${1:-$CAM_NAME}" +CAM_NAME="${1:-$SUNRISE_CAM}" # ── Configuration ────────────────────────────────────────────────────────────── base_dir="$BASE_DIR" diff --git a/daily_sunrise_video.sh b/daily_sunrise_video.sh index a259fc1..a338ebf 100644 --- a/daily_sunrise_video.sh +++ b/daily_sunrise_video.sh @@ -48,6 +48,19 @@ start_sec=$((sunrise_sec - SUNRISE_PRE_MIN * 60)) end_sec=$((sunrise_sec + SUNRISE_POST_MIN * 60)) echo "Window: $((start_sec/3600)):$(printf '%02d' $(((start_sec%3600)/60))) → $((end_sec/3600)):$(printf '%02d' $(((end_sec%3600)/60)))" +# ── Wait for capture window to finish ──────────────────────────────────────── +# The timer fires early (default 03:00); we sleep internally so images from +# the full window are available before we start encoding. +midnight=$(date -d "today 00:00:00" +%s) +now_day_sec=$(( $(date +%s) - midnight )) +if [ "$now_day_sec" -lt "$end_sec" ]; then + wait_sec=$(( end_sec - now_day_sec + 30 )) + echo "Waiting ${wait_sec}s for capture window to finish (ends $(date -d "@$(( midnight + end_sec ))" '+%H:%M:%S'))..." + sleep "$wait_sec" +else + echo "Capture window already closed at $(date -d "@$(( midnight + end_sec ))" '+%H:%M:%S') — processing available images" +fi + # ── Collect images in window ────────────────────────────────────────────────── if [ ! -d "$image_dir" ]; then "$SCRIPT_DIR/notify.sh" "FAILED: sunrise $current_date" \ @@ -126,21 +139,20 @@ fi sped_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$sped_video") echo "Speed-adjusted: ${sped_dur}s (saved — overlay still pending)" -# ── Build overlay filter ────────────────────────────────────────────────────── -# Time displayed as stacked characters down the right side (e.g. 0/7/:/2/3), -# semi-transparent with a soft drop shadow so it reads on any background. +# ── Build overlay filter (skipped if SUNRISE_OVERLAY_ENABLED=false) ────────── SR_TIME=$(echo "$sunrise_time_local" | cut -d'-' -f1,2 | tr '-' ':') -# Build "H\nH\n:\nM\nM" for vertical stacking in drawtext -SR_VERT=$(echo "$SR_TIME" | awk 'BEGIN{FS=""}{for(i=1;i<=NF;i++){printf "%s",$i; if(i/dev/null || true; temp_audio="" [ -f "$cam_audio" ] && rm -f "$cam_audio" - echo "Overlay failed — promoting ${promote_label} video as upload target" - "$SCRIPT_DIR/notify.sh" "WARNING: sunrise overlay failed $current_date" \ - "Overlay failed — uploading ${promote_label} video (no timestamp)" || true + "$SCRIPT_DIR/notify.sh" "Sunrise ready: $current_date" \ + "$(basename "$final_video") — ${actual_dur}s, sunrise at ${SR_TIME}" || true fi diff --git a/fullday-video.sh b/fullday-video.sh index c2df80a..29e536d 100755 --- a/fullday-video.sh +++ b/fullday-video.sh @@ -11,7 +11,7 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" # Camera name: first argument overrides conf (systemd passes it via ExecStart). -CAM_NAME="${1:-$CAM_NAME}" +CAM_NAME="${1:-$SUNRISE_CAM}" # ── Configuration ────────────────────────────────────────────────────────────── base_dir="$BASE_DIR" diff --git a/sky-cam.conf b/sky-cam.conf index fe6a7b6..ab7c8e6 100644 --- a/sky-cam.conf +++ b/sky-cam.conf @@ -147,12 +147,17 @@ # ============================================================================= # ── Install location ────────────────────────────────────────────────────────── -# Full path to the directory containing this file and all the scripts. -SCRIPT_DIR=/home/motion/drives/local-2tb/sunrise-scripts +# Auto-detected from where this file lives — override only if you move the +# scripts to a different location after install. +SCRIPT_DIR="${SCRIPT_DIR:-$(dirname "$(realpath "${BASH_SOURCE[0]}")")}" # ── Storage ─────────────────────────────────────────────────────────────────── -BASE_DIR=/home/motion/drives/local-2tb -MOVIES_DIR=$BASE_DIR/movies # override if videos live on a different drive +# BASE_DIR: root folder where camera images are stored. +# Structure created automatically: BASE_DIR//YYYY-MM-DD/HH-MM-SS.jpg +# Defaults to a 'data' subfolder next to the scripts; override to point at +# an external drive or network mount (recommended for production). +BASE_DIR="${BASE_DIR:-$SCRIPT_DIR/data}" +MOVIES_DIR="${MOVIES_DIR:-$BASE_DIR/movies}" # override if videos live on a different drive MUSIC_DIR=/home/motion/drives/local-2tb/music/4 Seasons # ── Location & timezone ─────────────────────────────────────────────────────── @@ -179,14 +184,28 @@ CAMERAS=(sunrise) # e.g. CAMERAS=(sunrise north west) SUNRISE_CAM=sunrise # which camera faces east (gets the sunrise video job) # ── Schedules ───────────────────────────────────────────────────────────────── -# SCHEDULE_SUNRISE when to run daily_sunrise_video.sh (SUNRISE_CAM only) -# SCHEDULE_SEASONS_ when to run 4-seasons.sh for each camera -# SCHEDULE_FULLDAY_ when to run fullday-video.sh for each camera -# -# Times are HH:MM:SS (24-hour). Space cameras at least 30 min apart. +# All times are HH:MM:SS (24-hour local time). # install.sh reads these and generates one systemd timer per camera per job. # -SCHEDULE_SUNRISE=09:00:00 +# SCHEDULE_SUNRISE +# When to START the sunrise video job. The script wakes up, calculates +# today's sunrise time, then WAITS internally until the capture window is +# fully finished before touching any images. Set this early enough to +# cover your earliest possible sunrise minus SUNRISE_PRE_MIN — 03:00 works +# year-round for most locations. The video is ready a few minutes after the +# capture window closes, not at this start time. +# +# SCHEDULE_SEASONS_ (one per camera in CAMERAS) +# When to run the Four Seasons daily clip job. Processes YESTERDAY'S images +# (not today's) — always runs after midnight. On the last day of a season +# movement this automatically triggers the movement montage build. +# Space multiple cameras at least 30 min apart to avoid disk contention. +# +# SCHEDULE_FULLDAY_ (one per camera in CAMERAS) +# When to run the full-day timelapse job. Also processes YESTERDAY'S images. +# Space after SCHEDULE_SEASONS to avoid overlap. +# +SCHEDULE_SUNRISE=03:00:00 SCHEDULE_SEASONS_sunrise=01:00:00 SCHEDULE_FULLDAY_sunrise=02:00:00 @@ -199,12 +218,18 @@ SCHEDULE_FULLDAY_sunrise=02:00:00 #SCHEDULE_FULLDAY_west=03:00:00 # ── Sunrise video tuning ────────────────────────────────────────────────────── -# How many minutes before/after sunrise to include in the daily clip. -SUNRISE_PRE_MIN=70 -SUNRISE_POST_MIN=10 -# Target duration (seconds) for the speed-adjusted sunrise video. +# Capture window around sunrise: +# Images from (sunrise - SUNRISE_PRE_MIN) to (sunrise + SUNRISE_POST_MIN) +# are included in the clip. Larger windows give more context; they are all +# speed-adjusted to fit SUNRISE_TARGET_SECS regardless of window size. +SUNRISE_PRE_MIN=70 # minutes before sunrise to start capturing +SUNRISE_POST_MIN=10 # minutes after sunrise to stop capturing +# Target length of the final sunrise video (seconds). SUNRISE_TARGET_SECS=10 -# Opacity of the sunrise-time overlay (0.0 = invisible, 1.0 = fully opaque). +# Burn the local sunrise time vertically on the right side of the video. +# Set false to skip the overlay entirely (cleaner look, faster encoding). +SUNRISE_OVERLAY_ENABLED=true +# Opacity of the sunrise-time text (0.0 = invisible, 1.0 = fully opaque). SUNRISE_OVERLAY_OPACITY=0.45 # ── Full-day timelapse tuning ───────────────────────────────────────────────── @@ -258,8 +283,9 @@ CAPTURE_INTERVAL=10 # The audio file is deleted automatically after it is mixed into the video. # AUDIO_ENABLED=false # set true if your camera has a working mic -AUDIO_PRE_BUFFER_MIN=2 # extra minutes to start before the capture window 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). # ── Mattermost — daily sunrise upload ───────────────────────────────────────── # mattermost_url, access_token, channel_id go in .env (see bottom of this file). diff --git a/sunrise-audio-capture.sh b/sunrise-audio-capture.sh index a88e0dc..7cc3068 100755 --- a/sunrise-audio-capture.sh +++ b/sunrise-audio-capture.sh @@ -1,13 +1,15 @@ #!/bin/bash -# sunrise-audio-capture.sh — record the camera's RTSP audio stream during the -# sunrise window so daily_sunrise_video.sh can mix in natural ambient sound -# (birds, rain, wind) at real speed while the video plays as a timelapse. +# sunrise-audio-capture.sh — record exactly SUNRISE_TARGET_SECS of audio +# centred on the actual sunrise moment, so daily_sunrise_video.sh can mix in +# real ambient sound (birds, rain, wind) at natural speed. # -# Triggered daily at 03:00 by sky-cam-audio-capture.timer. -# Waits internally until (sunrise − SUNRISE_PRE_MIN − AUDIO_PRE_BUFFER_MIN), -# then records for the full window. Exits immediately if AUDIO_ENABLED≠true. +# Triggered at 03:00 by sky-cam-audio-capture.timer; waits internally until +# the right moment before recording. Exits immediately if AUDIO_ENABLED≠true. # -# Output: BASE_DIR/SUNRISE_CAM/YYYY-MM-DD/sunrise-audio.m4a +# Recording window: floor(TARGET/2) seconds before sunrise, +# ceil(TARGET/2) seconds after (odd second goes to post). +# +# Output: BASE_DIR//YYYY-MM-DD/sunrise-audio.m4a # Deleted automatically by daily_sunrise_video.sh after mixing. set -euo pipefail @@ -40,13 +42,16 @@ fi sunrise_time_local=$(TZ="$TIMEZONE" date -d "$sunrise_time" +"%H:%M:%S") echo "Sunrise (local): $sunrise_time_local" -# ── Calculate wait and record duration ─────────────────────────────────────── +# ── Calculate recording window centred on sunrise ───────────────────────────── time_to_seconds() { IFS=':' read -r h m s <<< "$1"; echo $((10#$h*3600 + 10#$m*60 + 10#$s)); } sunrise_sec=$(time_to_seconds "$sunrise_time_local") -buffer_sec=$(( ${AUDIO_PRE_BUFFER_MIN:-2} * 60 )) -record_start_sec=$(( sunrise_sec - SUNRISE_PRE_MIN * 60 - buffer_sec )) -record_dur_sec=$(( (SUNRISE_PRE_MIN + SUNRISE_POST_MIN) * 60 + buffer_sec )) +half_pre=$(( SUNRISE_TARGET_SECS / 2 )) # floor — pre-sunrise portion +half_post=$(( SUNRISE_TARGET_SECS - half_pre )) # ceil — post-sunrise gets odd second +record_start_sec=$(( sunrise_sec - half_pre )) +record_dur_sec=$SUNRISE_TARGET_SECS + +echo "Audio window: ${half_pre}s before → ${half_post}s after sunrise (${record_dur_sec}s total)" midnight=$(date -d "today 00:00:00" +%s) now_day_sec=$(( $(date +%s) - midnight ))