New AUDIO_DIR variable (like BASE_DIR/MOVIES_DIR) is the root for all
audio recordings. Set in .env to put audio on a separate drive.
Directory layout:
AUDIO_DIR/<cam>/YYYY-MM-DD/HH-MM-SS.m4a — ambient recordings
AUDIO_DIR/sunrise/<cam>/YYYY-MM-DD/sunrise-audio.m4a — sunrise clips
sky-cam.conf:
- Add AUDIO_DIR="${AUDIO_DIR:-$BASE_DIR/audio}" in storage section
- Remove AMBIENT_DIR (replaced by AUDIO_DIR)
- Add SUNRISE_AUDIO_RETENTION_DAYS=7 (sunrise clips kept separately from ambient)
- Add per-camera ambient retention examples:
AMBIENT_RETENTION_DAYS_south=60 (keep bird recordings longer)
ambient-record.sh:
- Use AUDIO_DIR instead of AMBIENT_DIR
- Per-camera retention: AMBIENT_RETENTION_DAYS_<cam> overrides global default
sunrise-audio-capture.sh:
- Save clips to AUDIO_DIR/sunrise/<cam>/<date>/sunrise-audio.m4a
- Rolling retention of sunrise clips (SUNRISE_AUDIO_RETENTION_DAYS)
daily_sunrise_video.sh:
- Look for cam_audio in new AUDIO_DIR/sunrise path first, fall back to
old BASE_DIR path so existing recordings keep working
install.sh:
- Pre-create AUDIO_DIR/<cam> and AUDIO_DIR/sunrise/<SUNRISE_CAM> dirs
- Add AUDIO_DIR to .env.example storage section
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
Rule is now consistent throughout:
- *_ENABLED flags are plain true/false in sky-cam.conf — flip them there
- Private values (URLs, credentials, location) go in .env only
sky-cam.conf:
- NTFY_ENABLED, EMAIL_ENABLED, MM_NOTIFY_ENABLED are now plain false (not
using the ${VAR:-} pattern, which implied they could go in .env)
- NTFY_URL, EMAIL_TO, EMAIL_FROM, MM_NOTIFY_CHANNEL_ID use ${VAR:-} pattern
so they are set in .env
- LATITUDE/LONGITUDE comment clarified with example .env lines
install.sh .env.example: updated notification comment to match
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
Adds TIMEZONE, BASE_DIR, MOVIES_DIR (commented out) so .env.example
is a complete reference — location, storage paths, RTSP URLs, Mattermost
credentials, and notification endpoints all in one place.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
sky-cam.conf:
- MUSIC_DIR now uses override pattern (MUSIC_DIR="${MUSIC_DIR:-$SCRIPT_DIR/music}")
so the real path can be set in .env without hardcoding it
- LATITUDE/LONGITUDE use override pattern; set real coords in .env to keep
location private and out of git
- NTFY_URL now uses override pattern with empty default; set in .env
- NTFY_ENABLED moved out of comments into active config with false default
sunrise.py:
- Reads .env after sky-cam.conf so .env values override (same source order
as sky-cam.conf itself)
- Handles ${VAR:-default} bash syntax when parsing sky-cam.conf values
All completion notifications now include the full output file path so you
can tell at a glance where the file landed:
- daily_sunrise_video.sh: "filename — Xs, sunrise at HH:MM | /full/path"
- 4-seasons.sh: "filename — Xs | /full/path"
- montage-mvt.sh: "filename | checks | /full/path | verify: ..."
- year-end-join.sh: "filename — Xmin | /full/path" (also adds [cam] to title)
install.sh .env.example now includes LATITUDE, LONGITUDE, MUSIC_DIR, NTFY_URL
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
- chmod +x all *.sh and *.py in install.sh so permissions survive git checkouts
- Fix daily_sunrise_video.sh and Python scripts missing executable bit
- Add [CAM_NAME] prefix to all 4-seasons.sh notify.sh calls so multi-camera
setups show which camera each daily clip came from
- Include camera name in output filename: YYYY-MM-DD_<cam>_MvtN-DayXofY-final.mp4
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
Per-camera capture interval:
CAPTURE_INTERVAL_<cam> in sky-cam.conf overrides the global value for
a specific camera. capture.sh and 4-seasons.sh both respect it.
Example: south at 30s saves storage; north at 5s gives smoother motion.
4-seasons.sh one-frame bug fix:
Same root cause as the sunrise scripts — concat list had no 'duration'
entries, collapsing all JPEG frames to timestamp 0. Fixed by writing
'duration INTERVAL' per frame. Added fps=25 to the speed-adjust step
so the VFR source resamples to a standard frame rate.
ambient-record.sh (new):
Continuous AAC recorder for natural sounds (birds, rain, wind).
Saves AMBIENT_CHUNK_SECS chunks to AMBIENT_DIR/<cam>/YYYY-MM-DD/HH-MM-SS.m4a.
Reconnects automatically on stream failure. Rolling retention via
AMBIENT_RETENTION_DAYS. Enabled by setting AMBIENT_ENABLED=true and
listing cameras in AMBIENT_CAMS in sky-cam.conf, then re-running install.sh.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
enable --now only starts a service that is not yet running — it leaves
an already-running service untouched, so changes to .env credentials
are never picked up without a manual restart.
Switching to enable + restart means every install.sh run brings capture
services in sync with the current sky-cam.conf and .env: new cameras
start, reconfigured cameras restart, and nothing needs manual follow-up.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
capture.sh creates the per-day subdirectory at runtime, but BASE_DIR/<cam>
and MOVIES_DIR/<cam> need to exist first. Create them during install so the
capture service works immediately on first start without needing a separate
manual mkdir step.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
- Set XDG_RUNTIME_DIR before systemctl --user calls so daemon-reload
and enable --now work when run outside a login session (e.g. via SSH
or sudo)
- Run loginctl enable-linger so user services start at boot without a
login session
- Update .env.example to use single quotes around RTSP URLs and
document URL-encoding for special characters in passwords
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
- sky-cam.conf: CAMERAS now includes east, north, south with staggered
SCHEDULE_SEASONS times (01:00, 01:30, 02:00); fullday schedules and
tuning removed
- install.sh: fullday service/timer generation removed entirely
- fullday-video.sh + systemd units: deleted
- montage-mvt.sh: post-build technical checks (duration drift, video/audio
stream presence) added; notification now includes check results and the
exact verify-mvt.sh command to run
- verify-mvt.sh: new interactive CLI for montage review — shows checks,
plays video, deletes source JPEG folders on approval, or re-runs montage
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
capture-watchdog.sh: long-running script that checks the newest JPEG mtime
in each camera's output directory every CAPTURE_STALE_SECS/3 seconds (min 5s).
Sends a stall notification via notify.sh if no new frame has arrived within
CAPTURE_STALE_SECS. Sends a recovery notification once new frames resume.
Only alerts if the camera was previously working (avoids false positives on
first start or overnight before the first frame of the day).
sky-cam.conf: CAPTURE_STALE_SECS=30 added next to CAPTURE_INTERVAL with a
note that it should be at least 2x the interval.
install.sh: generates sky-cam-watchdog-<cam>.service for every camera that
has a RTSP URL configured. The watchdog service starts after and alongside
the capture service, restarts on failure, and is enabled/started with the
same loop as the capture service.
README.md: note about watchdog and journalctl command for its logs.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
montage-mvt.sh, year-end-join.sh: CAM_NAME fallback referenced undefined
\$CAM_NAME — crashes under set -euo pipefail on manual runs without args.
Fixed to fall back to \$SUNRISE_CAM (same fix applied earlier to other scripts).
notify.sh: Mattermost notification guard only checked MM_NOTIFY_CHANNEL_ID,
not mattermost_url or access_token. If those are unset, curl would send a
malformed request silently. Added guards for all three required values.
install.sh: seasons/fullday service units had literal \n in After=/Wants=
lines because bash does not interpret \n in heredoc variable expansions.
Changed to \$'...' syntax so actual newlines are written, making valid
systemd unit files. Also: audio capture timer now uses \$SCHEDULE_SUNRISE
instead of hardcoded 03:00, keeping it in sync if the user changes the
schedule.
sunrise-audio-capture.sh: record_start_sec could theoretically go negative
(sunrise very early + large SUNRISE_TARGET_SECS). Added floor-at-zero guard.
daily_sunrise_video.sh: added comment explaining why audio_offset is always 0.
README.md: fix CAM_RTSP_sunrise example to CAM_RTSP_east.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
sky-cam.conf now sources .env from the same directory at the bottom of the
file, so RTSP URLs, Mattermost tokens, and notification credentials can live
outside version control. Plaintext credential placeholders removed from conf.
install.sh generates .env.example on every run with the correct CAM_RTSP_*
variable names derived from the CAMERAS list, so users always know exactly
what to put in .env for their specific camera setup.
.gitignore: add .env and sunrise-sounds/ (downloaded audio library).
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
daily_sunrise_video.sh: derive CAM_NAME from $1 (defaulting to SUNRISE_CAM)
instead of an undefined variable; this was causing empty image/output paths.
install.sh: pass $SUNRISE_CAM as argument to daily_sunrise_video.sh so it
matches the pattern used by every other per-camera script.
sunrise2mm.py: use SUNRISE_CAM config key (not the undefined CAM_NAME) to
locate the output directory; fixes silent path mismatch on non-default names.
4-seasons.sh: remove stray `set -x` that was flooding systemd logs with
shell trace output.
README.md: correct Python dependencies from `ephem` to `suntime pytz` to
match what sunrise.py actually imports.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
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
daily_sunrise_video.sh:
- Fixed: was using undefined \$MOVIES_DIR (now \$BASE_DIR/movies)
- Fixed: sunrise_overlay.py was called but never existed — would fail daily
- Fixed: speed-adjust step had no explicit codec (-c:v libx264 missing)
- New: sunrise time overlaid as stacked vertical characters on the right
side using ffmpeg drawtext — no Python/Pillow dependency
- New: upload removed from script; now triggered by systemd OnSuccess=
- Combines speed-adjust and overlay into one ffmpeg pass (was two)
- SUNRISE_OVERLAY_OPACITY from conf controls transparency
install.sh:
- sky-cam-sunrise.service gains OnSuccess=sky-cam-sunrise-upload.service
so upload only runs when video creation succeeds, with separate log
- New sky-cam-sunrise-upload.service runs sunrise2mm.py
sunrise2mm.py: add shebang + chmod +x so systemd ExecStart= calls it directly
sky-cam.conf: add SUNRISE_OVERLAY_OPACITY=0.45
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
sky-cam.conf:
- CAMERAS array replaces single CAM_NAME; add a name, add its schedules,
re-run install.sh — no script editing needed
- SCHEDULE_SUNRISE, SCHEDULE_SEASONS_<cam>, SCHEDULE_FULLDAY_<cam> replace
hardcoded times in install.sh
- Notification options commented out (uncomment to enable)
- Lat/lon set to mid-Atlantic (25.0, -38.0) as neutral placeholder
install.sh:
- Loops over CAMERAS to generate per-camera sky-cam-seasons-<cam> and
sky-cam-fullday-<cam> timers with schedules from conf
- Sunrise timer remains a single sky-cam-sunrise unit (SUNRISE_CAM only)
fullday-video.sh, 4-seasons.sh, montage-mvt.sh, year-end-join.sh:
- Each accepts camera name as an argument (systemd passes it via ExecStart)
and falls back to conf default when run manually without an arg
- Camera name propagates through the full call chain:
4-seasons → montage-mvt → year-end-join
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
sky-cam.conf
- Add SCRIPT_DIR (install location) and CAM_NAME (camera/folder name)
- These are the only values that change when deploying to a new machine
install.sh (new)
- Sources sky-cam.conf, generates all systemd .service and .timer units
with correct ExecStart paths, then enables and starts the timers
- Run once after editing sky-cam.conf; run again if paths change
- Supports --system flag for system-wide install
All .sh scripts + sunrise2mm.py
- No hardcoded paths remain
- script_name=$(basename ...) derivation replaced by $CAM_NAME from conf
- sunrise_video.10s.sh now sources sky-cam.conf for BASE_DIR, CAM_NAME,
and SCRIPT_DIR (replaces all /home/motion/... references)
- sunrise2mm.py builds video path from BASE_DIR + CAM_NAME in conf
systemd/ template files kept as reference but no longer need editing
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ