Merge pull request #54 from outis1one/claude/comment-sunrise-overlay-9LvlR
Claude/comment sunrise overlay 9 lvl r
This commit is contained in:
@@ -50,7 +50,17 @@ while true; do
|
||||
mkdir -p "$dir"
|
||||
|
||||
timestamp=$(date +%H-%M-%S)
|
||||
|
||||
# Weather tag for THIS chunk — queried right before recording so the
|
||||
# filename reflects conditions at the time of capture, not earlier in the
|
||||
# day. weather-tag.sh prints empty on disabled / missing key / API error,
|
||||
# so the un-tagged filename is the safe fallback.
|
||||
weather_tag=$("$SCRIPT_DIR/weather-tag.sh" 2>/dev/null || true)
|
||||
if [ -n "$weather_tag" ]; then
|
||||
outfile="$dir/${CAM}-${timestamp}-${weather_tag}.m4a"
|
||||
else
|
||||
outfile="$dir/${CAM}-${timestamp}.m4a"
|
||||
fi
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Recording → $outfile (${CHUNK}s)"
|
||||
|
||||
|
||||
@@ -140,6 +140,20 @@ if $TEST_MODE; then
|
||||
fi
|
||||
|
||||
# ── Font detection ────────────────────────────────────────────────────────────
|
||||
# Picks a TrueType font file for ffmpeg's drawtext filter. drawtext needs an
|
||||
# absolute path to a .ttf/.otf file — it cannot use a font *name* like Arial.
|
||||
#
|
||||
# To use a different font:
|
||||
# 1. Find the .ttf path on this machine, e.g.:
|
||||
# fc-match "Liberation Mono" --format='%{file}\n'
|
||||
# find /usr/share/fonts -name '*.ttf' | grep -i mono
|
||||
# 2. Either (a) hard-code it below by replacing the fc-match line with:
|
||||
# FONT="/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf"
|
||||
# or (b) change the fc-match query string ("DejaVu Sans:style=Regular")
|
||||
# to your preferred face, e.g. "Liberation Mono:style=Bold".
|
||||
# 3. Add the chosen path to the fallback list below so the script still works
|
||||
# if fc-match is missing.
|
||||
# Bold faces (e.g. DejaVuSans-Bold.ttf) read better at small sizes against sky.
|
||||
FONT=""
|
||||
if command -v fc-match &>/dev/null; then
|
||||
FONT=$(fc-match "DejaVu Sans:style=Regular" --format="%{file}" 2>/dev/null || true)
|
||||
@@ -198,20 +212,99 @@ $TEST_MODE || echo "Speed-adjusted: ${sped_dur}s (saved — overlay still pendi
|
||||
$TEST_MODE && echo "Speed-adjusted: ${sped_dur}s"
|
||||
|
||||
# ── Build overlay filter ──────────────────────────────────────────────────────
|
||||
# Burns the sunrise time onto the video using ffmpeg's drawtext filter.
|
||||
# Layout is VERTICAL — each character of "HH:MM" is on its own line, stacked
|
||||
# top-to-bottom in the lower-right corner. This is achieved by writing each
|
||||
# char to a separate line of $temp_text, then drawtext renders that file
|
||||
# verbatim (newlines = line breaks).
|
||||
#
|
||||
# To switch to a HORIZONTAL "HH:MM" overlay, replace the printf below with:
|
||||
# printf '%s' "$SR_TIME" > "$temp_text"
|
||||
# and bump fontsize (e.g. h/18) since horizontal text needs less vertical room.
|
||||
#
|
||||
# ── Quick-edit cheatsheet for every drawtext parameter ──────────────────────
|
||||
#
|
||||
# FONT FILE (set above in the "Font detection" block, not here).
|
||||
# drawtext requires a .ttf path, not a font name.
|
||||
#
|
||||
# fontcolor Text color. Accepts named colors (yellow, white, red, cyan,
|
||||
# orange, lime, magenta, gray) or hex (0xFFCC00, 0xFF8800).
|
||||
# The "@N" suffix is opacity 0.0–1.0; comes from
|
||||
# SUNRISE_OVERLAY_OPACITY in sky-cam.conf. Examples:
|
||||
# fontcolor=white@0.7 # softer white, 70% opaque
|
||||
# fontcolor=0xFF8800@0.9 # warm orange, 90% opaque
|
||||
#
|
||||
# fontsize Pixel height of glyphs. "h/22" = video-height / 22, so it
|
||||
# scales with resolution (1080p → ~49px, 720p → ~33px). Use a
|
||||
# smaller divisor for BIGGER text:
|
||||
# fontsize=h/30 → small fontsize=h/22 → current
|
||||
# fontsize=h/18 → medium fontsize=h/14 → large
|
||||
# Or set an absolute pixel size: fontsize=48
|
||||
#
|
||||
# line_spacing Pixels of gap between the stacked characters. Increase for
|
||||
# a more spaced look (e.g. 8 or 12); 0 packs them tight.
|
||||
#
|
||||
# x , y Position of the text box. ffmpeg exposes:
|
||||
# w = video width h = video height
|
||||
# tw = text width th = text height
|
||||
# Current values place the box in the BOTTOM-RIGHT with an
|
||||
# 18px right margin and a bottom margin of 18px + 5% of height.
|
||||
# Recipes for the other corners (keep ~18px breathing room):
|
||||
# Top-left: x=18 y=18
|
||||
# Top-right: x=w-tw-18 y=18
|
||||
# Bottom-left: x=18 y=h-th-18-h*0.05
|
||||
# Bottom-right: x=w-tw-18 y=h-th-18-h*0.05 (current)
|
||||
# Centered: x=(w-tw)/2 y=(h-th)/2
|
||||
# Centered top: x=(w-tw)/2 y=24
|
||||
# To nudge the current spot, change the "18" margins or the
|
||||
# "h*0.05" lift (5% above the bottom edge — raise to 0.08 to
|
||||
# clear a status bar, lower to 0.0 to sit flush at the bottom).
|
||||
#
|
||||
# shadowcolor / shadowx / shadowy
|
||||
# A 1-pixel black drop-shadow @55% opacity for legibility against
|
||||
# bright sky. Set shadowx=shadowy=0 to disable. For a thicker
|
||||
# outline, swap the shadow* lines for:
|
||||
# :borderw=2:bordercolor=black@0.7
|
||||
# (borderw = outline thickness in pixels.)
|
||||
#
|
||||
# Other useful drawtext options you can append with ":name=value":
|
||||
# box=1:boxcolor=black@0.4:boxborderw=6 # solid background pill
|
||||
# alpha='if(lt(t,1),t,1)' # 1-second fade-in
|
||||
# enable='between(t,2,8)' # only show 2s–8s
|
||||
#
|
||||
# Full reference: https://ffmpeg.org/ffmpeg-filters.html#drawtext
|
||||
DT=""
|
||||
if [ "${SUNRISE_OVERLAY_ENABLED:-true}" = "true" ]; then
|
||||
# Vertical layout: write each char of "HH:MM" on its own line.
|
||||
# SR_TIME is exactly 5 chars (e.g. "06:42"); indices 0..4 below.
|
||||
# Switch to horizontal by replacing this printf with: printf '%s' "$SR_TIME" > "$temp_text"
|
||||
temp_text=$(mktemp --suffix=.txt)
|
||||
printf '%s\n%s\n%s\n%s\n%s' \
|
||||
"${SR_TIME:0:1}" "${SR_TIME:1:1}" "${SR_TIME:2:1}" "${SR_TIME:3:1}" "${SR_TIME:4:1}" \
|
||||
> "$temp_text"
|
||||
|
||||
# Source the glyph file + the text. fontfile is omitted if FONT detection
|
||||
# failed above, in which case ffmpeg falls back to its built-in font.
|
||||
if [ -n "$FONT" ]; then
|
||||
DT="drawtext=fontfile='${FONT}':textfile='${temp_text}'"
|
||||
else
|
||||
DT="drawtext=textfile='${temp_text}'"
|
||||
fi
|
||||
|
||||
# Color + opacity. Change "yellow" to any color name or 0xRRGGBB.
|
||||
# Opacity comes from SUNRISE_OVERLAY_OPACITY in sky-cam.conf.
|
||||
DT="${DT}:fontcolor=yellow@${SUNRISE_OVERLAY_OPACITY}"
|
||||
|
||||
# Size + spacing. fontsize is relative to video height (h/22).
|
||||
DT="${DT}:fontsize=h/22:line_spacing=4"
|
||||
|
||||
# Position — bottom-right corner. See the corner recipes in the comment
|
||||
# block above. The "18" values are pixel margins; "h*0.05" lifts the box
|
||||
# 5% of the height off the bottom so it doesn't kiss the frame edge.
|
||||
DT="${DT}:x=w-tw-18:y=h-th-18-h*0.05"
|
||||
|
||||
# Drop-shadow for legibility. Set shadowx=shadowy=0 to disable, or swap
|
||||
# for :borderw=2:bordercolor=black@0.7 for a thicker outline.
|
||||
DT="${DT}:shadowcolor=black@0.55:shadowx=1:shadowy=1"
|
||||
fi
|
||||
|
||||
|
||||
@@ -174,6 +174,38 @@ LATITUDE="${LATITUDE:-0.0000}"
|
||||
LONGITUDE="${LONGITUDE:-0.0000}"
|
||||
TIMEZONE="${TIMEZONE:-America/New_York}"
|
||||
|
||||
# ── Weather tagging (OpenWeather) ─────────────────────────────────────────────
|
||||
# When enabled, ambient-record.sh queries OpenWeather's "Current weather"
|
||||
# endpoint just before each 30-minute audio chunk and appends a one-word
|
||||
# weather tag to the filename, e.g.:
|
||||
# east-08-30-00-thunderstorm.m4a
|
||||
# east-09-00-00-rain.m4a
|
||||
# east-09-30-00-cloudy.m4a
|
||||
# That way each chunk is labelled with what was happening DURING that chunk —
|
||||
# a foggy dawn won't mislabel the sunny afternoon files.
|
||||
#
|
||||
# Tag vocabulary (fixed set — anything else → no tag):
|
||||
# thunderstorm rain rainshower snow snowstorm
|
||||
# fog windy cloudy clear
|
||||
#
|
||||
# Setup:
|
||||
# 1. Get a free API key: https://openweathermap.org/api
|
||||
# (the free "Current weather" tier — 60 calls/min — is plenty)
|
||||
# 2. Add to .env: OPENWEATHER_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
|
||||
# 3. Confirm LATITUDE / LONGITUDE are set in .env (already needed by sunrise.py)
|
||||
# 4. Flip WEATHER_ENABLED=true below
|
||||
#
|
||||
# The query uses the same LATITUDE / LONGITUDE configured above — one location
|
||||
# for the whole installation; per-camera coordinates are not supported.
|
||||
#
|
||||
# Failure-safe: if the toggle is off, the key is missing, the network is down,
|
||||
# or the API errors out, weather-tag.sh prints nothing and the recorder falls
|
||||
# back to the un-tagged filename. ambient-record.sh never blocks on this.
|
||||
#
|
||||
# Manual test: ./weather-tag.sh (prints e.g. "cloudy" or nothing)
|
||||
WEATHER_ENABLED=false
|
||||
OPENWEATHER_API_KEY="${OPENWEATHER_API_KEY:-}"
|
||||
|
||||
# ── Cameras ───────────────────────────────────────────────────────────────────
|
||||
# List every camera name here (space-separated inside the parentheses).
|
||||
# Each name becomes a subfolder under BASE_DIR (images) and BASE_DIR/movies.
|
||||
|
||||
@@ -90,5 +90,7 @@ retain="${SUNRISE_AUDIO_RETENTION_DAYS:-7}"
|
||||
if [ "$retain" -gt 0 ]; then
|
||||
audio_root="${AUDIO_DIR:-$BASE_DIR/audio}/sunrise/$CAM"
|
||||
deleted=$(find "$audio_root" -name "sunrise-audio.m4a" -mtime +"$retain" -print -delete 2>/dev/null | wc -l)
|
||||
[ "$deleted" -gt 0 ] && echo "Retention: deleted $deleted sunrise audio clip(s) older than ${retain}d"
|
||||
[ "$deleted" -gt 0 ] && echo "Retention: deleted $deleted sunrise audio clip(s) older than ${retain}d" || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
#!/bin/bash
|
||||
# weather-tag.sh — print a one-word weather tag for the current moment at
|
||||
# (LATITUDE, LONGITUDE), using the OpenWeather "Current weather" endpoint.
|
||||
#
|
||||
# Output: a single lowercase word from this fixed vocabulary, or empty:
|
||||
# thunderstorm rain rainshower snow snowstorm
|
||||
# fog windy cloudy clear
|
||||
#
|
||||
# Used by ambient-record.sh to suffix each 30-minute chunk's filename so the
|
||||
# file collection self-documents what the audio actually captured.
|
||||
#
|
||||
# Failure-safe: if WEATHER_ENABLED≠true, the API key is missing, the network
|
||||
# is down, the API returns an error, or the response can't be parsed, this
|
||||
# script prints NOTHING and exits 0. Callers MUST treat empty output as
|
||||
# "no tag — fall back to the un-tagged filename". Never blocks the recorder.
|
||||
#
|
||||
# Required setup:
|
||||
# 1. Sign up at https://openweathermap.org/api → free "Current weather" plan
|
||||
# 2. Put the key in .env: OPENWEATHER_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
|
||||
# 3. Confirm LATITUDE / LONGITUDE are set in .env (already needed by sunrise.py)
|
||||
# 4. Flip WEATHER_ENABLED=true in sky-cam.conf
|
||||
#
|
||||
# Manual test: ./weather-tag.sh (prints e.g. "cloudy" or nothing)
|
||||
|
||||
set -uo pipefail # NOT -e: we want to swallow every failure into "no tag"
|
||||
|
||||
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
|
||||
source "$SCRIPT_DIR/sky-cam.conf" 2>/dev/null || exit 0
|
||||
source "$SCRIPT_DIR/.env" 2>/dev/null || true
|
||||
|
||||
[ "${WEATHER_ENABLED:-false}" = "true" ] || exit 0
|
||||
[ -n "${OPENWEATHER_API_KEY:-}" ] || exit 0
|
||||
[ -n "${LATITUDE:-}" ] || exit 0
|
||||
[ -n "${LONGITUDE:-}" ] || exit 0
|
||||
|
||||
# ── Fetch current conditions ─────────────────────────────────────────────────
|
||||
# 5s connect / 8s total — must never delay the recorder noticeably.
|
||||
url="https://api.openweathermap.org/data/2.5/weather?lat=${LATITUDE}&lon=${LONGITUDE}&appid=${OPENWEATHER_API_KEY}&units=metric"
|
||||
resp=$(curl -s --connect-timeout 5 --max-time 8 "$url" 2>/dev/null) || exit 0
|
||||
[ -n "$resp" ] || exit 0
|
||||
|
||||
# ── Parse condition ID + wind speed ──────────────────────────────────────────
|
||||
# Condition codes: https://openweathermap.org/weather-conditions
|
||||
# OpenWeather may return multiple entries in weather[]; the first is primary.
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
code=$(echo "$resp" | jq -r '.weather[0].id // empty' 2>/dev/null)
|
||||
wind=$(echo "$resp" | jq -r '.wind.speed // 0' 2>/dev/null)
|
||||
else
|
||||
# Fallback parser if jq is missing — grabs the first "id":NNN it sees.
|
||||
code=$(echo "$resp" | grep -oE '"id":[0-9]+' | head -n1 | grep -oE '[0-9]+')
|
||||
wind=$(echo "$resp" | grep -oE '"speed":[0-9.]+' | head -n1 | grep -oE '[0-9.]+')
|
||||
fi
|
||||
|
||||
[ -n "${code:-}" ] || exit 0
|
||||
wind="${wind:-0}"
|
||||
|
||||
# ── Map condition code → tag (vocabulary documented at top of file) ──────────
|
||||
# Group ranges follow OpenWeather's published condition-code table:
|
||||
# 2xx thunder / 3xx drizzle / 5xx rain / 6xx snow / 7xx atmosphere / 800 clear / 80x clouds
|
||||
tag=""
|
||||
case "$code" in
|
||||
2*) tag="thunderstorm" ;;
|
||||
3*) tag="rainshower" ;; # drizzle = light shower
|
||||
500|520|521|522|531) tag="rainshower" ;; # light / shower variants
|
||||
501|502|503|504|511) tag="rain" ;; # moderate → extreme + freezing
|
||||
600|601|612|613|615|616|620|621) tag="snow" ;;
|
||||
602|622) tag="snowstorm" ;; # heavy snow / heavy shower snow
|
||||
611) tag="snow" ;; # sleet
|
||||
701|711|721|741|762) tag="fog" ;; # mist / smoke / haze / fog / ash
|
||||
731|751|761|771) tag="windy" ;; # dust whirls / sand / dust / squall
|
||||
781) tag="thunderstorm" ;; # tornado → reuse strongest tag
|
||||
800) tag="clear" ;;
|
||||
801) tag="clear" ;; # few clouds — still mostly clear
|
||||
802|803|804) tag="cloudy" ;;
|
||||
*) tag="" ;; # unknown code → no tag
|
||||
esac
|
||||
|
||||
# Wind override: gusty conditions drown out other audio cues. If the base
|
||||
# tag is clear/cloudy and wind ≥ 8 m/s (~18 mph), promote to "windy".
|
||||
if [ "$tag" = "clear" ] || [ "$tag" = "cloudy" ]; then
|
||||
# bash can't compare floats — strip the decimal for an integer check.
|
||||
wind_int="${wind%.*}"
|
||||
[ "${wind_int:-0}" -ge 8 ] 2>/dev/null && tag="windy"
|
||||
fi
|
||||
|
||||
[ -n "$tag" ] && printf '%s' "$tag"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user