#!/bin/bash # make-finals.sh — one-off recovery script. # # Scans ALL *-temp.mp4 files under a backup root, infers the camera name from # the path, uses season_info.py to get the correct movement info for each # file's date, speed-adjusts to the right target duration, and writes # *-final.mp4 files into MOVIES_DIR (from sky-cam.conf / .env). # # Safe to run multiple times — skips any final that already exists. # Never modifies the backup. # # Usage: # ./make-finals.sh [--dry-run] # # Example: # ./make-finals.sh ~/drives/data2-main --dry-run # ./make-finals.sh ~/drives/data2-main set -euo pipefail SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" export TIMEZONE ENCODE_PRESET="${ENCODE_PRESET:-slow}" CRF_SEASONS_FINAL="${CRF_SEASONS_FINAL:-26}" BACKUP_ROOT="" DRY_RUN=false FILTER_CAM="" FILTER_SEASON="" FILTER_MVT="" while [[ $# -gt 0 ]]; do case "$1" in --dry-run) DRY_RUN=true; shift ;; --cam) FILTER_CAM="$2"; shift 2 ;; --season) FILTER_SEASON="$2"; shift 2 ;; --mvt) FILTER_MVT="$2"; shift 2 ;; -*) echo "Unknown option: $1"; exit 1 ;; *) BACKUP_ROOT="$1"; shift ;; esac done if [ -z "$BACKUP_ROOT" ]; then echo "Usage: $0 [--cam CAM] [--season SEASON] [--mvt N] [--dry-run]" echo " e.g. $0 ~/drives/data2-main --cam north --season Autumn --mvt 1" exit 1 fi BACKUP_ROOT="$(realpath "$BACKUP_ROOT")" music_base_dir="$MUSIC_DIR" [ ! -d "$music_base_dir" ] && music_base_dir="$SCRIPT_DIR/music" $DRY_RUN && echo "DRY RUN — no files will be written" echo "Scanning: $BACKUP_ROOT" echo "Output: $MOVIES_DIR" [ -n "$FILTER_CAM" ] && echo "Filter: cam=$FILTER_CAM" [ -n "$FILTER_SEASON" ] && echo "Filter: season=$FILTER_SEASON" [ -n "$FILTER_MVT" ] && echo "Filter: mvt=$FILTER_MVT" echo "" # Known camera names in order of specificity (longer matches first) KNOWN_CAMS=(sunrise north south east west) infer_cam() { local path="$1" for cam in "${KNOWN_CAMS[@]}"; do if echo "$path" | grep -qE "(^|/)${cam}(/|$)"; then echo "$cam" return fi done echo "" } processed=0; skipped=0; errors=0 while IFS= read -r -d '' temp_file; do filename="$(basename "$temp_file")" date_str="${filename%%_*}" if ! [[ "$date_str" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then echo "SKIP (bad name): $temp_file" (( skipped++ )) || true continue fi cam=$(infer_cam "${temp_file#$BACKUP_ROOT/}") if [ -z "$cam" ]; then echo "SKIP (unknown cam): $temp_file" (( skipped++ )) || true continue fi [ -n "$FILTER_CAM" ] && [ "$cam" != "$FILTER_CAM" ] && continue _info="$(python3 "$SCRIPT_DIR/season_info.py" "$date_str" 2>/dev/null)" || { echo "SKIP (season_info failed $date_str): $filename" (( skipped++ )) || true continue } eval "$_info" [ -n "$FILTER_SEASON" ] && [ "$SEASON" != "$FILTER_SEASON" ] && continue [ -n "$FILTER_MVT" ] && [ "$MVT_NUM" != "$FILTER_MVT" ] && continue music_file=$(find "$music_base_dir" -type f -iname "*${SEASON}*" \ | grep -iE "Mvt[^0-9]*${MVT_NUM}[^0-9]" | sort | head -n 1) if [ -z "$music_file" ]; then echo "SKIP (no music $SEASON Mvt$MVT_NUM): $filename" (( skipped++ )) || true continue fi music_duration=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$music_file") loops_var="MONTAGE_MUSIC_LOOPS_${SEASON}_${MVT_NUM}" loops="${!loops_var:-${MONTAGE_MUSIC_LOOPS:-1}}" target=$(echo "scale=6; $music_duration * $loops / $DAYS_IN_MVT" | bc) out_dir="$MOVIES_DIR/$cam/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM" final="$out_dir/${date_str}_Mvt${MVT_NUM}-Day${DAY_OF_MVT}of${DAYS_IN_MVT}-final.mp4" if [ -f "$final" ]; then echo "EXISTS $cam/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM/$(basename "$final")" (( skipped++ )) || true continue fi raw_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$temp_file" 2>/dev/null) || raw_dur="" vid_codec=$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name \ -of csv=p=0 "$temp_file" 2>/dev/null) || vid_codec="" if [ -z "$raw_dur" ] || [ "$raw_dur" = "N/A" ] || [ -z "$vid_codec" ]; then echo "SKIP (corrupt/unreadable): $temp_file" (( skipped++ )) || true continue fi speed=$(echo "scale=6; $raw_dur / $target" | bc) echo "MAKE $cam/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM/$(basename "$final") [${speed}x]" if $DRY_RUN; then (( processed++ )) || true continue fi mkdir -p "$out_dir" if ffmpeg -loglevel warning \ -i "$temp_file" \ -vf "setpts=PTS/$speed" \ -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SEASONS_FINAL" \ -t "$target" -an \ -y "$final"; then (( processed++ )) || true else rm -f "$final" echo " ERROR: ffmpeg failed" (( errors++ )) || true fi done < <(find "$BACKUP_ROOT" -name "*-temp.mp4" -print0 | sort -z) echo "" echo "────────────────────────────────────────" echo "Written: $processed Skipped: $skipped Errors: $errors" $DRY_RUN && echo "(dry run — re-run without --dry-run to write files)"