diff --git a/.gitignore b/.gitignore index c18dd8d..00d2826 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ __pycache__/ +.env +sunrise-sounds/ diff --git a/4-seasons.sh b/4-seasons.sh index 213361c..03c825e 100755 --- a/4-seasons.sh +++ b/4-seasons.sh @@ -8,7 +8,6 @@ # Run once per day from cron (e.g. 01:00 daily). set -euo pipefail -set -x SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" @@ -27,7 +26,11 @@ yesterday=$(date --date="yesterday" +%Y-%m-%d) echo "Processing: $yesterday" # ── Season / movement info from astronomical calculation ────────────────────── -eval "$(python3 "$SCRIPT_DIR/season_info.py" "$yesterday")" +_season_info="$(python3 "$SCRIPT_DIR/season_info.py" "$yesterday")" || { + echo "Error: season_info.py failed — check Python dependencies (suntime pytz)" + exit 1 +} +eval "$_season_info" # Provides: SEASON MVT_NUM DAY_OF_MVT DAYS_IN_MVT MVT_START MVT_END # IS_LAST_DAY ASTRO_YEAR echo "Season=$SEASON Mvt=$MVT_NUM Year=$ASTRO_YEAR Day=$DAY_OF_MVT/$DAYS_IN_MVT LastDay=$IS_LAST_DAY" diff --git a/README.md b/README.md index 2e98066..006c5b1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ Automated sky / timelapse camera scripts that produce: | Dependency | Notes | |---|---| | `ffmpeg` + `ffprobe` | Encoding, capture, audio recording, duration probing | -| `python3` | `ephem` package for sunrise calculation, `requests` for Mattermost upload | +| `python3` | `suntime pytz` for sunrise calculation, `requests` for Mattermost upload — `pip3 install suntime pytz requests` | +| `fonts-dejavu` | DejaVu fonts for text overlays — `sudo apt install fonts-dejavu` | | `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 | @@ -22,7 +23,7 @@ Automated sky / timelapse camera scripts that produce: Install Python dependencies: ```bash -pip3 install ephem requests +pip3 install suntime pytz requests ``` --- diff --git a/bootstrap.sh b/bootstrap.sh index 1f63b7a..8bb45ea 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -20,12 +20,19 @@ mv "sky-cam-$BRANCH" "$TARGET" echo "" echo "Done. Next steps:" -echo " 1. Edit $TARGET/sky-cam.conf" +echo " 1. Install system packages (if not already present):" +echo " sudo apt install ffmpeg bc fonts-dejavu" +echo " pip3 install suntime pytz requests" +echo "" +echo " 2. Edit $TARGET/sky-cam.conf" echo " — SCRIPT_DIR full path to the directory you'll run scripts from" echo " — BASE_DIR where your camera images live" echo " — MUSIC_DIR path to your Vivaldi Four Seasons audio files" -echo " — CAM_NAME camera subfolder name (e.g. sunrise)" +echo " — CAMERAS list of camera names, e.g. (sunrise)" +echo " — SUNRISE_CAM which camera faces east (gets the sunrise video job)" echo " — LATITUDE / LONGITUDE / TIMEZONE your location" -echo " — Mattermost credentials (mattermost_url, access_token, channel_id)" echo "" -echo " 2. cd $TARGET && ./install.sh" +echo " 3. cd $TARGET && ./install.sh" +echo " This generates .env.example — copy it to .env and add credentials:" +echo " cp $TARGET/.env.example $TARGET/.env" +echo " \$EDITOR $TARGET/.env" diff --git a/capture.sh b/capture.sh index c447971..a85e007 100755 --- a/capture.sh +++ b/capture.sh @@ -17,7 +17,7 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" export TZ="$TIMEZONE" -CAM="${1:-$CAM_NAME}" +CAM="${1:-$SUNRISE_CAM}" rtsp_var="CAM_RTSP_${CAM}" RTSP_URL="${!rtsp_var:-}" diff --git a/daily_sunrise_video.sh b/daily_sunrise_video.sh index cba0e87..a259fc1 100644 --- a/daily_sunrise_video.sh +++ b/daily_sunrise_video.sh @@ -17,12 +17,14 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" export TZ="$TIMEZONE" +CAM_NAME="${1:-$SUNRISE_CAM}" + # ── Date / paths ────────────────────────────────────────────────────────────── current_date=$(date +%Y-%m-%d) output_date=$(date +%Y-%m) image_dir="$BASE_DIR/$CAM_NAME/$current_date" -output_dir="$BASE_DIR/movies/$CAM_NAME/$output_date/sunrise-only" +output_dir="$MOVIES_DIR/$CAM_NAME/$output_date/sunrise-only" mkdir -p "$output_dir" # ── Sunrise time ────────────────────────────────────────────────────────────── diff --git a/install.sh b/install.sh index a687a91..b343297 100755 --- a/install.sh +++ b/install.sh @@ -86,7 +86,7 @@ OnSuccess=sky-cam-sunrise-upload.service [Service] Type=oneshot -ExecStart=$SCRIPT_DIR/daily_sunrise_video.sh +ExecStart=$SCRIPT_DIR/daily_sunrise_video.sh $SUNRISE_CAM StandardOutput=journal StandardError=journal EOF @@ -186,6 +186,29 @@ for cam in "${CAMERAS[@]}"; do fi done +# ── Generate .env.example ──────────────────────────────────────────────────── +env_example="$HERE/.env.example" +{ + echo "# sky-cam credentials — copy to .env and fill in real values." + echo "# .env is gitignored and never checked in." + echo "" + echo "# RTSP stream URLs — one line per camera (variable name = CAM_RTSP_)" + for cam in "${CAMERAS[@]}"; do + echo "CAM_RTSP_${cam}=rtsp://admin:PASSWORD@192.168.1.XXX:554/stream1" + done + echo "" + echo "# Mattermost upload" + echo "mattermost_url=https://your-mattermost-server.example.com" + echo "access_token=your-access-token-here" + echo "channel_id=your-daily-upload-channel-id-here" + echo "" + echo "# Notifications (uncomment whichever you use)" + echo "#NTFY_URL=https://ntfy.sh/your-topic" + echo "#EMAIL_TO=you@example.com" + echo "#MM_NOTIFY_CHANNEL_ID=your-notify-channel-id-here" +} > "$env_example" +echo " wrote .env.example (cp .env.example .env then fill in real values)" + # ── Reload and enable ───────────────────────────────────────────────────────── echo "" $SC daemon-reload diff --git a/montage-mvt.sh b/montage-mvt.sh index d55b10c..9fc08a1 100755 --- a/montage-mvt.sh +++ b/montage-mvt.sh @@ -39,7 +39,11 @@ ATTR_FADE="$MONTAGE_ATTR_FADE" # ── Season / movement info ──────────────────────────────────────────────────── DATE_ARG="${1:-}" -eval "$(python3 "$SCRIPT_DIR/season_info.py" ${DATE_ARG:+"$DATE_ARG"})" +_season_info="$(python3 "$SCRIPT_DIR/season_info.py" ${DATE_ARG:+"$DATE_ARG"})" || { + echo "Error: season_info.py failed — check Python dependencies (suntime pytz)" + exit 1 +} +eval "$_season_info" # Provides: SEASON MVT_NUM DAY_OF_MVT DAYS_IN_MVT MVT_START MVT_END # IS_LAST_DAY ASTRO_YEAR echo "Season=$SEASON Mvt=$MVT_NUM Year=$ASTRO_YEAR (ref: ${DATE_ARG:-today})" diff --git a/sky-cam.conf b/sky-cam.conf index 1c4e8a3..fe6a7b6 100644 --- a/sky-cam.conf +++ b/sky-cam.conf @@ -245,8 +245,7 @@ MONTAGE_ATTR_FADE=1 # attribution fade-in and fade-out length (seconds) # Find the URL in your camera's web UI (usually under Network → Video → RTSP) # or check your camera's manual. # -#CAM_RTSP_sunrise=rtsp://admin:password@192.168.1.100:554/stream1 -#CAM_RTSP_north=rtsp://admin:password@192.168.1.101:554/stream1 +# CAM_RTSP_ goes in .env (see bottom of this file), not here. # Seconds between captured frames (applies to all cameras). # 10 = one frame every 10 s → 8640 frames/day → good timelapse density. @@ -263,9 +262,7 @@ AUDIO_PRE_BUFFER_MIN=2 # extra minutes to start before the capture window CAPTURE_AUDIO_BITRATE=96k # bitrate for the sunrise audio recording # ── 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 +# mattermost_url, access_token, channel_id go in .env (see bottom of this file). # ── Notifications — montage / year-end complete + any job failure ───────────── # Notifications fire when a movement montage or the year video finishes, @@ -288,3 +285,18 @@ channel_id=your-daily-upload-channel-id-here # Can be the same daily channel or a separate admin/private channel. #MM_NOTIFY_ENABLED=true #MM_NOTIFY_CHANNEL_ID=your-notify-channel-id-here + +# ── Credentials (.env override) ─────────────────────────────────────────────── +# Sensitive values (RTSP URLs with passwords, API tokens) can be kept out of +# this file — and out of git — by placing them in a .env file in the same +# directory. install.sh generates .env.example showing exactly which variables +# to define for your camera list. +# +# To use: +# cp .env.example .env +# $EDITOR .env ← fill in real values +# (never commit .env) +# +_conf_dir="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" +[ -f "$_conf_dir/.env" ] && source "$_conf_dir/.env" +unset _conf_dir diff --git a/sunrise.py b/sunrise.py index 2646b44..5d2fbc1 100644 --- a/sunrise.py +++ b/sunrise.py @@ -18,6 +18,10 @@ def _read_conf(path): conf = _read_conf(_here / 'sky-cam.conf') +for _key in ('LATITUDE', 'LONGITUDE', 'TIMEZONE'): + if _key not in conf: + raise SystemExit(f"sunrise.py: {_key} not set in sky-cam.conf") + latitude = float(conf['LATITUDE']) longitude = float(conf['LONGITUDE']) tz = pytz.timezone(conf['TIMEZONE']) diff --git a/sunrise2mm.py b/sunrise2mm.py index 5419fc1..af2357e 100755 --- a/sunrise2mm.py +++ b/sunrise2mm.py @@ -28,8 +28,11 @@ def read_config(config_file_path): exit(1) return config -# Read configuration from the file +# Read configuration — sky-cam.conf first, then .env overrides (credentials) config = read_config(config_file_path) +_env_path = str(_here / '.env') +if os.path.isfile(_env_path): + config.update(read_config(_env_path)) # Get the values from the configuration file mattermost_url = config.get('mattermost_url') @@ -47,7 +50,7 @@ today_date_str = today.strftime("%Y-%m-%d") # e.g., "2024-11-05" today_month_str = today.strftime("%Y-%m") # e.g., "2024-11" # Set the full folder path for today's video -base_video_folder = os.path.join(config.get('MOVIES_DIR', ''), config.get('CAM_NAME', 'sunrise')) +base_video_folder = os.path.join(config.get('MOVIES_DIR', ''), config.get('SUNRISE_CAM', 'sunrise')) output_dir = os.path.join(base_video_folder, today_month_str, "sunrise-only") # Check if the directory exists