# 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` | `suntime pytz` for sunrise calculation, `requests` for Mattermost upload — `pip3 install suntime pytz requests` | | `fonts-dejavu` | DejaVu fonts for text overlays — `sudo apt install fonts-dejavu` | | `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: ```bash pip3 install suntime pytz requests ``` --- ## Quick start ### 1. Download ```bash bash <(curl -fsSL https://raw.githubusercontent.com/outis1one/sky-cam/main/bootstrap.sh) cd sky-cam ``` Or with a custom install directory: ```bash curl -fsSL https://raw.githubusercontent.com/outis1one/sky-cam/main/bootstrap.sh | bash -s -- /opt/sky-cam cd /opt/sky-cam ``` ### 2. Configure ```bash $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///.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_` | 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 ```bash ./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 ```bash systemctl --user list-timers 'sky-cam-*' journalctl --user -u sky-cam-sunrise.service -f ``` --- ## How it works ``` Camera RTSP stream │ ├─ capture.sh (long-running systemd service, one per camera) │ ffmpeg pulls one frame every CAPTURE_INTERVAL seconds │ Writes: BASE_DIR//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//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 (runs at SCHEDULE_SEASONS_, 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 (runs at SCHEDULE_FULLDAY_, 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.sh` still includes the movement and you get a warning notification --- ## Notifications `notify.sh` sends alerts through any combination of: | Channel | Config key(s) | |---|---| | [ntfy](https://ntfy.sh) | `NTFY_ENABLED=true`, `NTFY_URL=https://ntfy.sh/your-topic` | | Email | `EMAIL_ENABLED=true`, `EMAIL_TO=you@example.com` | | Mattermost text post | `MM_NOTIFY_ENABLED=true`, `MM_NOTIFY_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. 1. Set `AUDIO_ENABLED=true` in `sky-cam.conf` 2. Set `CAM_RTSP_` for the sunrise camera (needed for both image capture and audio) 3. Re-run `./install.sh` to generate the `sky-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: ```bash # 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: ```bash 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:** 1. Camera mic recording (`sunrise-audio.m4a`) — real ambient sound at actual sunrise 2. Random file from `sunrise-sounds/` library — weather/season matched in a future update 3. No audio — overlay-only video (always produced regardless) --- ## Manual operations **Re-run today's sunrise** (e.g. after fixing a font issue): ```bash ./daily_sunrise_video.sh ``` **Re-run a daily seasons clip** for a specific date: ```bash ./4-seasons.sh # reprocesses yesterday ``` **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 sunrise # use a specific reference date + camera ``` **Rebuild the year-end video**: ```bash ./year-end-join.sh 2025 sunrise ``` **Check capture status**: ```bash systemctl --user status sky-cam-capture-sunrise.service journalctl --user -u sky-cam-capture-sunrise.service -f ``` **Check logs**: ```bash journalctl --user -u sky-cam-sunrise.service journalctl --user -u sky-cam-seasons-sunrise.service journalctl --user -u sky-cam-fullday-sunrise.service ```