From 26414f9883a1103efc109be3bc0ffb74c26af0b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 19:57:13 +0000 Subject: [PATCH] =?UTF-8?q?Add=20make-finals.sh=20=E2=80=94=20one-off=20re?= =?UTF-8?q?covery=20script=20for=20backup=20temp=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scans all *-temp.mp4 files under a backup root regardless of directory structure, infers camera name from path, uses season_info.py for correct movement sizing, writes finals to MOVIES_DIR. Handles messy nested paths (south/south/, movies/south/, etc.) and deduplicates via EXISTS check. https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ --- make-finals.sh | 140 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100755 make-finals.sh diff --git a/make-finals.sh b/make-finals.sh new file mode 100755 index 0000000..1c2ea11 --- /dev/null +++ b/make-finals.sh @@ -0,0 +1,140 @@ +#!/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 + +BACKUP_ROOT="" +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) DRY_RUN=true; shift ;; + -*) echo "Unknown option: $1"; exit 1 ;; + *) BACKUP_ROOT="$1"; shift ;; + esac +done + +if [ -z "$BACKUP_ROOT" ]; then + echo "Usage: $0 [--dry-run]" + echo " e.g. $0 ~/drives/data2-main --dry-run" + 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" +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 + + _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" + + 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") + target=$(echo "scale=6; $music_duration / $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") + 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)"