diff --git a/4-seasons.sh b/4-seasons.sh index a162db6..d362fbf 100755 --- a/4-seasons.sh +++ b/4-seasons.sh @@ -88,7 +88,7 @@ done > "$temp_file" 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 \ + -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -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") @@ -102,7 +102,7 @@ final_file="$output_dir/${yesterday}_Mvt${MVT_NUM}-Day${DAY_OF_MVT}of${DAYS_IN_M if ! ffmpeg -loglevel warning \ -i "$temp_video" \ -vf "setpts=PTS/$speed_factor" \ - -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SEASONS_FINAL" \ + -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SEASONS_FINAL" \ -t "$target_per_clip" -an \ -y "$final_file"; then rm -f "$final_file" diff --git a/daily_sunrise_video.sh b/daily_sunrise_video.sh index d5f15a9..d262b1f 100644 --- a/daily_sunrise_video.sh +++ b/daily_sunrise_video.sh @@ -110,7 +110,7 @@ raw_video=$(mktemp --suffix=.mp4) echo "Step 1/3: encoding raw video..." if ! ffmpeg -loglevel warning \ -f concat -safe 0 -i "$temp_list" \ - -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" -vsync 2 -an \ + -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" -vsync 2 -an \ -y "$raw_video"; then "$SCRIPT_DIR/notify.sh" "FAILED: sunrise step 1/3 (encode) $current_date" \ "ffmpeg raw encode from images failed — check: journalctl -u sky-cam-sunrise.service" || true @@ -128,7 +128,7 @@ echo "Step 2/3: speed-adjust → $sped_video" if ! ffmpeg -loglevel warning \ -i "$raw_video" \ -vf "setpts=PTS/${speed_factor}" \ - -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" -an \ + -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" -an \ -y "$sped_video"; then rm -f "$sped_video" "$SCRIPT_DIR/notify.sh" "FAILED: sunrise step 2/3 (speed) $current_date" \ @@ -223,7 +223,7 @@ if [ -n "$DT" ]; then if ffmpeg -loglevel warning \ -i "$work_video" \ -vf "${DT}" \ - -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" \ + -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_SUNRISE" \ "${audio_out_flags[@]}" \ -y "$final_video"; then step3b_ok=true @@ -253,4 +253,14 @@ if $step3b_ok; then [ -f "$cam_audio" ] && rm -f "$cam_audio" "$SCRIPT_DIR/notify.sh" "Sunrise ready: $current_date" \ "$(basename "$final_video") — ${actual_dur}s, sunrise at ${SR_TIME}" || true + + # ── Rolling retention: delete sunrise videos older than SUNRISE_RETENTION_DAYS ─ + retain="${SUNRISE_RETENTION_DAYS:-10}" + if [ "$retain" -gt 0 ]; then + deleted_old=$(find "$MOVIES_DIR/$CAM_NAME" \ + -path "*/sunrise-only/*-daily-sunrise.mp4" \ + -mtime +"$retain" -print -delete 2>/dev/null | wc -l) + [ "$deleted_old" -gt 0 ] && \ + echo "Retention: deleted $deleted_old sunrise video(s) older than ${retain} days" + fi fi diff --git a/montage-mvt.sh b/montage-mvt.sh index c181583..e956178 100755 --- a/montage-mvt.sh +++ b/montage-mvt.sh @@ -133,7 +133,7 @@ ffmpeg -loglevel warning \ -f concat -safe 0 -i "$concat_list" \ -vf "scale=${VIDEO_W}:${VIDEO_H}:force_original_aspect_ratio=decrease,\ pad=${VIDEO_W}:${VIDEO_H}:(ow-iw)/2:(oh-ih)/2,setsar=1" \ - -c:v libx264 -pix_fmt yuv420p -crf "$CRF_MONTAGE" -an \ + -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_MONTAGE" -an \ -y "$temp_concat" # ── Step 2: Speed-adjust — saved permanently so music/overlay failure is recoverable ─ @@ -146,7 +146,7 @@ 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 -crf "$CRF_MONTAGE" -an \ + -c:v libx264 -pix_fmt yuv420p -preset "$ENCODE_PRESET" -crf "$CRF_MONTAGE" -an \ -t "$music_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)" \ @@ -198,7 +198,8 @@ if ffmpeg -loglevel warning \ "[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 libx264 -pix_fmt yuv420p -c:a aac -b:a "$AUDIO_BITRATE" \ + -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 rm -f "$sped_file" echo "Done: $output_file" diff --git a/sky-cam.conf b/sky-cam.conf index 87fb9d8..a1f719b 100644 --- a/sky-cam.conf +++ b/sky-cam.conf @@ -217,20 +217,28 @@ SUNRISE_TARGET_SECS=10 SUNRISE_OVERLAY_ENABLED=true # Opacity of the sunrise-time text (0.0 = invisible, 1.0 = fully opaque). SUNRISE_OVERLAY_OPACITY=0.45 +# Delete daily sunrise videos older than this many days (rolling window). +# Set 0 to keep forever. +SUNRISE_RETENTION_DAYS=10 # ── Video encoding quality ──────────────────────────────────────────────────── # FFmpeg CRF: lower = higher quality / larger files. Typical range 18–30. # 18 ≈ visually lossless 23 = ffmpeg default 28 = smaller/lower -# Different scripts use different defaults by design — intermediate/draft -# encodings use a higher CRF (smaller files, quality less critical), while -# final "polished" outputs use a lower CRF for better quality. -# Each is independent — changing one does NOT change the others. +# H.265 (libx265) CRF is not directly comparable — H.265 CRF 22 ≈ H.264 CRF 26 +# in visual quality but ~40-50% smaller file. Final montages use H.265; +# intermediates (deleted after use) stay H.264. # -CRF_SUNRISE=28 # daily_sunrise_video.sh — daily Mattermost upload -CRF_SEASONS_RAW=28 # 4-seasons.sh — raw concat before speed-adjust -CRF_SEASONS_FINAL=26 # 4-seasons.sh — final daily clip (goes into montage) -CRF_MONTAGE=26 # montage-mvt.sh — concat and speed-adjust and final -AUDIO_BITRATE=192k # montage-mvt.sh — music track on movement montages +# ENCODE_PRESET: slow = better compression, same CRF quality, ~2× encode time. +# Use 'medium' on slower machines, 'slow' or 'slower' on fast ones. +# All encodes run at night so 'slow' is the right default. +# +CRF_SUNRISE=28 # daily_sunrise_video.sh — daily Mattermost upload (H.264) +CRF_SEASONS_RAW=28 # 4-seasons.sh — raw concat before speed-adjust (H.264) +CRF_SEASONS_FINAL=26 # 4-seasons.sh — final daily clip (goes into montage) (H.264) +CRF_MONTAGE=28 # montage-mvt.sh — intermediate concat + sped steps (H.264) +CRF_MONTAGE_FINAL=22 # montage-mvt.sh — final archive montage (H.265) +ENCODE_PRESET=slow # ffmpeg preset for all encodes (slow/medium/fast) +AUDIO_BITRATE=192k # montage-mvt.sh — music track on movement montages # ── Montage attribution overlay ─────────────────────────────────────────────── # A translucent bar across the top of each movement montage, naming the @@ -254,12 +262,15 @@ MONTAGE_ATTR_FADE=1 # attribution fade-in and fade-out length (seconds) # CAM_RTSP_ goes in .env (see bottom of this file), not here. # Seconds between captured frames (applies to all cameras). -# 10 = one frame every 10 s → 8640 frames/day → good timelapse density. +# Lower = smoother timelapse + larger storage footprint: +# 10 s → 8,640 frames/day ~2.5 GB/day (default, good balance) +# 5 s → 17,280 frames/day ~5 GB/day (smoother motion) +# 2 s → 43,200 frames/day ~12 GB/day (very smooth, high storage) +# CAPTURE_STALE_SECS should always be at least 2× CAPTURE_INTERVAL. CAPTURE_INTERVAL=10 # Seconds without a new frame before the watchdog sends a stall notification. -# Should be at least 2× CAPTURE_INTERVAL. A recovery notification fires -# automatically when frames start arriving again. +# A recovery notification fires automatically when capture resumes. CAPTURE_STALE_SECS=30 # ── Sunrise audio capture ──────────────────────────────────────────────────────