Files
sky-cam/README.md
T
Claude 64cec6b769 Rename default camera from 'sunrise' to 'east' for compass consistency
Cameras are now named by direction (east, north, south, west) throughout
config defaults, examples, and documentation. The config key SUNRISE_CAM
is unchanged — it still means 'which camera faces east and gets the sunrise
job'. Only the camera instance name value changes from 'sunrise' to 'east'.

sky-cam.conf: CAMERAS=(east), SUNRISE_CAM=east, SCHEDULE_*_east,
example comments updated to show all four compass points.
README.md: all command examples updated (4-seasons.sh east, journalctl
sky-cam-capture-east, etc.) — phenomenon references ('sunrise window',
'sunrise time') left unchanged.
bootstrap.sh: example updated to show compass directions.

https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
2026-04-20 00:53:58 +00:00

251 lines
9.3 KiB
Markdown

# sky-cam
Automated sky / timelapse camera scripts that produce:
- **Daily sunrise clip** — a 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
---
## Quick start
### 1. Install dependencies
```bash
sudo apt install ffmpeg bc fonts-dejavu curl python3-pip
pip3 install suntime pytz requests
```
| Package | Purpose |
|---|---|
| `ffmpeg` + `ffprobe` | Encoding, RTSP capture, audio recording, duration probing |
| `bc` | Floating-point arithmetic for speed factors |
| `fonts-dejavu` | Text overlays (sunrise time, attribution) |
| `python3` + `suntime pytz` | Astronomical sunrise calculation — pure math, no internet, works indefinitely |
| `python3` + `requests` | Mattermost upload |
### 2. 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
```
### 3. Configure
```bash
nano sky-cam.conf
```
`SCRIPT_DIR` and `BASE_DIR` are auto-detected — you only need to fill in settings specific to your setup:
| Setting | What it is |
|---|---|
| `BASE_DIR` | Root where camera images live — override if storing on a separate drive (`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 MP3 files |
| `CAMERAS` | Space-separated list of camera names, e.g. `(east north south west)` |
| `SUNRISE_CAM` | Which camera faces east and gets the sunrise job |
| `LATITUDE` / `LONGITUDE` / `TIMEZONE` | Your location for sunrise calculation |
| `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
```bash
./install.sh # generates .env.example alongside systemd units
cp .env.example .env
$EDITOR .env
```
`.env` holds all sensitive values and is never committed to git:
```bash
# RTSP stream URL — one per camera (variable name matches camera name)
CAM_RTSP_sunrise=rtsp://admin:password@192.168.1.100:554/stream1
# Mattermost upload
mattermost_url=https://your-mattermost.example.com
access_token=your-token
channel_id=your-channel-id
# Notifications (uncomment what you use)
#NTFY_URL=https://ntfy.sh/your-topic
#EMAIL_TO=you@example.com
```
Re-run `./install.sh` any time `sky-cam.conf` changes (schedules, cameras, etc.).
### 5. Verify
```bash
# Confirm cameras are capturing
systemctl --user status sky-cam-capture-east.service
ls BASE_DIR/east/$(date +%Y-%m-%d)/ # images should appear within CAPTURE_INTERVAL seconds
# Check all timers are scheduled
systemctl --user list-timers 'sky-cam-*'
# Test sunrise calculation
python3 sunrise.py
```
---
## 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 after 30s on loss
├─ sunrise-audio-capture.sh (starts at 03:00, waits internally for sunrise)
│ Calculates today's sunrise, then records exactly SUNRISE_TARGET_SECS of audio
│ centred on the moment of sunrise (odd second goes to post-sunrise)
│ 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 (starts at SCHEDULE_SUNRISE, waits for capture window)
│ Wakes at SCHEDULE_SUNRISE, sleeps until (sunrise + SUNRISE_POST_MIN)
│ Step 1: encode raw video from sunrise-window JPEGs
│ Step 2: speed-adjust to SUNRISE_TARGET_SECS → saved permanently
│ Step 3a: mix audio (camera mic → library fallback → no audio)
│ Step 3b: burn sunrise time overlay (optional, set SUNRISE_OVERLAY_ENABLED=false to skip)
│ Each step degrades independently — upload always fires
│ 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
Every pipeline saves an intermediate file before the riskiest step, so a partial failure always leaves something uploadable:
**Sunrise video — four-tier fallback, upload always fires:**
| Audio | Overlay | What gets uploaded |
|---|---|---|
| ✓ | ✓ | Final video with audio + timestamp |
| ✓ | ✗ | Audio-mixed video, no timestamp — overlay failure notified |
| ✗ | ✓ | Video with timestamp, no audio — audio failure notified |
| ✗ | ✗ | Speed-only video — both failures notified |
**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>` |
Notifications fire for:
- Sunrise video ready (with audio/overlay status), upload success/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 records exactly `SUNRISE_TARGET_SECS` of natural-speed audio centred on the actual sunrise moment and mixes it into the timelapse video.
1. Set `AUDIO_ENABLED=true` in `sky-cam.conf`
2. Set `CAM_RTSP_<cam>` in `.env` for the sunrise camera
3. Re-run `./install.sh` to generate the `sky-cam-audio-capture.timer`
The recording is split evenly around sunrise (e.g. 5 s before + 5 s after for a 10 s clip; odd second goes to post-sunrise). It is deleted automatically after mixing.
### 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`.
**Audio priority order:**
1. Camera mic recording (`sunrise-audio.m4a`) — real ambient sound at actual sunrise
2. Random file from `sunrise-sounds/` library
3. No audio — overlay-only video (always produced regardless)
---
## Manual operations
**Re-run today's sunrise** (e.g. after a config fix):
```bash
./daily_sunrise_video.sh
```
**Re-run a daily seasons clip** for yesterday:
```bash
./4-seasons.sh east
```
**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
```
**Rebuild the year-end video**:
```bash
./year-end-join.sh 2025 east
```
**Check capture status**:
```bash
systemctl --user status sky-cam-capture-east.service
journalctl --user -u sky-cam-capture-east.service -f
```
**Check logs**:
```bash
journalctl --user -u sky-cam-sunrise.service
journalctl --user -u sky-cam-seasons-east.service
journalctl --user -u sky-cam-fullday-east.service
```