Merge pull request #19 from outis1one/clDemoe/seasonal-sunrise-montage-nn268

ClDemoe/seasonal sunrise montage nn268
This commit is contained in:
Outis
2026-04-20 12:36:04 -04:00
committed by GitHub
9 changed files with 70 additions and 60 deletions
+8 -3
View File
@@ -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 <camera-name>"
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
+5 -8
View File
@@ -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_<cam>` | When to run the Four Seasons daily clip — processes **yesterday's** images; runs after midnight |
| `SCHEDULE_FULLDAY_<cam>` | 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 <cam> (runs at SCHEDULE_FULLDAY_<cam>, 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 <year> <season> <mvt> <cam> (run manually after notification)
Review montage, approve to delete source JPEG folders
```
### Resilience
@@ -227,8 +225,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**:
@@ -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
```
+5 -1
View File
@@ -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 <camera-name>"
exit 1
fi
STALE_SECS="${CAPTURE_STALE_SECS:-30}"
CHECK_INTERVAL=$(( STALE_SECS / 3 ))
[ "$CHECK_INTERVAL" -lt 5 ] && CHECK_INTERVAL=5
+5 -1
View File
@@ -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 <camera-name>"
exit 1
fi
rtsp_var="CAM_RTSP_${CAM}"
RTSP_URL="${!rtsp_var:-}"
+13 -9
View File
@@ -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
@@ -41,7 +41,11 @@ while [[ $# -gt 0 ]]; do
shift
done
[ -z "$CAM" ] && CAM="$SUNRISE_CAM"
if [ -z "$CAM" ]; then
echo "Usage: $0 --cam <camera-name> [--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"
@@ -122,10 +126,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"
+13 -6
View File
@@ -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: <camera-name> [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 <camera-name> [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
@@ -200,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"
+2 -3
View File
@@ -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-<cam>.timer and sky-cam-fullday-<cam>.timer per camera
# # one sky-cam-seasons-<cam>.timer per camera, e.g.:
# systemctl --user enable --now sky-cam-seasons-east.timer
#
# Check status:
# systemctl --user list-timers 'sky-cam-*'
+14 -27
View File
@@ -2,7 +2,7 @@
# verify-mvt.sh — review a completed movement montage and manage source JPEGs.
#
# Usage:
# ./verify-mvt.sh <ASTRO_YEAR> <SEASON> <MVT_NUM> [CAM_NAME]
# ./verify-mvt.sh <ASTRO_YEAR> <SEASON> <MVT_NUM> <CAM_NAME>
#
# 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 <ASTRO_YEAR> <SEASON> <MVT_NUM> [CAM_NAME]"
if [ -z "$ASTRO_YEAR" ] || [ -z "$SEASON" ] || [ -z "$MVT_NUM" ] || [ -z "$CAM_NAME" ]; then
echo "Usage: $0 <ASTRO_YEAR> <SEASON> <MVT_NUM> <CAM_NAME>"
echo " e.g. $0 2025 Spring 1 east"
exit 1
fi
@@ -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
@@ -174,7 +161,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 +170,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
;;
+5 -2
View File
@@ -20,8 +20,11 @@ if [ -z "$ASTRO_YEAR" ]; then
echo "Usage: $0 <ASTRO_YEAR> [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 <ASTRO_YEAR> <CAM_NAME> (e.g. $0 2025 north)"
exit 1
fi
year_dir="$MOVIES_DIR/$CAM_NAME/$ASTRO_YEAR"