Auto-detect SCRIPT_DIR/BASE_DIR; sunrise waits for capture window; optional overlay
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
This commit is contained in:
+56
-32
@@ -48,6 +48,17 @@ start_sec=$((sunrise_sec - SUNRISE_PRE_MIN * 60))
|
||||
end_sec=$((sunrise_sec + SUNRISE_POST_MIN * 60))
|
||||
echo "Window: $((start_sec/3600)):$(printf '%02d' $(((start_sec%3600)/60))) → $((end_sec/3600)):$(printf '%02d' $(((end_sec%3600)/60)))"
|
||||
|
||||
# ── Wait for capture window to finish ────────────────────────────────────────
|
||||
# The timer fires early (default 03:00); we sleep internally so images from
|
||||
# the full window are available before we start encoding.
|
||||
midnight=$(date -d "today 00:00:00" +%s)
|
||||
now_day_sec=$(( $(date +%s) - midnight ))
|
||||
if [ "$now_day_sec" -lt "$end_sec" ]; then
|
||||
wait_sec=$(( end_sec - now_day_sec + 30 ))
|
||||
echo "Waiting ${wait_sec}s for capture window to finish (ends $(date -d "@$(( midnight + end_sec ))" '+%H:%M:%S'))..."
|
||||
sleep "$wait_sec"
|
||||
fi
|
||||
|
||||
# ── Collect images in window ──────────────────────────────────────────────────
|
||||
if [ ! -d "$image_dir" ]; then
|
||||
"$SCRIPT_DIR/notify.sh" "FAILED: sunrise $current_date" \
|
||||
@@ -126,21 +137,20 @@ fi
|
||||
sped_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$sped_video")
|
||||
echo "Speed-adjusted: ${sped_dur}s (saved — overlay still pending)"
|
||||
|
||||
# ── Build overlay filter ──────────────────────────────────────────────────────
|
||||
# Time displayed as stacked characters down the right side (e.g. 0/7/:/2/3),
|
||||
# semi-transparent with a soft drop shadow so it reads on any background.
|
||||
# ── Build overlay filter (skipped if SUNRISE_OVERLAY_ENABLED=false) ──────────
|
||||
SR_TIME=$(echo "$sunrise_time_local" | cut -d'-' -f1,2 | tr '-' ':')
|
||||
# Build "H\nH\n:\nM\nM" for vertical stacking in drawtext
|
||||
SR_VERT=$(echo "$SR_TIME" | awk 'BEGIN{FS=""}{for(i=1;i<=NF;i++){printf "%s",$i; if(i<NF)printf "\\n"}}')
|
||||
|
||||
DT="drawtext"
|
||||
[ -n "$FONT" ] && DT="${DT}=fontfile='${FONT}'" || DT="${DT}"
|
||||
DT="${DT}:text='${SR_VERT}'"
|
||||
DT="${DT}:fontcolor=white@${SUNRISE_OVERLAY_OPACITY}"
|
||||
DT="${DT}:fontsize=h/22"
|
||||
DT="${DT}:line_spacing=4"
|
||||
DT="${DT}:x=w-tw-18:y=(h-th)/2"
|
||||
DT="${DT}:shadowcolor=black@0.55:shadowx=1:shadowy=1"
|
||||
DT=""
|
||||
if [ "${SUNRISE_OVERLAY_ENABLED:-true}" = "true" ]; then
|
||||
SR_VERT=$(echo "$SR_TIME" | awk 'BEGIN{FS=""}{for(i=1;i<=NF;i++){printf "%s",$i; if(i<NF)printf "\\n"}}')
|
||||
DT="drawtext"
|
||||
[ -n "$FONT" ] && DT="${DT}=fontfile='${FONT}'" || DT="${DT}"
|
||||
DT="${DT}:text='${SR_VERT}'"
|
||||
DT="${DT}:fontcolor=white@${SUNRISE_OVERLAY_OPACITY}"
|
||||
DT="${DT}:fontsize=h/22"
|
||||
DT="${DT}:line_spacing=4"
|
||||
DT="${DT}:x=w-tw-18:y=(h-th)/2"
|
||||
DT="${DT}:shadowcolor=black@0.55:shadowx=1:shadowy=1"
|
||||
fi
|
||||
|
||||
# ── Step 3: Audio mix + overlay — each independently optional/fallible ────────
|
||||
# Step 3a: mixes audio into a temp copy of the sped video (video stream is
|
||||
@@ -203,29 +213,43 @@ if [ -n "$audio_src" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Step 3b: burn overlay ─────────────────────────────────────────────────────
|
||||
echo "Step 3b/3: overlay → $final_video"
|
||||
# ── Step 3b: burn overlay (or promote work_video directly if disabled) ────────
|
||||
if $has_audio; then audio_out_flags=(-c:a copy); else audio_out_flags=(-an); fi
|
||||
|
||||
if ffmpeg -loglevel warning \
|
||||
-i "$work_video" \
|
||||
-vf "${DT}" \
|
||||
-c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" \
|
||||
"${audio_out_flags[@]}" \
|
||||
-y "$final_video"; then
|
||||
actual_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$final_video")
|
||||
echo "Done: $final_video (${actual_dur}s, sunrise at ${SR_TIME})"
|
||||
rm -f "$sped_video" "$temp_audio"; temp_audio=""
|
||||
[ -f "$cam_audio" ] && rm -f "$cam_audio"
|
||||
"$SCRIPT_DIR/notify.sh" "Sunrise ready: $current_date" \
|
||||
"$(basename "$final_video") — ${actual_dur}s, sunrise at ${SR_TIME}" || true
|
||||
step3b_ok=false
|
||||
if [ -n "$DT" ]; then
|
||||
echo "Step 3b/3: overlay → $final_video"
|
||||
if ffmpeg -loglevel warning \
|
||||
-i "$work_video" \
|
||||
-vf "${DT}" \
|
||||
-c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" \
|
||||
"${audio_out_flags[@]}" \
|
||||
-y "$final_video"; then
|
||||
step3b_ok=true
|
||||
else
|
||||
if $has_audio; then promote_label="audio-mixed"; else promote_label="speed-only"; fi
|
||||
mv "$work_video" "$final_video"
|
||||
[ "$work_video" != "$sped_video" ] && rm -f "$sped_video"
|
||||
temp_audio=""
|
||||
[ -f "$cam_audio" ] && rm -f "$cam_audio"
|
||||
echo "Overlay failed — promoting ${promote_label} video as upload target"
|
||||
"$SCRIPT_DIR/notify.sh" "WARNING: sunrise overlay failed $current_date" \
|
||||
"Overlay failed — uploading ${promote_label} video (no timestamp)" || true
|
||||
step3b_ok=true # degraded but recoverable — upload still fires
|
||||
fi
|
||||
else
|
||||
if $has_audio; then promote_label="audio-mixed"; else promote_label="speed-only"; fi
|
||||
echo "Step 3b/3: overlay disabled — promoting as final"
|
||||
mv "$work_video" "$final_video"
|
||||
[ "$work_video" != "$sped_video" ] && rm -f "$sped_video"
|
||||
temp_audio=""
|
||||
step3b_ok=true
|
||||
fi
|
||||
|
||||
if $step3b_ok; then
|
||||
actual_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$final_video")
|
||||
echo "Done: $final_video (${actual_dur}s, sunrise at ${SR_TIME})"
|
||||
rm -f "$sped_video" "$temp_audio" 2>/dev/null || true; temp_audio=""
|
||||
[ -f "$cam_audio" ] && rm -f "$cam_audio"
|
||||
echo "Overlay failed — promoting ${promote_label} video as upload target"
|
||||
"$SCRIPT_DIR/notify.sh" "WARNING: sunrise overlay failed $current_date" \
|
||||
"Overlay failed — uploading ${promote_label} video (no timestamp)" || true
|
||||
"$SCRIPT_DIR/notify.sh" "Sunrise ready: $current_date" \
|
||||
"$(basename "$final_video") — ${actual_dur}s, sunrise at ${SR_TIME}" || true
|
||||
fi
|
||||
|
||||
+40
-15
@@ -147,12 +147,17 @@
|
||||
# =============================================================================
|
||||
|
||||
# ── Install location ──────────────────────────────────────────────────────────
|
||||
# Full path to the directory containing this file and all the scripts.
|
||||
SCRIPT_DIR=/home/motion/drives/local-2tb/sunrise-scripts
|
||||
# Auto-detected from where this file lives — override only if you move the
|
||||
# scripts to a different location after install.
|
||||
SCRIPT_DIR="${SCRIPT_DIR:-$(dirname "$(realpath "${BASH_SOURCE[0]}")")}"
|
||||
|
||||
# ── Storage ───────────────────────────────────────────────────────────────────
|
||||
BASE_DIR=/home/motion/drives/local-2tb
|
||||
MOVIES_DIR=$BASE_DIR/movies # override if videos live on a different drive
|
||||
# BASE_DIR: root folder where camera images are stored.
|
||||
# Structure created automatically: BASE_DIR/<cam>/YYYY-MM-DD/HH-MM-SS.jpg
|
||||
# Defaults to a 'data' subfolder next to the scripts; override to point at
|
||||
# an external drive or network mount (recommended for production).
|
||||
BASE_DIR="${BASE_DIR:-$SCRIPT_DIR/data}"
|
||||
MOVIES_DIR="${MOVIES_DIR:-$BASE_DIR/movies}" # override if videos live on a different drive
|
||||
MUSIC_DIR=/home/motion/drives/local-2tb/music/4 Seasons
|
||||
|
||||
# ── Location & timezone ───────────────────────────────────────────────────────
|
||||
@@ -179,14 +184,28 @@ CAMERAS=(sunrise) # e.g. CAMERAS=(sunrise north west)
|
||||
SUNRISE_CAM=sunrise # which camera faces east (gets the sunrise video job)
|
||||
|
||||
# ── Schedules ─────────────────────────────────────────────────────────────────
|
||||
# SCHEDULE_SUNRISE when to run daily_sunrise_video.sh (SUNRISE_CAM only)
|
||||
# SCHEDULE_SEASONS_<cam> when to run 4-seasons.sh for each camera
|
||||
# SCHEDULE_FULLDAY_<cam> when to run fullday-video.sh for each camera
|
||||
#
|
||||
# Times are HH:MM:SS (24-hour). Space cameras at least 30 min apart.
|
||||
# All times are HH:MM:SS (24-hour local time).
|
||||
# install.sh reads these and generates one systemd timer per camera per job.
|
||||
#
|
||||
SCHEDULE_SUNRISE=09:00:00
|
||||
# SCHEDULE_SUNRISE
|
||||
# When to START the sunrise video job. The script wakes up, calculates
|
||||
# today's sunrise time, then WAITS internally until the capture window is
|
||||
# fully finished before touching any images. Set this early enough to
|
||||
# cover your earliest possible sunrise minus SUNRISE_PRE_MIN — 03:00 works
|
||||
# year-round for most locations. The video is ready a few minutes after the
|
||||
# capture window closes, not at this start time.
|
||||
#
|
||||
# SCHEDULE_SEASONS_<cam> (one per camera in CAMERAS)
|
||||
# When to run the Four Seasons daily clip job. Processes YESTERDAY'S images
|
||||
# (not today's) — always runs after midnight. On the last day of a season
|
||||
# movement this automatically triggers the movement montage build.
|
||||
# Space multiple cameras at least 30 min apart to avoid disk contention.
|
||||
#
|
||||
# SCHEDULE_FULLDAY_<cam> (one per camera in CAMERAS)
|
||||
# When to run the full-day timelapse job. Also processes YESTERDAY'S images.
|
||||
# Space after SCHEDULE_SEASONS to avoid overlap.
|
||||
#
|
||||
SCHEDULE_SUNRISE=03:00:00
|
||||
|
||||
SCHEDULE_SEASONS_sunrise=01:00:00
|
||||
SCHEDULE_FULLDAY_sunrise=02:00:00
|
||||
@@ -199,12 +218,18 @@ SCHEDULE_FULLDAY_sunrise=02:00:00
|
||||
#SCHEDULE_FULLDAY_west=03:00:00
|
||||
|
||||
# ── Sunrise video tuning ──────────────────────────────────────────────────────
|
||||
# How many minutes before/after sunrise to include in the daily clip.
|
||||
SUNRISE_PRE_MIN=70
|
||||
SUNRISE_POST_MIN=10
|
||||
# Target duration (seconds) for the speed-adjusted sunrise video.
|
||||
# Capture window around sunrise:
|
||||
# Images from (sunrise - SUNRISE_PRE_MIN) to (sunrise + SUNRISE_POST_MIN)
|
||||
# are included in the clip. Larger windows give more context; they are all
|
||||
# speed-adjusted to fit SUNRISE_TARGET_SECS regardless of window size.
|
||||
SUNRISE_PRE_MIN=70 # minutes before sunrise to start capturing
|
||||
SUNRISE_POST_MIN=10 # minutes after sunrise to stop capturing
|
||||
# Target length of the final sunrise video (seconds).
|
||||
SUNRISE_TARGET_SECS=10
|
||||
# Opacity of the sunrise-time overlay (0.0 = invisible, 1.0 = fully opaque).
|
||||
# Burn the local sunrise time vertically on the right side of the video.
|
||||
# Set false to skip the overlay entirely (cleaner look, faster encoding).
|
||||
SUNRISE_OVERLAY_ENABLED=true
|
||||
# Opacity of the sunrise-time text (0.0 = invisible, 1.0 = fully opaque).
|
||||
SUNRISE_OVERLAY_OPACITY=0.45
|
||||
|
||||
# ── Full-day timelapse tuning ─────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user