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
This commit is contained in:
Claude
2026-04-30 11:40:11 +00:00
parent 499bafdcc2
commit 5fb7ff09a3
10 changed files with 1484 additions and 0 deletions
+66
View File
@@ -225,6 +225,72 @@ EOF
done
fi
# ── Moon jobs: nightly tracker + monthly full-moon composite ────────────────
# Both jobs target the SUNRISE_CAM (the east-facing camera). They share three
# Python helpers (moon_phase.py, moon_detect.py, moon_composite.py) and one
# downloaded asset (the lunar reference texture).
if [ "${MOON_TRACK_ENABLED:-true}" = "true" ] || [ "${MOON_FULL_ENABLED:-true}" = "true" ]; then
# Pre-download skyfield ephemeris so first run doesn't hit the network at
# an inopportune moment. Stored next to the scripts.
if [ ! -f "$HERE/de421.bsp" ]; then
echo "Downloading skyfield ephemeris (de421.bsp, ~17 MB)..."
if curl -fsSL -o "$HERE/de421.bsp" \
"https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/de421.bsp"; then
echo " ok"
else
echo " WARNING: ephemeris download failed — moon jobs will retry on first run"
rm -f "$HERE/de421.bsp"
fi
fi
# Lunar reference texture for moon_phase_monthly.py. Default URL is the
# Wikipedia "FullMoon2010" by Gregory H. Revera (CC BY-SA 3.0), 3500×3500.
# Override MOON_REFERENCE_URL in .env to use a different source — any
# high-res photo of a full moon on a black background works.
ref_dir="${MOON_REFERENCE_DIR:-$HERE/moon-ref}"
ref_path="${MOON_REFERENCE_PATH:-$ref_dir/full-moon.jpg}"
ref_url="${MOON_REFERENCE_URL:-https://upload.wikimedia.org/wikipedia/commons/e/e1/FullMoon2010.jpg}"
if [ ! -f "$ref_path" ]; then
mkdir -p "$(dirname "$ref_path")"
echo "Downloading lunar reference texture..."
if curl -fsSL -o "$ref_path" "$ref_url"; then
echo " ok → $ref_path"
else
echo " WARNING: lunar reference download failed"
echo " Drop a high-res full-moon JPEG at $ref_path manually,"
echo " or set MOON_REFERENCE_URL in .env and re-run install.sh."
rm -f "$ref_path"
fi
fi
fi
# Nightly moon-track job — runs on the SUNRISE_CAM only.
if [ "${MOON_TRACK_ENABLED:-true}" = "true" ]; then
write_service "sky-cam-moon-track" \
"$SUNRISE_CAM: nightly moon tracking timelapse" \
"moon-track.sh $SUNRISE_CAM"
write_timer "sky-cam-moon-track" \
"$SUNRISE_CAM: nightly moon tracking timelapse" \
"${SCHEDULE_MOON_TRACK:-02:30:00}"
timers+=("sky-cam-moon-track")
fi
# Moon-phase composite job — runs daily; the Python helper internally checks
# full / first-quarter / third-quarter and only acts when today is the
# matching post-day for one of them. One timer covers all three phases.
if [ "${MOON_FULL_ENABLED:-true}" = "true" ] \
|| [ "${MOON_FIRST_QUARTER_ENABLED:-true}" = "true" ] \
|| [ "${MOON_THIRD_QUARTER_ENABLED:-true}" = "true" ]; then
write_service "sky-cam-moon-phase" \
"$SUNRISE_CAM: monthly moon-phase composite (full + quarters)" \
"moon-phase-monthly.sh" \
$'After=network-online.target\nWants=network-online.target\n'
write_timer "sky-cam-moon-phase" \
"$SUNRISE_CAM: monthly moon-phase composite" \
"${SCHEDULE_MOON_PHASE:-09:30:00}"
timers+=("sky-cam-moon-phase")
fi
# ── Per-camera Four Seasons jobs ─────────────────────────────────────────────
# Reads CAMERAS and SCHEDULE_SEASONS_<cam> from sky-cam.conf.
# Script receives the camera name as $1 so it knows which camera to process.