From 5d3ee97df0e7db29e914d178a9d2b703d63d3e2d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Apr 2026 21:31:26 +0000 Subject: [PATCH 1/2] 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 --- 4-seasons.sh | 5 +- fullday-video.sh | 5 +- install.sh | 115 +++++++++++++++++++++++++++++++++++++++++++ montage-mvt.sh | 3 +- sky-cam.conf | 22 ++++++--- sunrise2mm.py | 4 +- sunrise_video.10s.sh | 20 ++++---- year-end-join.sh | 10 ++-- 8 files changed, 151 insertions(+), 33 deletions(-) create mode 100755 install.sh diff --git a/4-seasons.sh b/4-seasons.sh index 54a903e..0109c1f 100755 --- a/4-seasons.sh +++ b/4-seasons.sh @@ -43,8 +43,7 @@ target_per_clip=$(echo "scale=6; $music_duration / $DAYS_IN_MVT" | bc) echo "Target clip duration: $target_per_clip s ($DAYS_IN_MVT days in movement)" # ── Locate yesterday's images ───────────────────────────────────────────────── -script_name=$(basename "$SCRIPT_DIR" | cut -d'-' -f1) -image_dir="$base_dir/$script_name/$yesterday" +image_dir="$base_dir/$CAM_NAME/$yesterday" if [ ! -d "$image_dir" ]; then echo "WARNING: Image directory $image_dir not found — skipping $yesterday." @@ -59,7 +58,7 @@ fi echo "Images found: $total_images" # ── Prepare output directory ────────────────────────────────────────────────── -output_dir="$base_dir/movies/$script_name/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM" +output_dir="$base_dir/movies/$CAM_NAME/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM" mkdir -p "$output_dir" # ── Build sorted image list ─────────────────────────────────────────────────── diff --git a/fullday-video.sh b/fullday-video.sh index be9fcf2..25d0ac6 100755 --- a/fullday-video.sh +++ b/fullday-video.sh @@ -17,9 +17,8 @@ RETENTION_DAYS=10 # full-day videos older than this are deleted # ── Date / paths ────────────────────────────────────────────────────────────── yesterday=$(date --date="yesterday" +%Y-%m-%d) -script_name=$(basename "$SCRIPT_DIR" | cut -d'-' -f1) -image_dir="$base_dir/$script_name/$yesterday" -output_dir="$base_dir/movies/$script_name/fullday" +image_dir="$base_dir/$CAM_NAME/$yesterday" +output_dir="$base_dir/movies/$CAM_NAME/fullday" mkdir -p "$output_dir" # ── Purge old full-day videos ───────────────────────────────────────────────── diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..b3e16d8 --- /dev/null +++ b/install.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# install.sh — generate and install sky-cam systemd units from sky-cam.conf. +# +# Usage: +# ./install.sh # installs to ~/.config/systemd/user (no root needed) +# ./install.sh --system # installs to /etc/systemd/system (needs sudo) +# +# Run this once after editing sky-cam.conf, and again whenever sky-cam.conf +# changes (e.g. SCRIPT_DIR moves). No other file needs editing. + +set -euo pipefail + +HERE="$(dirname "$(realpath "$0")")" +source "$HERE/sky-cam.conf" + +# ── Install target ──────────────────────────────────────────────────────────── +SYSTEM_MODE=false +[ "${1:-}" = "--system" ] && SYSTEM_MODE=true + +if $SYSTEM_MODE; then + UNIT_DIR="/etc/systemd/system" + SC="sudo systemctl" +else + UNIT_DIR="$HOME/.config/systemd/user" + SC="systemctl --user" +fi + +mkdir -p "$UNIT_DIR" +echo "Installing systemd units to $UNIT_DIR ..." + +# ── Helper: write a oneshot service unit ───────────────────────────────────── +write_service() { + local unit="$1" description="$2" script="$3" extra="${4:-}" + cat > "$UNIT_DIR/${unit}.service" < "$UNIT_DIR/${unit}.timer" < "$UNIT_DIR/sky-cam-notify-failure@.service" < Date: Sun, 19 Apr 2026 00:09:58 +0000 Subject: [PATCH 2/2] Add LATITUDE/LONGITUDE to conf; sunrise.py reads coords from conf 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 | 111 ++++++++++++++++++++++++++++++++++++++++++++------- sunrise.py | 53 +++++++++--------------- 2 files changed, 114 insertions(+), 50 deletions(-) diff --git a/sky-cam.conf b/sky-cam.conf index 12a433a..85df032 100644 --- a/sky-cam.conf +++ b/sky-cam.conf @@ -1,43 +1,124 @@ -# sky-cam.conf — single config for all sky-cam scripts and Python helpers. +# ============================================================================= +# sky-cam.conf — master configuration for all sky-cam scripts +# ============================================================================= # -# Bash scripts source this file: -# source "$(dirname "$(realpath "$0")")/sky-cam.conf" +# This is the ONLY file you need to edit. After editing, run: # -# Python reads it with key=value parsing (same format, quotes stripped). +# ./install.sh # installs systemd timers (no root needed) +# ./install.sh --system # system-wide install (requires sudo) # -# Fill in every value below, then run ./install.sh to deploy systemd timers. -# You should not need to edit any other file. +# Re-run install.sh any time this file changes. +# +# ----------------------------------------------------------------------------- +# MANUAL SYSTEMD SETUP (if you prefer not to use install.sh) +# ----------------------------------------------------------------------------- +# After editing this file, generate the unit files: +# +# mkdir -p ~/.config/systemd/user +# cp systemd/*.service systemd/*.timer ~/.config/systemd/user/ +# +# Then edit ExecStart= in each .service file to match your SCRIPT_DIR, then: +# +# systemctl --user daemon-reload +# systemctl --user enable --now sky-cam-sunrise.timer # 09:00 daily +# systemctl --user enable --now sky-cam-4seasons.timer # 01:00 daily +# systemctl --user enable --now sky-cam-fullday.timer # 02:00 daily +# +# Check status: +# systemctl --user list-timers 'sky-cam-*' +# journalctl --user -u sky-cam-4seasons.service -f +# +# System-wide (replace --user with no flag, prepend sudo): +# sudo cp systemd/*.service systemd/*.timer /etc/systemd/system/ +# sudo systemctl daemon-reload && sudo systemctl enable --now sky-cam-*.timer +# +# ----------------------------------------------------------------------------- +# SCRIPT MAP — what each file does and when it runs +# ----------------------------------------------------------------------------- +# +# You run / schedule: +# sunrise_video.10s.sh — daily at 09:00 (via systemd) +# grabs today's sunrise images, makes a 10-second +# video, uploads it to Mattermost +# +# 4-seasons.sh — daily at 01:00 (via systemd) +# makes yesterday's daily clip sized to its share +# of the matching Vivaldi movement's music duration; +# on the last day of a movement, auto-triggers +# montage-mvt.sh +# +# fullday-video.sh — daily at 02:00 (via systemd) +# full-day timelapse at fixed fps; deletes files +# older than 10 days automatically +# +# install.sh — run once at setup, and again if this file changes +# generates and installs systemd units from conf +# +# stitch-cameras.py — run manually when needed +# stitches north + sunrise into a panorama +# usage: python3 stitch-cameras.py north.jpg sunrise.jpg prefix [focal] +# +# Auto-triggered (do not run directly): +# montage-mvt.sh — called by 4-seasons.sh on last day of each movement +# concatenates all daily clips for the movement, +# speed-adjusts to match music, overlays attribution, +# sends completion notification; on last Autumn +# movement also triggers year-end-join.sh +# +# year-end-join.sh — called by montage-mvt.sh at end of Autumn Mvt 3 +# concatenates all 12 movement montages into one +# Four Seasons year video, sends notification +# +# Helpers (called by the scripts above, not run directly): +# notify.sh — sends notifications via ntfy / email / Mattermost +# season_info.py — outputs astronomical season/movement/year for a date +# sunrise.py — outputs today's sunrise time (used by sunrise_video) +# sunrise2mm.py — uploads the sunrise video to Mattermost +# +# ============================================================================= # ── Install location ────────────────────────────────────────────────────────── -# Full path to the directory containing this conf file and all the scripts. +# Full path to the directory containing this file and all the scripts. SCRIPT_DIR=/home/motion/drives/local-2tb/sunrise-scripts -# Camera name — used as the subfolder name under BASE_DIR and BASE_DIR/movies. -# E.g. "sunrise" → images at $BASE_DIR/sunrise/YYYY-MM-DD/, videos at $BASE_DIR/movies/sunrise/ +# Camera name — subfolder under BASE_DIR (images) and BASE_DIR/movies (videos). +# E.g. "sunrise" → images: $BASE_DIR/sunrise/YYYY-MM-DD/ videos: $BASE_DIR/movies/sunrise/ CAM_NAME=sunrise # ── Storage ─────────────────────────────────────────────────────────────────── BASE_DIR=/home/motion/drives/local-2tb MUSIC_DIR=/home/motion/drives/local-2tb/music/4 Seasons -# ── Timezone (IANA name) ────────────────────────────────────────────────────── +# ── Location & timezone ─────────────────────────────────────────────────────── +# Used by sunrise.py to calculate sunrise time each day. +# TIMEZONE must be a valid IANA name: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +LATITUDE=38.123456 +LONGITUDE=-97.654321 TIMEZONE=America/New_York -# ── Mattermost — daily sunrise upload ──────────────────────────────────────── +# ── Mattermost — daily sunrise upload ───────────────────────────────────────── mattermost_url=https://your-mattermost-server.example.com access_token=your-access-token-here channel_id=your-daily-upload-channel-id-here -# ── Notifications — montage/year-end complete + job failure ─────────────────── -# ntfy: https://ntfy.sh (cloud) or self-hosted. +# ── Notifications — montage / year-end complete + any job failure ───────────── +# Notifications fire when a movement montage or the year video finishes. +# They also fire automatically via systemd OnFailure= if any daily job fails. +# Enable one or more methods below. + +# ntfy — push notifications, zero signup required for self-hosted: +# https://docs.ntfy.sh/install/ +# or use the free cloud tier at ntfy.sh (pick any topic name) NTFY_ENABLED=false NTFY_URL=https://ntfy.sh/your-topic-here -# Email: requires the 'mail' command (mailutils / s-nail). +# Email — requires the 'mail' command on the system (package: mailutils or s-nail). +# For outbound SMTP configure /etc/ssmtp/ssmtp.conf or msmtp. EMAIL_ENABLED=false EMAIL_TO=you@example.com EMAIL_FROM=skycam@localhost -# Mattermost text post (can be same or different channel from daily upload). +# Mattermost text post — reuses the URL and token above, but posts to this +# channel (can be the same daily channel or a separate private/admin channel). MM_NOTIFY_ENABLED=false MM_NOTIFY_CHANNEL_ID=your-notify-channel-id-here diff --git a/sunrise.py b/sunrise.py index 4841655..2646b44 100644 --- a/sunrise.py +++ b/sunrise.py @@ -1,47 +1,30 @@ -#from suntime import Sun -#from datetime import datetime, timedelta -#import pytz - -#latitude = YOUR_LATITUDE -#longitude = YOUR_LONGITUDE - - - - - -#sun = Sun(latitude, longitude) -#tz = pytz.timezone(timezone) - -# Get yesterday's date and time -#yesterday = datetime.now(tz) - timedelta(days=1) -#sunrise_time = sun.get_sunrise_time(yesterday).astimezone(tz) - -# Print the sunrise time -#print(sunrise_time.strftime('%Y-%m-%d %H:%M:%S')) - - - -from datetime import datetime, timedelta +from datetime import datetime +import pathlib import pytz from suntime import Sun -# Example coordinates (e.g., New York City) -latitude = re.dacted -longitude = re.dacted +# Locate sky-cam.conf next to this script (works regardless of cwd). +_here = pathlib.Path(__file__).resolve().parent +def _read_conf(path): + conf = {} + with open(path) as f: + for line in f: + line = line.strip() + if line and not line.startswith('#') and '=' in line: + k, v = line.split('=', 1) + conf[k.strip()] = v.strip() + return conf -# Define the timezone -tz = pytz.timezone('America/New_York') +conf = _read_conf(_here / 'sky-cam.conf') + +latitude = float(conf['LATITUDE']) +longitude = float(conf['LONGITUDE']) +tz = pytz.timezone(conf['TIMEZONE']) -# Create a Sun object with the given latitude and longitude sun = Sun(latitude, longitude) - -# Get today's date and time in the desired timezone now = datetime.now(tz) - -# Get today's sunrise time in UTC and convert it to the desired timezone sunrise_utc = sun.get_sunrise_time(now) sunrise_time = sunrise_utc.astimezone(tz) -# Print the sunrise time in the desired format print(sunrise_time.strftime('%Y-%m-%d %H:%M:%S'))