#!/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