Claude 9fe0051f70 Add pure-ffmpeg RTSP capture and camera audio for sunrise videos
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
2026-04-19 20:59:40 +00:00
2026-04-17 23:01:35 -04:00
2026-04-17 22:48:48 -04:00

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.mp4year-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 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):

./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
S
Description
Mirror of outis1one/sky-cam from GitHub
Readme
60 MiB
Languages
Shell 55.2%
Python 44.8%