- sunrise_video.10s.sh: use $TIMEZONE (was hardcoded America/New_York), $SUNRISE_PRE_MIN/$SUNRISE_POST_MIN for capture window, $SUNRISE_TARGET_SECS for output duration (was wrong value 8 in a script named 10s) - fullday-video.sh: remove hardcoded FULLDAY_FPS/RETENTION_DAYS; both now come from sky-cam.conf - 4-seasons.sh, montage-mvt.sh: export TIMEZONE after sourcing conf so season_info.py subprocess picks it up via env rather than falling back to its hardcoded America/New_York default - sky-cam.conf: add SUNRISE_PRE_MIN, SUNRISE_POST_MIN, SUNRISE_TARGET_SECS, FULLDAY_FPS, RETENTION_DAYS https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
107 lines
5.2 KiB
Bash
Executable File
107 lines
5.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# 4-seasons.sh — create one daily clip from yesterday's images, sized to its
|
|
# share of the corresponding Vivaldi movement's music duration.
|
|
#
|
|
# On the last day of a movement period, automatically runs montage-mvt.sh to
|
|
# compile the full movement montage.
|
|
#
|
|
# 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"
|
|
export TIMEZONE # make it visible to season_info.py subprocess
|
|
|
|
# ── Configuration ──────────────────────────────────────────────────────────────
|
|
base_dir="$BASE_DIR"
|
|
music_base_dir="$MUSIC_DIR"
|
|
[ ! -d "$music_base_dir" ] && music_base_dir="$SCRIPT_DIR/music"
|
|
|
|
# ── Date setup ────────────────────────────────────────────────────────────────
|
|
yesterday=$(date --date="yesterday" +%Y-%m-%d)
|
|
echo "Processing: $yesterday"
|
|
|
|
# ── Season / movement info from astronomical calculation ──────────────────────
|
|
eval "$(python3 "$SCRIPT_DIR/season_info.py" "$yesterday")"
|
|
# 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"
|
|
|
|
# ── Locate music file and get its duration ────────────────────────────────────
|
|
music_file=$(find "$music_base_dir" -type f -iname "*${SEASON}*Mvt*${MVT_NUM}*" | sort | head -n 1)
|
|
if [ -z "$music_file" ] || [ ! -f "$music_file" ]; then
|
|
echo "ERROR: Music file not found for $SEASON Mvt $MVT_NUM in $music_base_dir"
|
|
exit 1
|
|
fi
|
|
music_duration=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$music_file")
|
|
echo "Music: $(basename "$music_file") ($music_duration s)"
|
|
|
|
# Each daily clip covers music_duration / DAYS_IN_MVT seconds so that
|
|
# concatenating all clips for the movement sums to the exact music duration.
|
|
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 ─────────────────────────────────────────────────
|
|
image_dir="$base_dir/$CAM_NAME/$yesterday"
|
|
|
|
if [ ! -d "$image_dir" ]; then
|
|
echo "WARNING: Image directory $image_dir not found — skipping $yesterday."
|
|
exit 0
|
|
fi
|
|
|
|
total_images=$(find "$image_dir" -type f -name "*.jpg" | wc -l)
|
|
if [ "$total_images" -lt 1 ]; then
|
|
echo "WARNING: No images in $image_dir — skipping $yesterday."
|
|
exit 0
|
|
fi
|
|
echo "Images found: $total_images"
|
|
|
|
# ── Prepare output directory ──────────────────────────────────────────────────
|
|
output_dir="$base_dir/movies/$CAM_NAME/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM"
|
|
mkdir -p "$output_dir"
|
|
|
|
# ── Build sorted image list ───────────────────────────────────────────────────
|
|
temp_file=$(mktemp --suffix=.txt)
|
|
temp_video="$output_dir/${yesterday}_Mvt${MVT_NUM}-temp.mp4"
|
|
cleanup() { rm -f "$temp_file" "$temp_video" 2>/dev/null || true; }
|
|
trap cleanup EXIT
|
|
|
|
find "$image_dir" -type f -name "*.jpg" | sort | while read -r img; do
|
|
echo "file '$img'"
|
|
done > "$temp_file"
|
|
|
|
# ── Step 1: Build raw video at default frame rate ─────────────────────────────
|
|
echo "Step 1/2: encoding raw video..."
|
|
ffmpeg -loglevel warning \
|
|
-f concat -safe 0 -i "$temp_file" \
|
|
-c:v libx264 -pix_fmt yuv420p -crf 28 -vsync 2 -an \
|
|
-y "$temp_video"
|
|
|
|
raw_duration=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$temp_video")
|
|
echo "Raw duration: $raw_duration s"
|
|
|
|
# ── Step 2: Speed-adjust to target clip duration ──────────────────────────────
|
|
speed_factor=$(echo "scale=6; $raw_duration / $target_per_clip" | bc)
|
|
echo "Step 2/2: speed factor $speed_factor..."
|
|
|
|
final_file="$output_dir/${yesterday}_Mvt${MVT_NUM}-Day${DAY_OF_MVT}of${DAYS_IN_MVT}-final.mp4"
|
|
ffmpeg -loglevel warning \
|
|
-i "$temp_video" \
|
|
-vf "setpts=PTS/$speed_factor" \
|
|
-c:v libx264 -pix_fmt yuv420p -crf 26 \
|
|
-t "$target_per_clip" -an \
|
|
-y "$final_file"
|
|
|
|
adjusted=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$final_file")
|
|
echo "Daily clip: $final_file ($adjusted s)"
|
|
|
|
# ── Auto-trigger montage on last day of movement ──────────────────────────────
|
|
if [ "$IS_LAST_DAY" = "true" ]; then
|
|
echo "Last day of $SEASON Mvt $MVT_NUM — triggering montage compilation..."
|
|
# Pass $yesterday so montage-mvt.sh looks up the correct movement
|
|
# (running the next morning, "today" would already be the next movement).
|
|
"$SCRIPT_DIR/montage-mvt.sh" "$yesterday"
|
|
fi
|