From 7486916f9f9c55677fdd93d96bc68eb7fd3abcb4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 19 Apr 2026 16:35:33 +0000 Subject: [PATCH] Sunrise time overlay; systemd upload separation; bug fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit daily_sunrise_video.sh: - Fixed: was using undefined \$MOVIES_DIR (now \$BASE_DIR/movies) - Fixed: sunrise_overlay.py was called but never existed — would fail daily - Fixed: speed-adjust step had no explicit codec (-c:v libx264 missing) - New: sunrise time overlaid as stacked vertical characters on the right side using ffmpeg drawtext — no Python/Pillow dependency - New: upload removed from script; now triggered by systemd OnSuccess= - Combines speed-adjust and overlay into one ffmpeg pass (was two) - SUNRISE_OVERLAY_OPACITY from conf controls transparency install.sh: - sky-cam-sunrise.service gains OnSuccess=sky-cam-sunrise-upload.service so upload only runs when video creation succeeds, with separate log - New sky-cam-sunrise-upload.service runs sunrise2mm.py sunrise2mm.py: add shebang + chmod +x so systemd ExecStart= calls it directly sky-cam.conf: add SUNRISE_OVERLAY_OPACITY=0.45 https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ --- daily_sunrise_video.sh | 203 +++++++++++++++++------------------------ install.sh | 36 +++++++- sky-cam.conf | 2 + sunrise2mm.py | 1 + 4 files changed, 119 insertions(+), 123 deletions(-) mode change 100644 => 100755 sunrise2mm.py diff --git a/daily_sunrise_video.sh b/daily_sunrise_video.sh index 8289109..11a2a20 100644 --- a/daily_sunrise_video.sh +++ b/daily_sunrise_video.sh @@ -1,152 +1,117 @@ #!/bin/bash +# daily_sunrise_video.sh — collect today's sunrise images, speed-adjust to +# SUNRISE_TARGET_SECS, burn the local sunrise time vertically on the right +# side, and write the final video. Upload is handled by a separate systemd +# service (sky-cam-sunrise-upload) triggered via OnSuccess= so the two jobs +# have independent log entries and failure states. + +set -euo pipefail SCRIPT_DIR="$(dirname "$(realpath "$0")")" source "$SCRIPT_DIR/sky-cam.conf" +export TZ="$TIMEZONE" -# Get today's date in the format YYYY-MM-DD +# ── Date / paths ────────────────────────────────────────────────────────────── current_date=$(date +%Y-%m-%d) output_date=$(date +%Y-%m) -# Define the directory where images are stored (using today's date) image_dir="$BASE_DIR/$CAM_NAME/$current_date" - -# Set output directory -output_dir="$MOVIES_DIR/$CAM_NAME/$output_date/sunrise-only" - -# Make sure the output directory exists +output_dir="$BASE_DIR/movies/$CAM_NAME/$output_date/sunrise-only" mkdir -p "$output_dir" -# Get the sunrise time for today using the sunrise.py script +# ── Sunrise time ────────────────────────────────────────────────────────────── sunrise_time=$(python3 "$SCRIPT_DIR/sunrise.py") - -# Check if the sunrise time was fetched successfully if [[ -z "$sunrise_time" ]]; then - echo "Error: Failed to retrieve sunrise time." - exit 1 + echo "Error: Failed to retrieve sunrise time." + exit 1 fi +echo "Sunrise (UTC): $sunrise_time" -# Output the sunrise time in UTC -echo "Sunrise time (UTC): $sunrise_time" +sunrise_time_local=$(TZ="$TIMEZONE" date -d "$sunrise_time" +"%H-%M-%S") +echo "Sunrise (local): $sunrise_time_local" -# Export timezone so date and subprocesses use the configured local time. -export TZ="$TIMEZONE" +# ── Capture window ──────────────────────────────────────────────────────────── +time_to_seconds() { IFS='-' read -r h m s <<< "$1"; echo $((10#$h * 3600 + 10#$m * 60 + 10#$s)); } -# Convert sunrise time from UTC to local time. -sunrise_time_et=$(TZ="$TIMEZONE" date -d "$sunrise_time" +"%H-%M-%S") +sunrise_sec=$(time_to_seconds "$sunrise_time_local") +start_sec=$((sunrise_sec - SUNRISE_PRE_MIN * 60)) +end_sec=$((sunrise_sec + SUNRISE_POST_MIN * 60)) +echo "Window: $((start_sec/3600)):$(printf '%02d' $(((start_sec%3600)/60))) → $((end_sec/3600)):$(printf '%02d' $(((end_sec%3600)/60)))" -# Output the converted sunrise time in Eastern Time -echo "Sunrise time (Eastern Time): $sunrise_time_et" - -# Function to convert time to seconds since midnight -time_to_seconds() { - IFS='-' read -r h m s <<< "$1" - echo $((10#$h * 3600 + 10#$m * 60 + 10#$s)) -} - -# Convert sunrise time (in Eastern Time) to seconds since midnight -sunrise_seconds=$(time_to_seconds "$sunrise_time_et") - -# Calculate the capture window around sunrise. -start_seconds=$((sunrise_seconds - SUNRISE_PRE_MIN * 60)) -end_seconds=$((sunrise_seconds + SUNRISE_POST_MIN * 60)) - -# Output the start and end times in seconds for debugging -echo "Start seconds: $start_seconds" -echo "End seconds: $end_seconds" -echo "Sunrise seconds: $sunrise_seconds" - -# Check if the image directory exists +# ── Collect images in window ────────────────────────────────────────────────── if [ ! -d "$image_dir" ]; then - echo "Error: The directory for images does not exist: $image_dir" - exit 1 + echo "Error: image directory not found: $image_dir" + exit 1 fi -# Create a temporary file to store the list of image files -temp_file=$(mktemp) +temp_list=$(mktemp --suffix=.txt) +trap 'rm -f "$temp_list" "$raw_video" 2>/dev/null || true' EXIT -# Find, sort, and process images in the specified directory -# Sort files by filename, which will order them correctly by MM-DD-SS -find "$image_dir" -type f -name "*.jpg" | sort -n | while read -r image; do - # Extract the time portion of the filename (e.g., 09-16-00 from 09-16-00.jpg) - image_time=$(basename "$image" .jpg) # MM-DD-SS +find "$image_dir" -type f -name "*.jpg" | sort | while read -r img; do + img_sec=$(time_to_seconds "$(basename "$img" .jpg)") + if [[ $img_sec -ge $start_sec && $img_sec -le $end_sec ]]; then + echo "file '$img'" + fi +done > "$temp_list" - # Convert image time to seconds since midnight - image_seconds=$(time_to_seconds "$image_time") +num_images=$(wc -l < "$temp_list") +if [[ $num_images -lt 1 ]]; then + echo "No images found in sunrise window." + exit 1 +fi +echo "Images in window: $num_images" - # Debug: Output the image time and its seconds since midnight - echo "Image: $image, Time: $image_time, Seconds: $image_seconds" - - # Check if the image time falls within the specified range - if [[ $image_seconds -ge $start_seconds && $image_seconds -le $end_seconds ]]; then - echo "Image $image_time ($image_seconds) is within the range [$start_seconds, $end_seconds]" - - # Add the image to the temporary file - echo "file '$image'" >> "$temp_file" - fi -done - -# Check if any images were found in the time range -if [ ! -s "$temp_file" ]; then - echo "No images found within the specified time range." - exit 1 +# ── Font detection (same fallback chain as montage-mvt.sh) ─────────────────── +FONT="" +if command -v fc-match &>/dev/null; then + FONT=$(fc-match "DejaVu Sans:style=Regular" --format="%{file}" 2>/dev/null || true) +fi +if [ -z "$FONT" ]; then + for f in \ + /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf \ + /usr/share/fonts/dejavu/DejaVuSans.ttf \ + /usr/share/fonts/TTF/DejaVuSans.ttf \ + /usr/share/fonts/truetype/freefont/FreeSans.ttf; do + [ -f "$f" ] && { FONT="$f"; break; } + done fi -# Calculate the number of images -num_images=$(wc -l < "$temp_file") -echo "Total images to be included in the video: $num_images" +# ── Step 1: Raw video from images ───────────────────────────────────────────── +raw_video=$(mktemp --suffix=.mp4) +echo "Step 1/2: encoding raw video..." +ffmpeg -loglevel warning \ + -f concat -safe 0 -i "$temp_list" \ + -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" -vsync 2 -an \ + -y "$raw_video" -# Output video filename for the first movie (no speed adjustments) -first_movie="$output_dir/$current_date-sunrise-day.mp4" +raw_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$raw_video") +speed_factor=$(echo "scale=3; $raw_dur / $SUNRISE_TARGET_SECS" | bc) +echo "Raw: ${raw_dur}s speed factor: ${speed_factor}x" -# Create the first movie at the default frame rate (no speed adjustments) -echo "Creating video at default frame rate..." -ffmpeg -f concat -safe 0 -i "$temp_file" -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" -vsync 2 -y "$first_movie" +# ── Step 2: Speed-adjust + sunrise time overlay ─────────────────────────────── +# Time displayed as stacked characters down the right side (e.g. 0/7/:/2/3), +# semi-transparent with a soft drop shadow so it reads on any background. +SR_TIME=$(echo "$sunrise_time_local" | cut -d'-' -f1,2 | tr '-' ':') +# Build "H\nH\n:\nM\nM" for vertical stacking in drawtext +SR_VERT=$(echo "$SR_TIME" | awk 'BEGIN{FS=""}{for(i=1;i<=NF;i++){printf "%s",$i; if(i&1 | grep "Duration" | cut -d ' ' -f 4 | sed s/,//) -echo "Initial video duration: $video_duration" +DT="drawtext" +[ -n "$FONT" ] && DT="${DT}=fontfile='${FONT}'" || DT="${DT}" +DT="${DT}:text='${SR_VERT}'" +DT="${DT}:fontcolor=white@${SUNRISE_OVERLAY_OPACITY}" +DT="${DT}:fontsize=h/22" +DT="${DT}:line_spacing=4" +DT="${DT}:x=w-tw-18:y=(h-th)/2" +DT="${DT}:shadowcolor=black@0.55:shadowx=1:shadowy=1" -# Get the video duration in seconds (from HH:MM:SS format) -IFS=':' read -r hours minutes seconds <<< $(echo $video_duration | cut -d '.' -f1) # Extract time -total_seconds=$(($hours * 3600 + $minutes * 60 + $seconds)) # Convert to total seconds - -target_duration=$SUNRISE_TARGET_SECS - -speed_up_factor=$(echo "scale=3; $total_seconds / $target_duration" | bc) -echo "Calculated speed-up factor: $speed_up_factor" - -# Output filename for the second movie (speed-adjusted) final_video="$output_dir/$current_date-daily-sunrise.mp4" +echo "Step 2/2: speed-adjust + overlay → $final_video" -# Apply the speed adjustment using the setpts filter -echo "Adjusting video speed to ${SUNRISE_TARGET_SECS}s..." -ffmpeg -i "$first_movie" -filter:v "setpts=PTS/$speed_up_factor" -y "$final_video" +ffmpeg -loglevel warning \ + -i "$raw_video" \ + -vf "setpts=PTS/${speed_factor},${DT}" \ + -c:v libx264 -pix_fmt yuv420p -crf "$CRF_SUNRISE" -an \ + -y "$final_video" -# Check the duration of the adjusted video -adjusted_duration=$(ffmpeg -i "$final_video" 2>&1 | grep "Duration" | cut -d ' ' -f 4 | sed s/,//) -echo "Adjusted video duration: $adjusted_duration" - -# Check for ffmpeg success -if [ $? -ne 0 ]; then - echo "Error: ffmpeg failed to create the final video." - exit 1 -fi - -# Inform user the video has been created -echo "Video created at $final_video" - -# Clean up temporary files -rm "$temp_file" - -# Run the sunrise_overlay.py script to add the sunrise overlay -python3 "$SCRIPT_DIR/sunrise_overlay.py" "$final_video" - -# Run the sunrise2mm.py script to upload the video -python3 "$SCRIPT_DIR/sunrise2mm.py" - -# Check if the Python script was successful -if [ $? -eq 0 ]; then - echo "Successfully uploaded the video to Mattermost." -else - echo "There was an issue uploading the video to Mattermost." -fi +actual_dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$final_video") +echo "Done: $final_video (${actual_dur}s, sunrise at ${SR_TIME})" diff --git a/install.sh b/install.sh index ec7b455..2166c34 100755 --- a/install.sh +++ b/install.sh @@ -75,10 +75,38 @@ EOF echo " wrote sky-cam-notify-failure@.service" # ── Sunrise video — SUNRISE_CAM only ───────────────────────────────────────── -write_service "sky-cam-sunrise" \ - "$SUNRISE_CAM: daily sunrise video → Mattermost" \ - "daily_sunrise_video.sh" \ - "After=network-online.target\nWants=network-online.target\n" +# OnSuccess= triggers the upload service only when video creation succeeds. +cat > "$UNIT_DIR/sky-cam-sunrise.service" < "$UNIT_DIR/sky-cam-sunrise-upload.service" <