Moves Season/MvtN mp4 files from flat camera dirs into astro-YEAR/<cam>/Season/MvtN/ using season_info.py for correct year assignment. Handles cross-year mixing in old backup structure. Dry-run support; never deletes files. https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
118 lines
3.4 KiB
Bash
Executable File
118 lines
3.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# reorg-backup.sh — reorganize backup season files into astro-YEAR folders.
|
|
#
|
|
# Moves files from:
|
|
# <backup_root>/<cam>/Season/MvtN/YYYY-MM-DD_*
|
|
# Into:
|
|
# <backup_root>/astro-YEAR/<cam>/Season/MvtN/YYYY-MM-DD_*
|
|
#
|
|
# Uses season_info.py to determine the correct ASTRO_YEAR from each file's date.
|
|
# NEVER deletes anything — only moves files.
|
|
#
|
|
# Usage:
|
|
# ./reorg-backup.sh <backup_root> [--dry-run]
|
|
#
|
|
# Example:
|
|
# ./reorg-backup.sh ~/drives/data2-main --dry-run
|
|
# ./reorg-backup.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 <backup_root> [--dry-run]"
|
|
echo " e.g. $0 ~/drives/data2-main --dry-run"
|
|
exit 1
|
|
fi
|
|
|
|
BACKUP_ROOT="$(realpath "$BACKUP_ROOT")"
|
|
|
|
$DRY_RUN && echo "DRY RUN — no files will be moved"
|
|
echo "Backup root: $BACKUP_ROOT"
|
|
echo ""
|
|
|
|
moved=0
|
|
skipped=0
|
|
errors=0
|
|
|
|
# Find all mp4 files in Season/MvtN directories (not already in astro-* dirs)
|
|
while IFS= read -r -d '' src_file; do
|
|
filename="$(basename "$src_file")"
|
|
|
|
# Extract date: handles YYYY-MM-DD_ (underscore) prefix
|
|
date_str="${filename%%_*}"
|
|
if ! [[ "$date_str" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
|
|
echo "SKIP (unrecognised filename): $src_file"
|
|
(( skipped++ )) || true
|
|
continue
|
|
fi
|
|
|
|
# Get ASTRO_YEAR for this date
|
|
_info="$(python3 "$SCRIPT_DIR/season_info.py" "$date_str" 2>/dev/null)" || {
|
|
echo "SKIP (season_info.py failed for $date_str): $filename"
|
|
(( skipped++ )) || true
|
|
continue
|
|
}
|
|
eval "$_info"
|
|
|
|
# Reconstruct target path:
|
|
# <backup_root>/astro-YEAR/<cam>/<Season>/Mvt<N>/<filename>
|
|
# Source path is:
|
|
# <backup_root>/<cam>/<Season>/Mvt<N>/<filename>
|
|
rel_path="${src_file#$BACKUP_ROOT/}" # e.g. north/Winter/Mvt1/2025-01-06_...
|
|
cam_and_below="${rel_path#*/}" # e.g. Winter/Mvt1/2025-01-06_... (strip cam)
|
|
cam_name="${rel_path%%/*}" # e.g. north
|
|
|
|
dest_dir="$BACKUP_ROOT/astro-${ASTRO_YEAR}/$cam_name/$(dirname "$cam_and_below")"
|
|
dest_file="$dest_dir/$filename"
|
|
|
|
if [ "$src_file" = "$dest_file" ]; then
|
|
(( skipped++ )) || true
|
|
continue
|
|
fi
|
|
|
|
if [ -f "$dest_file" ]; then
|
|
echo "EXISTS — skip: astro-${ASTRO_YEAR}/$cam_name/$cam_and_below"
|
|
(( skipped++ )) || true
|
|
continue
|
|
fi
|
|
|
|
echo "MOVE astro-${ASTRO_YEAR}/$cam_name/$cam_and_below"
|
|
|
|
if ! $DRY_RUN; then
|
|
mkdir -p "$dest_dir"
|
|
if mv "$src_file" "$dest_file"; then
|
|
(( moved++ )) || true
|
|
else
|
|
echo " ERROR: mv failed"
|
|
(( errors++ )) || true
|
|
fi
|
|
else
|
|
(( moved++ )) || true
|
|
fi
|
|
|
|
done < <(find "$BACKUP_ROOT" \
|
|
-not -path "*/astro-*" \
|
|
-regextype posix-extended \
|
|
-regex ".*/[A-Z][a-z]+/Mvt[0-9]+/[0-9]{4}-[0-9]{2}-[0-9]{2}_.*\.mp4" \
|
|
-print0 | sort -z)
|
|
|
|
echo ""
|
|
echo "────────────────────────────────────────"
|
|
echo "Moved: $moved Skipped: $skipped Errors: $errors"
|
|
$DRY_RUN && echo "(dry run — re-run without --dry-run to move files)"
|