Merge pull request #24 from outis1one/claude/seasonal-sunrise-montage-nn268
Claude/seasonal sunrise montage nn268
This commit is contained in:
+6
-4
@@ -50,10 +50,12 @@ 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)"
|
||||
# Each daily clip covers (music_duration * loops) / DAYS_IN_MVT seconds.
|
||||
# Short movements use loops > 1 so each day gets more screen time.
|
||||
loops_var="MONTAGE_MUSIC_LOOPS_${SEASON}_${MVT_NUM}"
|
||||
loops="${!loops_var:-${MONTAGE_MUSIC_LOOPS:-1}}"
|
||||
target_per_clip=$(echo "scale=6; $music_duration * $loops / $DAYS_IN_MVT" | bc)
|
||||
echo "Target clip duration: $target_per_clip s ($DAYS_IN_MVT days in movement, ${loops}x music)"
|
||||
|
||||
# ── Locate yesterday's images ─────────────────────────────────────────────────
|
||||
image_dir="$base_dir/$CAM_NAME/$yesterday"
|
||||
|
||||
+9
-2
@@ -97,7 +97,9 @@ while IFS= read -r -d '' temp_file; do
|
||||
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)
|
||||
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"
|
||||
@@ -108,7 +110,12 @@ while IFS= read -r -d '' temp_file; do
|
||||
continue
|
||||
fi
|
||||
|
||||
raw_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$temp_file")
|
||||
raw_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$temp_file" 2>/dev/null || true)
|
||||
if [ -z "$raw_dur" ] || [ "$raw_dur" = "N/A" ]; 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]"
|
||||
|
||||
+12
-7
@@ -71,6 +71,11 @@ fi
|
||||
music_duration=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$music_file")
|
||||
echo "Music: $(basename "$music_file") (${music_duration}s)"
|
||||
|
||||
loops_var="MONTAGE_MUSIC_LOOPS_${SEASON}_${MVT_NUM}"
|
||||
loops="${!loops_var:-${MONTAGE_MUSIC_LOOPS:-1}}"
|
||||
effective_duration=$(echo "scale=3; $music_duration * $loops" | bc)
|
||||
[ "$loops" -gt 1 ] && echo "Music loops: ${loops}x (effective ${effective_duration}s)"
|
||||
|
||||
# ── Collect sorted daily clips ────────────────────────────────────────────────
|
||||
mapfile -d '' daily_clips < <(find "$video_dir" -maxdepth 1 -name "*-final.mp4" -print0 | sort -z)
|
||||
if [ "${#daily_clips[@]}" -eq 0 ]; then
|
||||
@@ -139,15 +144,15 @@ pad=${VIDEO_W}:${VIDEO_H}:(ow-iw)/2:(oh-ih)/2,setsar=1" \
|
||||
# ── Step 2: Speed-adjust — saved permanently so music/overlay failure is recoverable ─
|
||||
# Deleted automatically if step 3 succeeds.
|
||||
concat_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$temp_concat")
|
||||
speed_factor=$(echo "scale=6; $concat_dur / $music_duration" | bc)
|
||||
echo "Step 2/3: speed $speed_factor (raw=${concat_dur}s → music=${music_duration}s)"
|
||||
speed_factor=$(echo "scale=6; $concat_dur / $effective_duration" | bc)
|
||||
echo "Step 2/3: speed $speed_factor (raw=${concat_dur}s → target=${effective_duration}s)"
|
||||
|
||||
sped_file="$output_dir/${MVT_START}_${SEASON}_Mvt${MVT_NUM}-Sped.mp4"
|
||||
if ! ffmpeg -loglevel warning \
|
||||
-i "$temp_concat" \
|
||||
-vf "setpts=PTS/$speed_factor" \
|
||||
-c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_MONTAGE" -an \
|
||||
-t "$music_duration" -y "$sped_file"; then
|
||||
-t "$effective_duration" -y "$sped_file"; then
|
||||
rm -f "$sped_file"
|
||||
"$SCRIPT_DIR/notify.sh" "FAILED: montage step 2/3 (speed) $SEASON Mvt$MVT_NUM ($ASTRO_YEAR)" \
|
||||
"speed-adjust failed — concat temp lost; re-run montage-mvt.sh" || true
|
||||
@@ -165,7 +170,7 @@ ATTR_FADE_END=$(echo "$ATTR_DUR - $ATTR_FADE" | bc)
|
||||
ALPHA="if(lt(t,${ATTR_FADE}),t/${ATTR_FADE},if(gt(t,${ATTR_FADE_END}),(${ATTR_DUR}-t)/${ATTR_FADE},1))"
|
||||
ENABLE="between(t,0,${ATTR_DUR})"
|
||||
|
||||
fade_out_start=$(echo "scale=3; $music_duration - $FADE_DUR" | bc)
|
||||
fade_out_start=$(echo "scale=3; $effective_duration - $FADE_DUR" | bc)
|
||||
echo "Step 3/3: music + fades + attribution overlay..."
|
||||
|
||||
# Build attribution drawtext filters (box=1 gives a per-line background).
|
||||
@@ -193,20 +198,20 @@ output_file="$output_dir/${MVT_START}_${SEASON}_Mvt${MVT_NUM}-Montage.mp4"
|
||||
# If music/overlay fails, the sped video is promoted to the output path so
|
||||
# year-end-join can still include this movement (as a silent, no-overlay clip).
|
||||
if ffmpeg -loglevel warning \
|
||||
-i "$sped_file" -i "$music_file" \
|
||||
-i "$sped_file" -stream_loop $((loops - 1)) -i "$music_file" \
|
||||
-filter_complex \
|
||||
"[0:v]fade=t=in:st=0:d=${FADE_DUR},fade=t=out:st=${fade_out_start}:d=${FADE_DUR},${DT}[vout];\
|
||||
[1:a]afade=t=in:st=0:d=${FADE_DUR},afade=t=out:st=${fade_out_start}:d=${FADE_DUR}[aout]" \
|
||||
-map "[vout]" -map "[aout]" \
|
||||
-c:v libx265 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_MONTAGE_FINAL" \
|
||||
-tag:v hvc1 -c:a aac -b:a "$AUDIO_BITRATE" \
|
||||
-t "$music_duration" -y "$output_file"; then
|
||||
-t "$effective_duration" -y "$output_file"; then
|
||||
rm -f "$sped_file"
|
||||
echo "Done: $output_file"
|
||||
|
||||
# ── Post-build checks ─────────────────────────────────────────────────────
|
||||
actual_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$output_file")
|
||||
dur_delta=$(echo "scale=1; $actual_dur - $music_duration" | bc | sed 's/^-//')
|
||||
dur_delta=$(echo "scale=1; $actual_dur - $effective_duration" | bc | sed 's/^-//')
|
||||
stream_types=$(ffprobe -v error -show_entries stream=codec_type -of csv=p=0 "$output_file" 2>/dev/null)
|
||||
has_video=$(echo "$stream_types" | grep -c "video" || echo 0)
|
||||
has_audio=$(echo "$stream_types" | grep -c "audio" || echo 0)
|
||||
|
||||
@@ -251,6 +251,24 @@ MONTAGE_FADE_DUR=2.0 # video + audio fade in/out (seconds)
|
||||
MONTAGE_ATTR_DUR=6 # attribution overlay total duration (seconds)
|
||||
MONTAGE_ATTR_FADE=1 # attribution fade-in and fade-out length (seconds)
|
||||
|
||||
# ── Music loop multiplier ─────────────────────────────────────────────────────
|
||||
# Short movements (especially Mvt 2 adagios) give each day only 3-5 seconds of
|
||||
# screen time. Looping the music N times multiplies the per-day target so days
|
||||
# are less compressed and more watchable. Per-movement overrides take the form
|
||||
# MONTAGE_MUSIC_LOOPS_<Season>_<MvtNum>.
|
||||
#
|
||||
# Recommended starting point based on movement durations:
|
||||
# Summer Mvt 2 (107s) → 3× Winter Mvt 2 (132s) → 3×
|
||||
# Autumn Mvt 2 (152s) → 2× Summer Mvt 3 (159s) → 2×
|
||||
# Spring Mvt 2 (171s) → 2× others 1× (already ≥10 s/day)
|
||||
#
|
||||
MONTAGE_MUSIC_LOOPS=1 # default for all movements
|
||||
MONTAGE_MUSIC_LOOPS_Summer_2=3
|
||||
MONTAGE_MUSIC_LOOPS_Winter_2=3
|
||||
MONTAGE_MUSIC_LOOPS_Autumn_2=2
|
||||
MONTAGE_MUSIC_LOOPS_Summer_3=2
|
||||
MONTAGE_MUSIC_LOOPS_Spring_2=2
|
||||
|
||||
# ── Direct RTSP capture (replaces MotionEye / any NVR) ────────────────────────
|
||||
# capture.sh pulls frames directly from each camera's RTSP stream.
|
||||
# No NVR software required — just ffmpeg and the camera's stream URL.
|
||||
|
||||
Reference in New Issue
Block a user