- 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
--cam NAME overrides SUNRISE_CAM so the script can be used for any
camera (e.g. north). Summer is now included in the find targets so
any ASTRO_YEAR=2025 Summer clips (Summer 2026) are also migrated.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
The old pattern *Mvt*3* matched .mp3 in every MP3 filename, so sort|head-1
always picked track 01 (Mvt1). Fix by finding all files for the season
then filtering with grep -iE "Mvt[^0-9]*N[^0-9]", which requires the
movement number to be surrounded by non-digit characters (space, dot,
underscore) and cannot match digits embedded in .mp3 or track prefixes.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
All *-final.mp4 clips are valid footage regardless of what speed factor
the old script used — montage-mvt.sh re-adjusts the full concatenated
video to match music duration anyway, so per-clip speed is irrelevant.
Move every ASTRO_YEAR=2025 clip to the correct new movement directory
based on its date. Spring Mvt1 now gets all 30 clips (Mar 21–Apr 19).
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
- Filter to ASTRO_YEAR=2025 (Winter 2025-26 onward); skip Autumn 2025 and earlier
- Move files instead of creating symlinks
- Quarantine clips matching Day<N>of92 (Spring clips from broken script with
wrong 92-day movement total) to quarantine-old-scripts/ for review + deletion
- Preserve clips with Dayof92 (no day number, old legacy format) — these are
migrated normally since montage-mvt.sh re-adjusts speed regardless
- Summary shows delete commands for temp files and quarantine folder
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
migrate-seasons.sh uses season_info.py to re-classify every old
*-final.mp4 clip by its YYYY-MM-DD filename prefix, placing a symlink
in the correct new-system directory regardless of how the old system
labelled movements. Handles the boundary mismatches between old and
new movement splits automatically.
Coverage after migration (based on available clips):
Spring 2026 Mvt1 30/31 days (missing Day 1 only — excellent)
Winter Mvt1 ~18/29 days
Winter Mvt2 ~24/29 days
Winter Mvt3 ~20/31 days
Autumn Mvt1 ~24/30 days
Autumn Mvt3 ~18/30 days
Autumn Mvt2 absent — year-end join will not fire for 2024 cycle
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
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
- Step 1 is now dependency installation (apt + pip3) before anything else
- SCRIPT_DIR removed from config table (auto-detected)
- RTSP/Mattermost credentials moved to .env section (step 4)
- install.sh now shown as generating .env.example before credentials are filled in
- Schedule settings explained: SEASONS/FULLDAY process yesterday; SUNRISE waits internally
- How it works: audio capture described as SUNRISE_TARGET_SECS centred on sunrise
- daily_sunrise_video.sh pipeline updated: steps 3a/3b shown separately
- Resilience section replaced with four-tier audio/overlay matrix table
- Camera audio section updated: exact duration, centred split, odd-second rule
- Manual operations: examples use explicit camera name argument
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
4-seasons.sh, fullday-video.sh: CAM_NAME fallback referenced undefined
\$CAM_NAME — under set -euo pipefail this crashes if called manually without
an argument. Changed to fall back to \$SUNRISE_CAM (matches capture.sh).
daily_sunrise_video.sh: add log message when the capture window has already
closed (manual re-run after the fact). Behaviour is unchanged — processing
proceeds immediately — but the operator now gets a clear confirmation rather
than silence.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
Previously sunrise-audio-capture.sh recorded the entire capture window
(70+ minutes) and daily_sunrise_video.sh sought to the right position with
a complex offset calculation.
Now: record floor(TARGET/2) seconds before sunrise and ceil(TARGET/2) after —
odd second goes to post-sunrise. The file is exactly SUNRISE_TARGET_SECS long
and already centred, so no offset is needed when mixing.
AUDIO_PRE_BUFFER_MIN removed — it was part of the old whole-window scheme
and is no longer meaningful.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
sky-cam.conf:
- SCRIPT_DIR auto-detects from its own location via BASH_SOURCE — no manual
path entry needed after bootstrap; override still works via environment.
- BASE_DIR defaults to \$SCRIPT_DIR/data; MOVIES_DIR defaults to \$BASE_DIR/movies.
Override either to point at a drive/mount as before.
- SCHEDULE_SUNRISE changed to 03:00 — the script now waits internally until
the capture window closes before touching images, so the schedule just needs
to be early enough, not timed exactly to sunrise.
- Schedule section rewritten to explain what each job does and what day's data
it processes (SEASONS/FULLDAY always process yesterday).
- SUNRISE_PRE_MIN/POST_MIN comments explain the window and its relationship to
SUNRISE_TARGET_SECS.
- SUNRISE_OVERLAY_ENABLED=true added; set false to skip the timestamp entirely.
daily_sunrise_video.sh:
- After calculating the capture window, sleep until end_sec+30s if we fired
before the window finished — guarantees all images are present.
- DT (drawtext filter) is only built when SUNRISE_OVERLAY_ENABLED=true.
- Step 3b skips ffmpeg entirely when overlay is disabled, moving work_video
directly to final_video (faster, no re-encode).
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
daily_sunrise_video.sh: use \$MOVIES_DIR instead of \$BASE_DIR/movies so the
override in sky-cam.conf is respected when videos live on a separate drive.
capture.sh: default camera name falls back to \$SUNRISE_CAM (not the
undefined \$CAM_NAME) when no argument is passed.
sunrise2mm.py: also loads .env after sky-cam.conf so Mattermost credentials
moved to .env are actually visible to the upload script.
sunrise.py: raise a clear error message when LATITUDE/LONGITUDE/TIMEZONE
are missing from sky-cam.conf instead of an opaque KeyError.
4-seasons.sh, montage-mvt.sh: capture season_info.py output before eval so
a Python failure exits cleanly with a diagnostic message rather than
silently continuing with undefined variables.
bootstrap.sh: add system package install step (ffmpeg bc fonts-dejavu) and
show the .env setup step after install.sh generates .env.example.
README.md: correct Python package names and add fonts-dejavu dependency.
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
Each stage can now fail independently, giving four upload paths:
audio+overlay, audio-only, overlay-only, or speed-only video.
Step 3a copies the video stream (no re-encode) when mixing audio,
so step 3b failure still leaves a clean audio-mixed file to promote.
Overlay failure notification now reports which quality was uploaded.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
daily_sunrise_video.sh: priority chain is camera mic → library random MP3 →
overlay-only → sped video promotion. If audio mixing fails a warning
notification fires and the script retries with overlay-only so a video is
always produced and uploaded.
download-sunrise-sounds.py: downloads CC-licensed ambient sounds from
freesound.org into 11 weather/season category folders (~275 files total at
25 per category), writing a manifest.json with attribution data.
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
Sunrise:
- Overlay failure no longer exits 1; the speed-only video is renamed to the
expected final path so OnSuccess still fires and the upload still happens
- sunrise2mm.py now notifies on every upload outcome (success, post failure,
file-not-found) via notify.sh so the user always knows whether the video
reached Mattermost
Montage:
- Music+overlay failure no longer exits 1; the sped video is promoted to the
*-Montage.mp4 path so year-end-join can still include this movement (as a
silent, no-overlay clip) and the user is notified of the degraded output
- Year-end join trigger is now outside the success/failure branch so it fires
in both cases
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
Each pipeline now saves intermediate files permanently before the step most
likely to fail, so a partial failure leaves a recoverable artifact:
- daily_sunrise_video.sh: speed-adjusted video (*-sped.mp4) survives if the
drawtext overlay step fails; deleted automatically on success
- montage-mvt.sh: speed-adjusted video (*-Sped.mp4) survives if the
music-mux + attribution overlay step fails; deleted on success
- 4-seasons.sh / montage-mvt.sh / year-end-join.sh: explicit ffmpeg error
handling with targeted notify.sh calls naming the failed step and the
surviving artifact path
- montage-mvt.sh / 4-seasons.sh / year-end-join.sh: clip-duration-sum vs
music-duration drift check warns when cumulative float error exceeds 2 s
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
Adds six CRF vars, AUDIO_BITRATE, and three MONTAGE_* overlay durations.
Values match previous hardcoded defaults so behavior is unchanged.
The same CRF appeared in several scripts but with intentionally different
values per purpose (28 for draft intermediates, 26 for polished output).
Each script's CRF is now its own variable in conf, so changing one never
silently changes another.
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
Video duration is still controlled by SUNRISE_TARGET_SECS in conf,
but the filename no longer encodes it — so changing the duration
doesn't break anything and the name stays stable day to day.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
The output video was always named -sunrise-10s.mp4 regardless of the
configured duration. Both sunrise_video.10s.sh and sunrise2mm.py now
derive the filename from SUNRISE_TARGET_SECS so changing it in conf
produces correctly named files that the uploader still finds.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
- sunrise_video.10s.sh: use $TIMEZONE (was hardcoded America/New_York),
$SUNRISE_PRE_MIN/$SUNRISE_POST_MIN for capture window, $SUNRISE_TARGET_SECS
for output duration (was wrong value 8 in a script named 10s)
- fullday-video.sh: remove hardcoded FULLDAY_FPS/RETENTION_DAYS; both now
come from sky-cam.conf
- 4-seasons.sh, montage-mvt.sh: export TIMEZONE after sourcing conf so
season_info.py subprocess picks it up via env rather than falling back
to its hardcoded America/New_York default
- sky-cam.conf: add SUNRISE_PRE_MIN, SUNRISE_POST_MIN, SUNRISE_TARGET_SECS,
FULLDAY_FPS, RETENTION_DAYS
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
sunrise.py now reads LATITUDE, LONGITUDE, and TIMEZONE from sky-cam.conf
instead of hardcoded values, using the same pathlib-based pattern as
sunrise2mm.py. sky-cam.conf gains a comprehensive header with manual
systemd setup instructions and a full script map for future reference.
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
sky-cam.conf (new)
- Single file replaces mattermost_config.txt reference, notify_config.txt,
and the hardcoded BASE_DIR/MUSIC_DIR/TIMEZONE scattered across scripts
- Bash-sourceable and Python key=value readable (same format)
All .sh scripts
- source sky-cam.conf at startup; BASE_DIR / MUSIC_DIR from config
sunrise2mm.py
- Reads sky-cam.conf relative to script location; no hardcoded path
notify.sh
- Sources sky-cam.conf; Mattermost credentials come from there directly,
no separate mattermost_config.txt lookup needed
systemd/sky-cam-notify-failure@.service (new)
- Template unit called by OnFailure= in each service
- Sends notification via notify.sh when any sky-cam job fails
- All three .service units now have OnFailure=sky-cam-notify-failure@%n.service
Removed: notify_config.txt, sky-cam.crontab (superseded)
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
season_info.py
- Add ASTRO_YEAR: year of the Winter solstice that started the current cycle
(Spring/Summer/Autumn 2026 → 2025; Winter Dec-2026 → 2026)
4-seasons.sh
- Daily clip output path now includes ASTRO_YEAR:
movies/$name/$ASTRO_YEAR/$SEASON/Mvt$N/
montage-mvt.sh (full rewrite)
- Attribution is now a drawtext overlay (top 6 s of the video, fade in/out)
instead of an appended black card — no frame-size matching needed, and
year-end join requires no special handling
- Output path mirrors the new year-based folder structure
- Sends notification via notify.sh when done
- Automatically triggers year-end-join.sh after Autumn Mvt 3
year-end-join.sh (new)
- Concatenates all 12 movement montages for a given ASTRO_YEAR
- Output: movies/$name/$ASTRO_YEAR/$name-$ASTRO_YEAR.mp4
- Sends notification when done
notify.sh + notify_config.txt (new)
- Generic best-effort notification helper: ntfy, email, Mattermost text post
- All methods disabled by default; user fills in notify_config.txt
sky-cam.crontab + systemd/ (new)
- Crontab file: sunrise at 09:00, 4-seasons at 01:00, fullday at 02:00
- systemd .service + .timer units for all three daily jobs
- montage/year-end triggered internally, not by cron
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
- Warp only north cylindrically (sunrise completely unmodified)
- Focal-length grid search picks the best phase-correlation alignment
- Soft validity mask (31x31 erosion) fades out black corners of the warp
- Per-channel colour/exposure match: north's treeline strip mean is scaled
to match sunrise's, removing the visible sky-colour seam between cameras
- Corrected overlap blend: invalid north pixels fall back to sunrise
(s_weight = max(alpha, 1-validity)) instead of showing black
- Auto-crops left black-corner columns from the final canvas
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
North is projected onto a cylinder so its right edge curves naturally into
sunrise's left edge, giving a continuous panoramic sweep from North to East.
cyl-f1920-preview.jpg — f=1920 px (~90° HFOV): most natural perspective
cyl-f1550-preview.jpg — f=1550 px (~105° HFOV): more pronounced curve
stitch-cameras.py updated to use cylindrical projection. Focal length is
a tunable argv[4]; refine once the user confirms which curve looks right.
The 36-second inter-shot gap causes cloud drift at the seam — live video
frames captured simultaneously will not have this artefact.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
Phase-correlation on the treeline/horizon band (rows 70-90%) found:
tx = -3324 px (north is 3324 px to the left of sunrise)
ty = -85 px (north camera is 85 px higher — slight tilt difference)
overlap = 516 px wide, feather-blended with a horizontal gradient
Result in images/stitch-preview/north-sunrise-aligned-preview.jpg:
7164x2075 px panorama with continuous horizon and treeline.
Cloud "V" at seam is a 36-second inter-shot artefact only; live video
frames captured simultaneously will have no such discontinuity.
stitch-cameras.py: accepts tx/ty overrides as argv[4]/argv[5] so the
offsets can be tuned without code changes.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
- images/stitch-preview/north-sunrise-hstack.jpg:
naive side-by-side preview of the two skycams at 14:29 on 2026-04-17
- images/stitch-preview/feature-matches-debug.jpg:
close-up of overlap region (right 40% of north × left 40% of sunrise)
with ORB Lowe-ratio matches drawn — 39 candidates, RANSAC survives only
4 inliers because the 90° perspective offset invalidates a similarity
transform. Overlap IS present (trees, houses, power poles visible in
both) but full homography needs more/better tie points.
- stitch-cameras.py: reusable script that generates both previews plus a
similarity-transform .txt for reuse in ffmpeg filter chains once we
have enough matches (or manual tie-points).
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
season_info.py (new):
- Computes actual equinox/solstice dates via Jean Meeus approximation
- Converts to LOCAL timezone (pytz / zoneinfo) so the calendar date reflects
what the camera sees on the ground, including DST transitions
- Timezone defaults to America/New_York; override via $TIMEZONE env or 2nd arg
- Divides each season into 3 equal movements; handles Winter's year boundary
- Outputs IS_LAST_DAY=true on the final day of each movement
4-seasons.sh:
- Uses season_info.py for all date/season logic (no hardcoded day-of-year)
- Passes $yesterday to season_info.py so astronomical dates are correct
- Removed forced 1280x720 scale; clips stay at native camera resolution
- Auto-triggers montage-mvt.sh "$yesterday" on last day of movement so the
correct movement (not the next day's) is compiled
montage-mvt.sh:
- Accepts optional YYYY-MM-DD argument (passed by 4-seasons.sh) so it
resolves the correct season/movement when run the morning after last day
- Detects source resolution from first daily clip via ffprobe; attribution
card is generated at that exact resolution — no resizing of main video
- Font sizes proportional to frame height so text looks right at any res
- License corrected to CC BY-SA 3.0 (was CC BY-NC-SA 4.0)
fullday-video.sh:
- Removed 30-minute target; video length is natural (num_images / FPS)
- Default FULLDAY_FPS=5 (configurable at top of script)
- Retains RETENTION_DAYS=10 auto-delete for full-day videos only
- Sunrise 10s uploads and montage videos are never auto-deleted
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
4-seasons.sh:
- Fix per-video target duration: divide by days_in_movement (~30) not
days_in_season (~90), so concatenated clips sum to the movement's
music duration
- Force consistent 1280x720 output (scale+pad) to prevent resolution
mismatches when montage-mvt.sh concatenates across days
- Use ffprobe (not ffmpeg stderr parsing) for reliable duration reading
- Add music-dir fallback to repo-local music/ folder
- Remove the 2× music-doubling workaround (root cause fixed above)
montage-mvt.sh — complete rewrite:
- Proper season/movement detection matching 4-seasons.sh logic
- Step 1: concat all *-final.mp4 clips, normalise to 1280x720
- Step 2: speed-adjust concatenated video to exactly match music duration
- Step 3: merge with Vivaldi movement audio + 2 s video/audio fade in/out
- Step 4: generate 7-second black attribution card (drawtext) crediting
John Harrison / Wichita State University Chamber Players / FMA /
CC BY-NC-SA 4.0
- Step 5: concat montage + attribution into final file
- Temp files cleaned up via EXIT trap
fullday-video.sh — new script:
- Full-day timelapse from yesterday's images targeting ~30 minutes
- Per-frame duration = 1800 / num_images (clamped 0.017–10 s/frame)
- 1280x720 output, video-only (no audio)
- Auto-deletes fullday videos older than 10 days
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ