#!/bin/bash # fullday-video.sh — create an ~30-minute timelapse of yesterday's full day of # images. Files older than 10 days are deleted automatically to save space. # Run once per day (e.g. from cron at 02:00, after 4-seasons.sh). set -euo pipefail # ── Configuration ────────────────────────────────────────────────────────────── base_dir="/home/motion/drives/local-2tb" script_name=$(basename "$(dirname "$(realpath "$0")")" | cut -d'-' -f1) TARGET_DURATION=1800 # target video length in seconds (~30 min) MIN_FRAME_DUR=0.016667 # max 60 fps MAX_FRAME_DUR=10.0 # min 0.1 fps (10 s per frame) RETENTION_DAYS=10 TARGET_W=1280 TARGET_H=720 # ── Date / paths ────────────────────────────────────────────────────────────── yesterday=$(date --date="yesterday" +%Y-%m-%d) image_dir="$base_dir/$script_name/$yesterday" output_dir="$base_dir/movies/$script_name/fullday" mkdir -p "$output_dir" # ── Purge old full-day videos ───────────────────────────────────────────────── echo "Removing full-day videos older than $RETENTION_DAYS days..." find "$output_dir" -maxdepth 1 -name "*-fullday.mp4" -mtime "+$RETENTION_DAYS" -delete # ── Sanity checks ───────────────────────────────────────────────────────────── if [ ! -d "$image_dir" ]; then echo "WARNING: Image directory $image_dir not found — skipping." exit 0 fi num_images=$(find "$image_dir" -type f -name "*.jpg" | wc -l) if [ "$num_images" -lt 1 ]; then echo "WARNING: No images found in $image_dir — skipping." exit 0 fi echo "Images: $num_images" # ── Calculate per-frame duration to hit TARGET_DURATION ─────────────────────── frame_dur=$(echo "scale=6; $TARGET_DURATION / $num_images" | bc) # Clamp to [MIN_FRAME_DUR, MAX_FRAME_DUR] if (( $(echo "$frame_dur < $MIN_FRAME_DUR" | bc -l) )); then frame_dur=$MIN_FRAME_DUR; fi if (( $(echo "$frame_dur > $MAX_FRAME_DUR" | bc -l) )); then frame_dur=$MAX_FRAME_DUR; fi fps=$(echo "scale=4; 1 / $frame_dur" | bc) approx_min=$(echo "scale=1; $num_images * $frame_dur / 60" | bc) echo "Frame duration: ${frame_dur}s (fps=${fps}) ≈ ${approx_min} min" # ── Build concat list with per-frame duration ───────────────────────────────── # The concat demuxer requires the last file to be repeated without a duration. temp_list=$(mktemp --suffix=.txt) trap 'rm -f "$temp_list"' EXIT last_img="" find "$image_dir" -type f -name "*.jpg" | sort | while read -r img; do echo "file '$img'" echo "duration $frame_dur" last_img="$img" done > "$temp_list" # Append last image again (required by concat demuxer to set final frame pts) last_img=$(find "$image_dir" -type f -name "*.jpg" | sort | tail -n 1) echo "file '$last_img'" >> "$temp_list" # ── Encode ──────────────────────────────────────────────────────────────────── output_file="$output_dir/${yesterday}-fullday.mp4" echo "Creating full-day video: $output_file" ffmpeg -loglevel warning \ -f concat -safe 0 -i "$temp_list" \ -vf "scale=${TARGET_W}:${TARGET_H}:force_original_aspect_ratio=decrease,\ pad=${TARGET_W}:${TARGET_H}:(ow-iw)/2:(oh-ih)/2,setsar=1" \ -c:v libx264 -pix_fmt yuv420p -crf 28 \ -an -y "$output_file" actual_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$output_file") echo "Done: $output_file (${actual_dur}s)"