Claude/seasonal sunrise montage nn268
sky-cam
Automated sky / timelapse camera scripts that produce:
- Daily sunrise clip — a 10-second speed-adjusted video of the sunrise window, uploaded to Mattermost each morning
- Four Seasons timelapse — daily clips sized to each Vivaldi movement's music duration, assembled automatically into per-movement montages (with music + attribution overlay) and a full-year video
- Full-day timelapse — a fixed-fps timelapse of every image captured that day, kept for a configurable retention window
Prerequisites
| Dependency | Notes |
|---|---|
ffmpeg + ffprobe |
Encoding, capture, audio recording, duration probing |
python3 |
ephem package for sunrise calculation, requests for Mattermost upload |
bc |
Shell arithmetic (floating-point speed factors) |
fontconfig (fc-match) |
Font detection for overlays — optional, falls back to hardcoded paths |
| IP camera with RTSP stream | capture.sh pulls frames directly — no NVR software needed |
| Vivaldi Four Seasons audio | 12 MP3 files named so that *Spring*Mvt*1*, *Summer*Mvt*2*, etc. match with find -iname |
Install Python dependencies:
pip3 install ephem requests
Quick start
1. Download
bash <(curl -fsSL https://raw.githubusercontent.com/outis1one/sky-cam/main/bootstrap.sh)
cd sky-cam
Or with a custom install directory:
curl -fsSL https://raw.githubusercontent.com/outis1one/sky-cam/main/bootstrap.sh | bash -s -- /opt/sky-cam
cd /opt/sky-cam
2. Configure
$EDITOR sky-cam.conf
Minimum settings to fill in (everything else has sensible defaults):
| Setting | What it is |
|---|---|
SCRIPT_DIR |
Full path to this directory |
BASE_DIR |
Root where camera images live (BASE_DIR/<cam>/<date>/<HH-MM-SS>.jpg) |
MOVIES_DIR |
Where finished videos are written (default: BASE_DIR/movies) |
MUSIC_DIR |
Directory containing the 12 Vivaldi Four Seasons audio files |
CAMERAS |
Space-separated list of camera names, e.g. (sunrise north) |
SUNRISE_CAM |
Which camera faces east and gets the sunrise job |
LATITUDE / LONGITUDE / TIMEZONE |
Your location for sunrise calculation |
CAM_RTSP_<cam> |
RTSP stream URL for each camera, e.g. rtsp://admin:pass@192.168.1.100:554/stream1 |
CAPTURE_INTERVAL |
Seconds between captured frames (default: 10) |
mattermost_url / access_token / channel_id |
Mattermost upload credentials |
3. Install systemd timers
./install.sh # user-level timers (~/.config/systemd/user), no root needed
# or
./install.sh --system # system-wide (/etc/systemd/system), requires sudo
Re-run install.sh any time sky-cam.conf changes.
4. Verify
systemctl --user list-timers 'sky-cam-*'
journalctl --user -u sky-cam-sunrise.service -f
How it works
Camera RTSP stream
│
├─ capture.sh <cam> (long-running systemd service, one per camera)
│ ffmpeg pulls one frame every CAPTURE_INTERVAL seconds
│ Writes: BASE_DIR/<cam>/YYYY-MM-DD/HH-MM-SS.jpg
│ Restarts at midnight for the new date directory; auto-reconnects
│
├─ sunrise-audio-capture.sh (runs at 03:00, waits for sunrise window)
│ ffmpeg records audio-only from RTSP during sunrise window
│ Writes: BASE_DIR/<cam>/YYYY-MM-DD/sunrise-audio.m4a
│ Deleted automatically after being mixed into the sunrise video
│
Camera JPEGs + audio
│
├─ daily_sunrise_video.sh (runs at SCHEDULE_SUNRISE)
│ Step 1: encode raw video from sunrise-window JPEGs
│ Step 2: speed-adjust to SUNRISE_TARGET_SECS → saved permanently
│ Step 3: burn sunrise time overlay + mix camera audio (if available)
│ OnSuccess → sunrise2mm.py uploads to Mattermost
│
├─ 4-seasons.sh <cam> (runs at SCHEDULE_SEASONS_<cam>, processes yesterday)
│ Step 1: encode all of yesterday's JPEGs into raw video
│ Step 2: speed-adjust to music_duration / days_in_movement
│ Last day of movement → triggers montage-mvt.sh
│ Step 1: concatenate all daily clips
│ 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
Resilience
Each pipeline saves an intermediate file before the step most likely to fail, so a partial failure leaves a recoverable artifact:
- Sunrise: if the overlay (step 3) fails, the speed-only video is promoted to the upload target — the upload still happens and you get a notification of the overlay failure
- Montage: if music + overlay (step 3) fails, the speed-adjusted silent video is promoted to
*-Montage.mp4—year-end-join.shstill includes the movement and you get a warning notification
Notifications
notify.sh sends alerts through any combination of:
| Channel | Config key(s) |
|---|---|
| ntfy | NTFY_ENABLED=true, NTFY_URL=https://ntfy.sh/your-topic |
EMAIL_ENABLED=true, EMAIL_TO=you@example.com |
|
| Mattermost text post | MM_NOTIFY_ENABLED=true, MM_NOTIFY_CHANNEL_ID=<channel-id> |
You receive notifications for:
- Sunrise: video ready, upload success/failure, overlay failure
- Each daily seasons clip saved
- Montage complete (or degraded if audio/overlay failed)
- Year-end Four Seasons video complete
- Any step failure, with the surviving file path named
Camera audio (optional)
If your camera has a microphone, sky-cam can mix a natural-speed 10-second audio clip (birds, rain, wind — whatever was actually happening at sunrise) into the daily sunrise video.
- Set
AUDIO_ENABLED=trueinsky-cam.conf - Set
CAM_RTSP_<cam>for the sunrise camera (needed for both image capture and audio) - Re-run
./install.shto generate thesky-cam-audio-capture.timer
The audio is recorded during the same window as the images, stored alongside them, and deleted automatically after being mixed into the final video. No audio library or AI required — it's the real sound from your camera.
Audio fallback library (optional)
If the camera has no mic, or audio capture fails, daily_sunrise_video.sh picks a random ambient sound from sunrise-sounds/ instead. The library is organised into 11 weather/season folders (clear-spring, rain, thunder, windy, etc.) — populate it once with:
# Get a free API key at https://freesound.org/apiv2/apply/
python3 download-sunrise-sounds.py --api-key YOUR_KEY
Default: ~275 CC-licensed 128 kbps MP3 previews (~25 per category). Re-run any time to top up:
python3 download-sunrise-sounds.py --api-key YOUR_KEY --per-category 40
Attribution data for every file is written to sunrise-sounds/manifest.json.
Priority order for sunrise audio:
- Camera mic recording (
sunrise-audio.m4a) — real ambient sound at actual sunrise - Random file from
sunrise-sounds/library — weather/season matched in a future update - No audio — overlay-only video (always produced regardless)
Manual operations
Re-run today's sunrise (e.g. after fixing a font issue):
./daily_sunrise_video.sh
Re-run a daily seasons clip for a specific date:
./4-seasons.sh <cam> # reprocesses yesterday
Rebuild a movement montage (e.g. to retry audio after a failure):
./montage-mvt.sh # uses today's movement
./montage-mvt.sh 2025-06-15 sunrise # use a specific reference date + camera
Rebuild the year-end video:
./year-end-join.sh 2025 sunrise
Check capture status:
systemctl --user status sky-cam-capture-sunrise.service
journalctl --user -u sky-cam-capture-sunrise.service -f
Check logs:
journalctl --user -u sky-cam-sunrise.service
journalctl --user -u sky-cam-seasons-sunrise.service
journalctl --user -u sky-cam-fullday-sunrise.service