Merge pull request #11 from outis1one/claude/seasonal-sunrise-montage-nn268
Claude/seasonal sunrise montage nn268
This commit is contained in:
@@ -1 +1,3 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
|
.env
|
||||||
|
sunrise-sounds/
|
||||||
|
|||||||
+5
-2
@@ -8,7 +8,6 @@
|
|||||||
# Run once per day from cron (e.g. 01:00 daily).
|
# Run once per day from cron (e.g. 01:00 daily).
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
set -x
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
|
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
|
||||||
source "$SCRIPT_DIR/sky-cam.conf"
|
source "$SCRIPT_DIR/sky-cam.conf"
|
||||||
@@ -27,7 +26,11 @@ yesterday=$(date --date="yesterday" +%Y-%m-%d)
|
|||||||
echo "Processing: $yesterday"
|
echo "Processing: $yesterday"
|
||||||
|
|
||||||
# ── Season / movement info from astronomical calculation ──────────────────────
|
# ── 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
|
# Provides: SEASON MVT_NUM DAY_OF_MVT DAYS_IN_MVT MVT_START MVT_END
|
||||||
# IS_LAST_DAY ASTRO_YEAR
|
# 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"
|
echo "Season=$SEASON Mvt=$MVT_NUM Year=$ASTRO_YEAR Day=$DAY_OF_MVT/$DAYS_IN_MVT LastDay=$IS_LAST_DAY"
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ Automated sky / timelapse camera scripts that produce:
|
|||||||
| Dependency | Notes |
|
| Dependency | Notes |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `ffmpeg` + `ffprobe` | Encoding, capture, audio recording, duration probing |
|
| `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) |
|
| `bc` | Shell arithmetic (floating-point speed factors) |
|
||||||
| `fontconfig` (`fc-match`) | Font detection for overlays — optional, falls back to hardcoded paths |
|
| `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 |
|
| 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:
|
Install Python dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip3 install ephem requests
|
pip3 install suntime pytz requests
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+11
-4
@@ -20,12 +20,19 @@ mv "sky-cam-$BRANCH" "$TARGET"
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Done. Next steps:"
|
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 " — SCRIPT_DIR full path to the directory you'll run scripts from"
|
||||||
echo " — BASE_DIR where your camera images live"
|
echo " — BASE_DIR where your camera images live"
|
||||||
echo " — MUSIC_DIR path to your Vivaldi Four Seasons audio files"
|
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 " — LATITUDE / LONGITUDE / TIMEZONE your location"
|
||||||
echo " — Mattermost credentials (mattermost_url, access_token, channel_id)"
|
|
||||||
echo ""
|
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"
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")"
|
|||||||
source "$SCRIPT_DIR/sky-cam.conf"
|
source "$SCRIPT_DIR/sky-cam.conf"
|
||||||
export TZ="$TIMEZONE"
|
export TZ="$TIMEZONE"
|
||||||
|
|
||||||
CAM="${1:-$CAM_NAME}"
|
CAM="${1:-$SUNRISE_CAM}"
|
||||||
|
|
||||||
rtsp_var="CAM_RTSP_${CAM}"
|
rtsp_var="CAM_RTSP_${CAM}"
|
||||||
RTSP_URL="${!rtsp_var:-}"
|
RTSP_URL="${!rtsp_var:-}"
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")"
|
|||||||
source "$SCRIPT_DIR/sky-cam.conf"
|
source "$SCRIPT_DIR/sky-cam.conf"
|
||||||
export TZ="$TIMEZONE"
|
export TZ="$TIMEZONE"
|
||||||
|
|
||||||
|
CAM_NAME="${1:-$SUNRISE_CAM}"
|
||||||
|
|
||||||
# ── Date / paths ──────────────────────────────────────────────────────────────
|
# ── Date / paths ──────────────────────────────────────────────────────────────
|
||||||
current_date=$(date +%Y-%m-%d)
|
current_date=$(date +%Y-%m-%d)
|
||||||
output_date=$(date +%Y-%m)
|
output_date=$(date +%Y-%m)
|
||||||
|
|
||||||
image_dir="$BASE_DIR/$CAM_NAME/$current_date"
|
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"
|
mkdir -p "$output_dir"
|
||||||
|
|
||||||
# ── Sunrise time ──────────────────────────────────────────────────────────────
|
# ── Sunrise time ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+24
-1
@@ -86,7 +86,7 @@ OnSuccess=sky-cam-sunrise-upload.service
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=$SCRIPT_DIR/daily_sunrise_video.sh
|
ExecStart=$SCRIPT_DIR/daily_sunrise_video.sh $SUNRISE_CAM
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
StandardError=journal
|
StandardError=journal
|
||||||
EOF
|
EOF
|
||||||
@@ -186,6 +186,29 @@ for cam in "${CAMERAS[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
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_<camname>)"
|
||||||
|
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 ─────────────────────────────────────────────────────────
|
# ── Reload and enable ─────────────────────────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
$SC daemon-reload
|
$SC daemon-reload
|
||||||
|
|||||||
+5
-1
@@ -39,7 +39,11 @@ ATTR_FADE="$MONTAGE_ATTR_FADE"
|
|||||||
|
|
||||||
# ── Season / movement info ────────────────────────────────────────────────────
|
# ── Season / movement info ────────────────────────────────────────────────────
|
||||||
DATE_ARG="${1:-}"
|
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
|
# Provides: SEASON MVT_NUM DAY_OF_MVT DAYS_IN_MVT MVT_START MVT_END
|
||||||
# IS_LAST_DAY ASTRO_YEAR
|
# IS_LAST_DAY ASTRO_YEAR
|
||||||
echo "Season=$SEASON Mvt=$MVT_NUM Year=$ASTRO_YEAR (ref: ${DATE_ARG:-today})"
|
echo "Season=$SEASON Mvt=$MVT_NUM Year=$ASTRO_YEAR (ref: ${DATE_ARG:-today})"
|
||||||
|
|||||||
+17
-5
@@ -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)
|
# Find the URL in your camera's web UI (usually under Network → Video → RTSP)
|
||||||
# or check your camera's manual.
|
# or check your camera's manual.
|
||||||
#
|
#
|
||||||
#CAM_RTSP_sunrise=rtsp://admin:password@192.168.1.100:554/stream1
|
# CAM_RTSP_<cam> goes in .env (see bottom of this file), not here.
|
||||||
#CAM_RTSP_north=rtsp://admin:password@192.168.1.101:554/stream1
|
|
||||||
|
|
||||||
# Seconds between captured frames (applies to all cameras).
|
# Seconds between captured frames (applies to all cameras).
|
||||||
# 10 = one frame every 10 s → 8640 frames/day → good timelapse density.
|
# 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
|
CAPTURE_AUDIO_BITRATE=96k # bitrate for the sunrise audio recording
|
||||||
|
|
||||||
# ── Mattermost — daily sunrise upload ─────────────────────────────────────────
|
# ── Mattermost — daily sunrise upload ─────────────────────────────────────────
|
||||||
mattermost_url=https://your-mattermost-server.example.com
|
# mattermost_url, access_token, channel_id go in .env (see bottom of this file).
|
||||||
access_token=your-access-token-here
|
|
||||||
channel_id=your-daily-upload-channel-id-here
|
|
||||||
|
|
||||||
# ── Notifications — montage / year-end complete + any job failure ─────────────
|
# ── Notifications — montage / year-end complete + any job failure ─────────────
|
||||||
# Notifications fire when a movement montage or the year video finishes,
|
# 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.
|
# Can be the same daily channel or a separate admin/private channel.
|
||||||
#MM_NOTIFY_ENABLED=true
|
#MM_NOTIFY_ENABLED=true
|
||||||
#MM_NOTIFY_CHANNEL_ID=your-notify-channel-id-here
|
#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
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ def _read_conf(path):
|
|||||||
|
|
||||||
conf = _read_conf(_here / 'sky-cam.conf')
|
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'])
|
latitude = float(conf['LATITUDE'])
|
||||||
longitude = float(conf['LONGITUDE'])
|
longitude = float(conf['LONGITUDE'])
|
||||||
tz = pytz.timezone(conf['TIMEZONE'])
|
tz = pytz.timezone(conf['TIMEZONE'])
|
||||||
|
|||||||
+5
-2
@@ -28,8 +28,11 @@ def read_config(config_file_path):
|
|||||||
exit(1)
|
exit(1)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
# Read configuration from the file
|
# Read configuration — sky-cam.conf first, then .env overrides (credentials)
|
||||||
config = read_config(config_file_path)
|
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
|
# Get the values from the configuration file
|
||||||
mattermost_url = config.get('mattermost_url')
|
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"
|
today_month_str = today.strftime("%Y-%m") # e.g., "2024-11"
|
||||||
|
|
||||||
# Set the full folder path for today's video
|
# 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")
|
output_dir = os.path.join(base_video_folder, today_month_str, "sunrise-only")
|
||||||
|
|
||||||
# Check if the directory exists
|
# Check if the directory exists
|
||||||
|
|||||||
Reference in New Issue
Block a user