#!/bin/bash # year-end-join.sh — concatenate all 12 movement montages for one astronomical # year into a single "Four Seasons" video. # # Called automatically by montage-mvt.sh after the last Autumn movement finishes. # Can also be run manually: ./year-end-join.sh 2025 # # The 12 individual movement videos already carry their own attribution overlay # (first 6 seconds of each), so no extra card is appended here. # # Output: $MOVIES_DIR/$CAM_NAME/$ASTRO_YEAR/$CAM_NAME-$ASTRO_YEAR.mp4 set -euo pipefail SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" ASTRO_YEAR="${1:-}" if [ -z "$ASTRO_YEAR" ]; then echo "Usage: $0 [CAM_NAME] (e.g. $0 2025 north)" exit 1 fi CAM_NAME="${2:-}" if [ -z "$CAM_NAME" ]; then echo "Usage: $0 (e.g. $0 2025 north)" exit 1 fi year_dir="$MOVIES_DIR/$CAM_NAME/$ASTRO_YEAR" echo "Assembling Four Seasons $ASTRO_YEAR from $year_dir ..." # ── Collect the 12 montage files in season/movement order ──────────────────── concat_list=$(mktemp --suffix=.txt) trap 'rm -f "$concat_list"' EXIT missing=0 total_expected_dur=0 for season in Winter Spring Summer Autumn; do for mvt in 1 2 3; do mvt_dir="$year_dir/$season/Mvt$mvt/montage" # Pick the newest (or only) montage file for this movement f=$(find "$mvt_dir" -maxdepth 1 -name "*-Montage.mp4" 2>/dev/null | sort | tail -1) if [ -z "$f" ]; then echo "WARNING: missing montage for $season Mvt$mvt ($mvt_dir)" missing=$((missing + 1)) else d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$f") total_expected_dur=$(echo "scale=3; $total_expected_dur + $d" | bc) echo " $season Mvt$mvt → $(basename "$f") (${d}s)" printf "file '%s'\n" "$f" >> "$concat_list" fi done done echo "Expected total duration: ${total_expected_dur}s ($(echo "scale=1; $total_expected_dur/60" | bc) min)" found=$(wc -l < "$concat_list") echo "$found of 12 movements found ($missing missing)" if [ "$found" -eq 0 ]; then echo "ERROR: no montage files found — aborting." exit 1 fi if [ "$missing" -gt 0 ]; then echo "WARNING: joining with $missing movement(s) absent." fi # ── Join ────────────────────────────────────────────────────────────────────── output_file="$year_dir/${CAM_NAME}-${ASTRO_YEAR}.mp4" echo "Output: $output_file" if ! ffmpeg -loglevel warning \ -f concat -safe 0 -i "$concat_list" \ -c copy -y "$output_file"; then "$SCRIPT_DIR/notify.sh" "FAILED: year-end join $CAM_NAME $ASTRO_YEAR" \ "ffmpeg concat failed — the 12 movement montages are still intact" || true exit 1 fi total_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$output_file") total_min=$(echo "scale=1; $total_dur / 60" | bc) echo "Done: $output_file (${total_min} min)" # Sanity-check actual vs expected duration dur_drift=$(echo "scale=3; $total_dur - $total_expected_dur" | bc | sed 's/^-//') if awk "BEGIN{exit !($dur_drift > 2.0)}"; then echo "WARNING: output ${total_dur}s differs from expected ${total_expected_dur}s by ${dur_drift}s" "$SCRIPT_DIR/notify.sh" "WARNING: year-end duration drift $CAM_NAME $ASTRO_YEAR" \ "Output ${total_dur}s vs expected ${total_expected_dur}s (drift ${dur_drift}s)" || true fi # ── Notify ──────────────────────────────────────────────────────────────────── "$SCRIPT_DIR/notify.sh" \ "Four Seasons $ASTRO_YEAR complete [$CAM_NAME]" \ "${CAM_NAME}-${ASTRO_YEAR}.mp4 — ${total_min} minutes ($found movements) | $output_file" \ || true