Removes dependency on MotionEye or any NVR software. Everything is now
CLI/ffmpeg configured entirely from sky-cam.conf.
capture.sh
Long-running systemd service (one per camera) that pulls frames from the
camera's RTSP stream at CAPTURE_INTERVAL seconds, writing HH-MM-SS.jpg
into BASE_DIR/<cam>/YYYY-MM-DD/. Restarts at midnight for the new date
directory; auto-reconnects on camera disconnect.
sunrise-audio-capture.sh
One-shot service triggered at 03:00. Waits until (sunrise minus
SUNRISE_PRE_MIN minus AUDIO_PRE_BUFFER_MIN), then records the RTSP audio
stream for the full sunrise window. Output deleted after mixing.
daily_sunrise_video.sh
Step 3 now mixes in sunrise-audio.m4a when AUDIO_ENABLED=true and the
file exists. Audio is centred on actual sunrise time at natural speed
while the video plays as the sped-up timelapse. Uses filter_complex
when audio is present (can't combine -vf with -filter_complex).
install.sh
Generates sky-cam-capture-<cam>.service for each camera with CAM_RTSP_<cam>
set, and sky-cam-audio-capture.{service,timer} when AUDIO_ENABLED=true.
sky-cam.conf
New settings: CAM_RTSP_<cam>, CAPTURE_INTERVAL, AUDIO_ENABLED,
AUDIO_PRE_BUFFER_MIN, CAPTURE_AUDIO_BITRATE.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
198 lines
7.2 KiB
Markdown
198 lines
7.2 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
pip3 install ephem 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/<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
|
|
|
|
```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 <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.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=<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_<cam>` 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.
|
|
|
|
---
|
|
|
|
## 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 <cam> # 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
|
|
```
|