Commit Graph
21 Commits
Author SHA1 Message Date
Claude 5fb7ff09a3 Add nightly moon-track timelapse and monthly moon-phase composites
Two new jobs operate on the SUNRISE_CAM, sharing three Python helpers
(moon_phase, moon_detect, moon_composite) and one cached lunar texture:

- moon-track.sh: nightly batch detects the moon in each east frame,
  crops a 480x480 box around it, stitches into an mp4 that holds the
  moon roughly centred while clouds and stars drift past.

- moon-phase-monthly.sh: daily check that runs whichever phase composite
  is due that day. Handles full moon (posts D+3), first quarter (D+2,
  best-effort due to daytime-only geometry from east), and third quarter
  (D+2). Picks the frame closest in time to the exact phase moment that
  meets quality / altitude / illumination thresholds, then composites
  the cached lunar texture into it -- sky/halo/parallactic-angle/timing
  real from east, surface detail borrowed from the reference image.

Honest-by-design: a 38-px white blob from a wide-field IP camera cannot
be enhanced into crater detail by software. The composite makes the
borrowing explicit and constrains everything else (when, where, sky,
orientation) to match what east actually saw.

install.sh now downloads the skyfield ephemeris (de421.bsp) and the
default lunar reference (Wikipedia CC BY-SA full-moon photo) on first
run. Both can be overridden via .env.

https://claude.ai/code/session_015PBVDESC3KLMbq1LpA6qLn
2026-04-30 11:40:11 +00:00
Claude 11d4630197 Restructure audio into AUDIO_DIR with per-camera retention
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
2026-04-22 14:43:05 +00:00
Claude 3c50e5dad2 Clarify .env vs sky-cam.conf split for toggles and private values
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
2026-04-22 13:56:58 +00:00
Claude 2a28c8e5ea Expand .env.example to cover all machine-specific config
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
2026-04-22 13:43:02 +00:00
Claude 74a74570fc Move location/music/ntfy config to .env; add paths to notifications
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
2026-04-22 13:30:02 +00:00
Claude ae40b898f6 Fix executable bits and add camera name to 4-seasons notifications
- 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
2026-04-22 12:56:37 +00:00
Claude dbdd59677f Per-camera capture interval; ambient recorder; fix 4-seasons one-frame bug
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
2026-04-21 21:08:10 +00:00
Claude c5474714be install.sh: restart capture services instead of enable --now
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
2026-04-21 18:28:44 +00:00
Claude 5c88b12b9f Pre-create camera data directories in install.sh
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
2026-04-21 16:00:48 +00:00
Claude 3c2e413ea2 Expand URL-encoding reference in .env.example
Full number-row special character table with percent codes, safe
characters called out, and a note on : and / which break URL parsing.

https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
2026-04-21 15:25:12 +00:00
Claude 9d4d50b91c Fix install.sh for non-login shell environments
- 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
2026-04-21 15:21:12 +00:00
Claude b4019ade9d Add north/south cameras, remove fullday, add verify-mvt.sh
- 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
2026-04-20 15:56:12 +00:00
Claude 3265ed80fd Add per-camera capture watchdog with stall and recovery notifications
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
2026-04-20 01:13:49 +00:00
Claude 8b9ad74c58 Fix six bugs found in final audit
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
2026-04-20 01:01:25 +00:00
Claude a1b9ffa8ee Add .env credentials file support; gitignore .env and sunrise-sounds/
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
2026-04-19 23:08:01 +00:00
Claude 66fe1f46ca Fix CAM_NAME bug, set -x noise, wrong Python package in README
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
2026-04-19 21:56:33 +00:00
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
Claude 7486916f9f Sunrise time overlay; systemd upload separation; bug fixes
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
2026-04-19 16:35:33 +00:00
Claude 6d85fa525a Multi-camera support; schedules in conf; comment out notifications
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
2026-04-19 11:04:51 +00:00
Claude 4ca1f579d2 Rename sunrise_video.10s.sh → daily_sunrise_video.sh
Name no longer implies a fixed duration. Updated all references:
install.sh, sky-cam.conf script map, systemd/sky-cam-sunrise.service.

https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
2026-04-19 00:52:24 +00:00
Claude 5d3ee97df0 All paths into sky-cam.conf; install.sh generates systemd units
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
2026-04-18 21:31:26 +00:00