Adds six CRF vars, AUDIO_BITRATE, and three MONTAGE_* overlay durations. Values match previous hardcoded defaults so behavior is unchanged. The same CRF appeared in several scripts but with intentionally different values per purpose (28 for draft intermediates, 26 for polished output). Each script's CRF is now its own variable in conf, so changing one never silently changes another. https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
110 lines
5.3 KiB
Bash
Executable File
110 lines
5.3 KiB
Bash
Executable File
#!/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
|
|
|
|
# Camera name: first argument overrides conf (systemd passes it via ExecStart).
|
|
CAM_NAME="${1:-$CAM_NAME}"
|
|
|
|
# ── 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="$MOVIES_DIR/$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 "$CRF_SEASONS_RAW" -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 "$CRF_SEASONS_FINAL" \
|
|
-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" "$CAM_NAME"
|
|
fi
|