From 8c752fc165a9e5e245565ec231d8f4c48e0a4ece Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 16:28:15 +0000 Subject: [PATCH 1/2] Require camera name explicitly across all seasons scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No script defaults to SUNRISE_CAM (east) for the seasons pipeline. Every direction is treated equally — camera name is a required arg, not an optional override. - 4-seasons.sh: $1 required, exits with usage if missing - montage-mvt.sh: arg order changed to [date]; $1 required - year-end-join.sh: $2 (cam) required - verify-mvt.sh: $4 (cam) required - migrate-seasons.sh: example commands updated to new arg order - README.md: montage-mvt.sh examples updated https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ --- 4-seasons.sh | 11 ++++++++--- README.md | 4 ++-- migrate-seasons.sh | 16 ++++++++-------- montage-mvt.sh | 15 +++++++++++---- verify-mvt.sh | 12 ++++++------ year-end-join.sh | 7 +++++-- 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/4-seasons.sh b/4-seasons.sh index ac07487..a162db6 100755 --- a/4-seasons.sh +++ b/4-seasons.sh @@ -13,8 +13,13 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")" 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:-$SUNRISE_CAM}" +# Camera name: required first argument (systemd passes it via ExecStart). +CAM_NAME="${1:-}" +if [ -z "$CAM_NAME" ]; then + echo "Usage: $0 " + echo " e.g. $0 east or $0 north" + exit 1 +fi # ── Configuration ────────────────────────────────────────────────────────────── base_dir="$BASE_DIR" @@ -125,5 +130,5 @@ if [ "$IS_LAST_DAY" = "true" ]; then echo "Last day of $SEASON Mvt $MVT_NUM — triggering montage compilation..." # Pass $yesterday so montage-mvt.sh looks up the correct movement # (running the next morning, "today" would already be the next movement). - "$SCRIPT_DIR/montage-mvt.sh" "$yesterday" "$CAM_NAME" + "$SCRIPT_DIR/montage-mvt.sh" "$CAM_NAME" "$yesterday" fi diff --git a/README.md b/README.md index 165397b..2ed5e63 100644 --- a/README.md +++ b/README.md @@ -227,8 +227,8 @@ Attribution data for every file is written to `sunrise-sounds/manifest.json`. **Rebuild a movement montage** (e.g. to retry audio after a failure): ```bash -./montage-mvt.sh # uses today's movement -./montage-mvt.sh 2025-06-15 east # specific date + camera +./montage-mvt.sh east # uses today's date +./montage-mvt.sh east 2025-06-15 # specific date ``` **Rebuild the year-end video**: diff --git a/migrate-seasons.sh b/migrate-seasons.sh index 9fef5b2..3161a03 100755 --- a/migrate-seasons.sh +++ b/migrate-seasons.sh @@ -17,10 +17,10 @@ # (default: ~/drives/local-2tb/movies/sunrise) # # After running, trigger montages for completed movements: -# ./montage-mvt.sh 2026-04-19 east # Spring Mvt1 (~30/31 days) -# ./montage-mvt.sh 2026-01-18 east # Winter Mvt1 (~18/29 days) -# ./montage-mvt.sh 2026-02-16 east # Winter Mvt2 (~24/29 days) -# ./montage-mvt.sh 2026-03-19 east # Winter Mvt3 (~20/31 days) +# ./montage-mvt.sh east 2026-04-19 # Spring Mvt1 +# ./montage-mvt.sh east 2026-01-18 # Winter Mvt1 +# ./montage-mvt.sh east 2026-02-16 # Winter Mvt2 +# ./montage-mvt.sh east 2026-03-19 # Winter Mvt3 set -euo pipefail @@ -122,10 +122,10 @@ $DRY_RUN && echo "Re-run without --dry-run to move the files." && echo "" echo "Next steps:" echo " 1. Build montages for completed movements:" -echo " ./montage-mvt.sh 2026-04-19 $CAM # Spring Mvt1 (today is last day)" -echo " ./montage-mvt.sh 2026-01-18 $CAM # Winter Mvt1" -echo " ./montage-mvt.sh 2026-02-16 $CAM # Winter Mvt2" -echo " ./montage-mvt.sh 2026-03-19 $CAM # Winter Mvt3" +echo " ./montage-mvt.sh $CAM 2026-04-19 # Spring Mvt1" +echo " ./montage-mvt.sh $CAM 2026-01-18 # Winter Mvt1" +echo " ./montage-mvt.sh $CAM 2026-02-16 # Winter Mvt2" +echo " ./montage-mvt.sh $CAM 2026-03-19 # Winter Mvt3" echo " 2. Confirm each montage plays correctly with music." echo " 3. Delete temp files from old archive:" echo " find \"$OLD_ROOT\" -name '*-temp.mp4' -delete" diff --git a/montage-mvt.sh b/montage-mvt.sh index 7601c91..d5bf98f 100755 --- a/montage-mvt.sh +++ b/montage-mvt.sh @@ -22,9 +22,17 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" export TIMEZONE # make it visible to season_info.py subprocess -# Args: [date] [camera-name] -# Camera name: second argument overrides conf (passed through from 4-seasons.sh). -CAM_NAME="${2:-$SUNRISE_CAM}" +# Args: [date] +# Camera name: required first argument. +# Date: optional second argument (defaults to today; pass yesterday's date when +# running the morning after the last day of a movement). +CAM_NAME="${1:-}" +DATE_ARG="${2:-}" +if [ -z "$CAM_NAME" ]; then + echo "Usage: $0 [YYYY-MM-DD]" + echo " e.g. $0 east 2026-04-19" + exit 1 +fi # ── Configuration ────────────────────────────────────────────────────────────── base_dir="$BASE_DIR" @@ -38,7 +46,6 @@ ATTR_DUR="$MONTAGE_ATTR_DUR" ATTR_FADE="$MONTAGE_ATTR_FADE" # ── Season / movement info ──────────────────────────────────────────────────── -DATE_ARG="${1:-}" _season_info="$(python3 "$SCRIPT_DIR/season_info.py" ${DATE_ARG:+"$DATE_ARG"})" || { echo "Error: season_info.py failed — check Python dependencies (suntime pytz)" exit 1 diff --git a/verify-mvt.sh b/verify-mvt.sh index da59351..eafbe7b 100755 --- a/verify-mvt.sh +++ b/verify-mvt.sh @@ -2,7 +2,7 @@ # verify-mvt.sh — review a completed movement montage and manage source JPEGs. # # Usage: -# ./verify-mvt.sh [CAM_NAME] +# ./verify-mvt.sh # # Shows technical checks on the montage, lets you play it, then: # (p) play — open in ffplay / mpv / vlc (whichever is available) @@ -21,10 +21,10 @@ export TIMEZONE ASTRO_YEAR="${1:-}" SEASON="${2:-}" MVT_NUM="${3:-}" -CAM_NAME="${4:-$SUNRISE_CAM}" +CAM_NAME="${4:-}" -if [ -z "$ASTRO_YEAR" ] || [ -z "$SEASON" ] || [ -z "$MVT_NUM" ]; then - echo "Usage: $0 [CAM_NAME]" +if [ -z "$ASTRO_YEAR" ] || [ -z "$SEASON" ] || [ -z "$MVT_NUM" ] || [ -z "$CAM_NAME" ]; then + echo "Usage: $0 " echo " e.g. $0 2025 Spring 1 east" exit 1 fi @@ -174,7 +174,7 @@ while true; do r) echo " Re-running music+overlay step..." - "$SCRIPT_DIR/montage-mvt.sh" "$mvt_start_date" "$CAM_NAME" + "$SCRIPT_DIR/montage-mvt.sh" "$CAM_NAME" "$mvt_start_date" break ;; @@ -183,7 +183,7 @@ while true; do # Remove sped file so montage-mvt.sh starts from scratch find "$MOVIES_DIR/$CAM_NAME/$ASTRO_YEAR/$SEASON/Mvt${MVT_NUM}" \ -maxdepth 1 -name "*-Sped.mp4" -delete 2>/dev/null || true - "$SCRIPT_DIR/montage-mvt.sh" "$mvt_start_date" "$CAM_NAME" + "$SCRIPT_DIR/montage-mvt.sh" "$CAM_NAME" "$mvt_start_date" break ;; diff --git a/year-end-join.sh b/year-end-join.sh index d8430f5..3ae21f8 100755 --- a/year-end-join.sh +++ b/year-end-join.sh @@ -20,8 +20,11 @@ if [ -z "$ASTRO_YEAR" ]; then echo "Usage: $0 [CAM_NAME] (e.g. $0 2025 north)" exit 1 fi -# Camera name: second argument overrides conf (passed through from montage-mvt.sh). -CAM_NAME="${2:-$SUNRISE_CAM}" +CAM_NAME="${2:-}" +if [ -z "$CAM_NAME" ]; then + echo "Usage: $0 (e.g. $0 2025 north)" + exit 1 +fi year_dir="$MOVIES_DIR/$CAM_NAME/$ASTRO_YEAR" From c4fb086f1d9ace2b04fe87d3618c1b143feb90f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 16:34:05 +0000 Subject: [PATCH 2/2] Fix bugs found in post-refactor audit - verify-mvt.sh: remove dead first _info= line (used MVT_START before defined); fix JPEG size loop to accumulate bytes correctly and display human-readable total; show size in delete confirmation - montage-mvt.sh: grep -c uses || echo 0 instead of || true to be unambiguous about the no-match value under set -e - capture.sh, capture-watchdog.sh: require camera name arg, remove silent SUNRISE_CAM fallback - migrate-seasons.sh: require --cam arg, error if missing - README.md, sky-cam.conf: remove stale fullday-video.sh references; README pipeline diagram updated to show verify-mvt.sh https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ --- README.md | 9 +++------ capture-watchdog.sh | 6 +++++- capture.sh | 6 +++++- migrate-seasons.sh | 6 +++++- montage-mvt.sh | 4 ++-- sky-cam.conf | 5 ++--- verify-mvt.sh | 29 ++++++++--------------------- 7 files changed, 30 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 2ed5e63..7bce176 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,6 @@ nano sky-cam.conf | `CAPTURE_INTERVAL` | Seconds between captured frames (default: 10) | | `SCHEDULE_SUNRISE` | When to start the sunrise job — default `03:00`, script waits internally until the capture window closes | | `SCHEDULE_SEASONS_` | When to run the Four Seasons daily clip — processes **yesterday's** images; runs after midnight | -| `SCHEDULE_FULLDAY_` | When to run the full-day timelapse — also processes yesterday; schedule after SEASONS | ### 4. Set up credentials @@ -137,10 +136,9 @@ Camera JPEGs + audio │ Step 2: speed-adjust to exactly match music → saved permanently │ Step 3: mix music + fades + attribution overlay → Montage.mp4 │ Last movement of Autumn → triggers year-end-join.sh - │ - └─ fullday-video.sh (runs at SCHEDULE_FULLDAY_, processes yesterday) - Encode all of yesterday's JPEGs at FULLDAY_FPS - Delete videos older than RETENTION_DAYS + │ On completion → notify with verify-mvt.sh command + └─ verify-mvt.sh (run manually after notification) + Review montage, approve to delete source JPEG folders ``` ### Resilience @@ -251,5 +249,4 @@ journalctl --user -u sky-cam-watchdog-east.service -f ```bash journalctl --user -u sky-cam-sunrise.service journalctl --user -u sky-cam-seasons-east.service -journalctl --user -u sky-cam-fullday-east.service ``` diff --git a/capture-watchdog.sh b/capture-watchdog.sh index 19c459d..be740b2 100755 --- a/capture-watchdog.sh +++ b/capture-watchdog.sh @@ -12,7 +12,11 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" export TZ="$TIMEZONE" -CAM="${1:-$SUNRISE_CAM}" +CAM="${1:-}" +if [ -z "$CAM" ]; then + echo "Usage: $0 " + exit 1 +fi STALE_SECS="${CAPTURE_STALE_SECS:-30}" CHECK_INTERVAL=$(( STALE_SECS / 3 )) [ "$CHECK_INTERVAL" -lt 5 ] && CHECK_INTERVAL=5 diff --git a/capture.sh b/capture.sh index a85e007..d5d026d 100755 --- a/capture.sh +++ b/capture.sh @@ -17,7 +17,11 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" export TZ="$TIMEZONE" -CAM="${1:-$SUNRISE_CAM}" +CAM="${1:-}" +if [ -z "$CAM" ]; then + echo "Usage: $0 " + exit 1 +fi rtsp_var="CAM_RTSP_${CAM}" RTSP_URL="${!rtsp_var:-}" diff --git a/migrate-seasons.sh b/migrate-seasons.sh index 3161a03..7e562bf 100755 --- a/migrate-seasons.sh +++ b/migrate-seasons.sh @@ -41,7 +41,11 @@ while [[ $# -gt 0 ]]; do shift done -[ -z "$CAM" ] && CAM="$SUNRISE_CAM" +if [ -z "$CAM" ]; then + echo "Usage: $0 --cam [--dry-run] [OLD_ROOT]" + echo " e.g. $0 --cam east ~/drives/local-2tb/movies/sunrise" + exit 1 +fi NEW_ROOT="$MOVIES_DIR/$CAM/2025" echo "Old archive : $OLD_ROOT" diff --git a/montage-mvt.sh b/montage-mvt.sh index d5bf98f..c181583 100755 --- a/montage-mvt.sh +++ b/montage-mvt.sh @@ -207,8 +207,8 @@ if ffmpeg -loglevel warning \ actual_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$output_file") dur_delta=$(echo "scale=1; $actual_dur - $music_duration" | bc | sed 's/^-//') stream_types=$(ffprobe -v error -show_entries stream=codec_type -of csv=p=0 "$output_file" 2>/dev/null) - has_video=$(echo "$stream_types" | grep -c "video" || true) - has_audio=$(echo "$stream_types" | grep -c "audio" || true) + has_video=$(echo "$stream_types" | grep -c "video" || echo 0) + has_audio=$(echo "$stream_types" | grep -c "audio" || echo 0) checks="duration=${actual_dur}s (drift ${dur_delta}s)" [ "$has_video" -gt 0 ] && checks="$checks video=ok" || checks="$checks video=MISSING" [ "$has_audio" -gt 0 ] && checks="$checks audio=ok" || checks="$checks audio=MISSING" diff --git a/sky-cam.conf b/sky-cam.conf index 65c29d7..fa512a6 100644 --- a/sky-cam.conf +++ b/sky-cam.conf @@ -21,9 +21,8 @@ # # systemctl --user daemon-reload # systemctl --user enable --now sky-cam-sunrise.timer -# systemctl --user enable --now sky-cam-seasons-sunrise.timer -# systemctl --user enable --now sky-cam-fullday-sunrise.timer -# # one sky-cam-seasons-.timer and sky-cam-fullday-.timer per camera +# # one sky-cam-seasons-.timer per camera, e.g.: +# systemctl --user enable --now sky-cam-seasons-east.timer # # Check status: # systemctl --user list-timers 'sky-cam-*' diff --git a/verify-mvt.sh b/verify-mvt.sh index eafbe7b..6fa0e76 100755 --- a/verify-mvt.sh +++ b/verify-mvt.sh @@ -40,13 +40,7 @@ if [ -z "$montage_file" ] || [ ! -f "$montage_file" ]; then fi # ── Get movement date range from season_info ────────────────────────────────── -_info="$(python3 "$SCRIPT_DIR/season_info.py" "$MVT_START" 2>/dev/null)" 2>/dev/null || true - -# Use the sped file's date prefix to find a representative date in the movement -sped_file=$(find "$MOVIES_DIR/$CAM_NAME/$ASTRO_YEAR/$SEASON/Mvt${MVT_NUM}" \ - -maxdepth 1 -name "*-Sped.mp4" 2>/dev/null | head -n 1 || true) - -# Get MVT_START from the montage filename (format: YYYY-MM-DD_Season_MvtN-Montage.mp4) +# Extract date from montage filename (format: YYYY-MM-DD_Season_MvtN-Montage.mp4) mvt_start_date=$(basename "$montage_file" | cut -d_ -f1) _info="$(python3 "$SCRIPT_DIR/season_info.py" "$mvt_start_date")" eval "$_info" @@ -89,30 +83,23 @@ printf " File size : %s\n" "$file_size" printf " Daily clips : %d / %d days\n" "$final_count" "$DAYS_IN_MVT" printf " Music : %s\n" "$(basename "${music_file:-NOT FOUND}")" -# Count JPEG folders available for deletion +# Count JPEG folders and sum their sizes jpeg_count=0 -jpeg_size=0 +jpeg_bytes=0 d="$MVT_START" while [[ "$d" < "$MVT_END" || "$d" == "$MVT_END" ]]; do dir="$BASE_DIR/$CAM_NAME/$d" if [ -d "$dir" ]; then jpeg_count=$(( jpeg_count + 1 )) + jpeg_bytes=$(( jpeg_bytes + $(du -sb "$dir" 2>/dev/null | cut -f1 || echo 0) )) fi d=$(date -d "$d + 1 day" +%Y-%m-%d) done if [ "$jpeg_count" -gt 0 ]; then - jpeg_size=$(du -sh "$BASE_DIR/$CAM_NAME" --exclude="*" 2>/dev/null || true) - # Sum only the movement date folders - total_jpeg_bytes=0 - d="$MVT_START" - while [[ "$d" < "$MVT_END" || "$d" == "$MVT_END" ]]; do - dir="$BASE_DIR/$CAM_NAME/$d" - [ -d "$dir" ] && total_jpeg_bytes=$(du -sb "$dir" 2>/dev/null | cut -f1 || echo 0) || true - d=$(date -d "$d + 1 day" +%Y-%m-%d) - done - printf " JPEG folders : %d date folders (%s → %s)\n" \ - "$jpeg_count" "$MVT_START" "$MVT_END" + jpeg_size=$(numfmt --to=iec-i --suffix=B "$jpeg_bytes" 2>/dev/null || echo "${jpeg_bytes}B") + printf " JPEG folders : %d date folders, %s (%s → %s)\n" \ + "$jpeg_count" "$jpeg_size" "$MVT_START" "$MVT_END" else printf " JPEG folders : none found (already deleted)\n" fi @@ -148,7 +135,7 @@ while true; do continue fi echo "" - echo " Will delete $jpeg_count JPEG date folders ($MVT_START → $MVT_END) for camera [$CAM_NAME]." + echo " Will delete $jpeg_count JPEG date folders ($MVT_START → $MVT_END) for camera [$CAM_NAME] — ${jpeg_size}." echo " The montage video and daily clips are kept." read -rp " Confirm? [yes/no]: " confirm if [ "$confirm" = "yes" ]; then