Sunrise time overlay; systemd upload separation; bug fixes
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
This commit is contained in:
+84
-119
@@ -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<NF)printf "\\n"}}')
|
||||
|
||||
# Check the duration of the created video
|
||||
video_duration=$(ffmpeg -i "$first_movie" 2>&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})"
|
||||
|
||||
+32
-4
@@ -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" <<EOF
|
||||
[Unit]
|
||||
Description=Sky-Cam — $SUNRISE_CAM: daily sunrise video
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
OnFailure=sky-cam-notify-failure@%n.service
|
||||
OnSuccess=sky-cam-sunrise-upload.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=$SCRIPT_DIR/daily_sunrise_video.sh
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
EOF
|
||||
echo " wrote sky-cam-sunrise.service"
|
||||
|
||||
cat > "$UNIT_DIR/sky-cam-sunrise-upload.service" <<EOF
|
||||
[Unit]
|
||||
Description=Sky-Cam — $SUNRISE_CAM: upload daily sunrise to Mattermost
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
OnFailure=sky-cam-notify-failure@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=$SCRIPT_DIR/sunrise2mm.py
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
EOF
|
||||
echo " wrote sky-cam-sunrise-upload.service"
|
||||
|
||||
write_timer "sky-cam-sunrise" "$SUNRISE_CAM: daily sunrise video" "$SCHEDULE_SUNRISE"
|
||||
|
||||
timers=(sky-cam-sunrise)
|
||||
|
||||
@@ -204,6 +204,8 @@ SUNRISE_PRE_MIN=70
|
||||
SUNRISE_POST_MIN=10
|
||||
# Target duration (seconds) for the speed-adjusted sunrise video.
|
||||
SUNRISE_TARGET_SECS=10
|
||||
# Opacity of the sunrise-time overlay (0.0 = invisible, 1.0 = fully opaque).
|
||||
SUNRISE_OVERLAY_OPACITY=0.45
|
||||
|
||||
# ── Full-day timelapse tuning ─────────────────────────────────────────────────
|
||||
FULLDAY_FPS=5 # frame rate of the timelapse video
|
||||
|
||||
Regular → Executable
+1
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import requests
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
Reference in New Issue
Block a user