#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" STATE_FILE="${SCRIPT_DIR}/.personality_index" IN_DIR="${1:?Usage: $0 [\"Title\"] [\"Location\"] [max_frames] [additional_prompt]}" TITLE="${2:-}" LOCATION="${3:-}" MAX_FRAMES="${4:-12}" ADDITIONAL_PROMPT="${5:-}" SELECTED_PERSONALITY="${6:-}" if [ "$ADDITIONAL_PROMPT" = "no-append" ]; then ADDITIONAL_PROMPT="" fi # Source system-wide OpenRouter API key if present so the webserver can run this as www-data. if [ -f /etc/mvlog/openrouter.env ]; then # shellcheck disable=SC1091 . /etc/mvlog/openrouter.env fi : "${OPENROUTER_API_KEY:?Set OPENROUTER_API_KEY first in the environment}" MODEL="${OPENROUTER_MODEL:-openai/gpt-4o-mini}" if [ "$MODEL" = "openrouter/auto" ] && [ "${OPENROUTER_ALLOW_AUTO:-}" != "1" ]; then echo "WARN: openrouter/auto is unreliable for strict JSON; using openai/gpt-4o-mini. Set OPENROUTER_MODEL to a specific vision model, or OPENROUTER_ALLOW_AUTO=1 to force auto." >&2 MODEL="openai/gpt-4o-mini" fi OPENROUTER_MAX_RETRIES="${OPENROUTER_MAX_RETRIES:-3}" OPENROUTER_SITE_URL="${OPENROUTER_SITE_URL:-https://bubulescu.org}" OPENROUTER_SITE_NAME="${OPENROUTER_SITE_NAME:-MVLog}" WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/mvlog.XXXXXX")" FRAMES_DIR="$WORKDIR/frames" mkdir -p "$FRAMES_DIR" cleanup() { rm -rf "$WORKDIR" } trap cleanup EXIT echo "DEBUG: WORKDIR=$WORKDIR" >&2 mapfile -t FILES < <( find "$IN_DIR" -maxdepth 1 -type f \( \ -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' -o -iname '*.gif' -o \ -iname '*.mp4' -o -iname '*.mov' -o -iname '*.m4v' -o -iname '*.webm' -o -iname '*.mkv' \ \) -printf '%f\n' | sort -V ) if [ "${#FILES[@]}" -eq 0 ]; then echo "No media files found in $IN_DIR" >&2 exit 1 fi frame_count=0 extract_image_frame() { local src="$1" dst="$2" ffmpeg -hide_banner -loglevel error -y -i "$src" -vf "scale=1024:-1" -q:v 4 "$dst" } extract_video_frame_at() { local src="$1" time="$2" dst="$3" ffmpeg -hide_banner -loglevel error -y -ss "$time" -i "$src" -frames:v 1 -vf "scale=1024:-1" -q:v 4 "$dst" } for f in "${FILES[@]}"; do full="$IN_DIR/$f" ext="${f##*.}" ext_l="${ext,,}" if [[ "$ext_l" =~ ^(mp4|mov|m4v|webm|mkv)$ ]]; then duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$full" 2>/dev/null || echo 0) duration=${duration:-0} if awk "BEGIN{exit !($duration >= 6)}"; then nframes=3 elif awk "BEGIN{exit !($duration >= 3)}"; then nframes=2 else nframes=1 fi remaining=$((MAX_FRAMES - frame_count)) if [ "$remaining" -le 0 ]; then break fi if [ "$nframes" -gt "$remaining" ]; then nframes=$remaining fi i=1 while [ "$i" -le "$nframes" ]; do time=$(awk -v d="$duration" -v i="$i" -v n="$nframes" 'BEGIN{ if(d<=0){print 0} else {printf "%.3f", (i / (n + 1)) * d }}') out="$FRAMES_DIR/frame_$(printf "%03d" "$frame_count").jpg" extract_video_frame_at "$full" "$time" "$out" frame_count=$((frame_count + 1)) i=$((i + 1)) if [ "$frame_count" -ge "$MAX_FRAMES" ]; then break 2 fi done else out="$FRAMES_DIR/frame_$(printf "%03d" "$frame_count").jpg" extract_image_frame "$full" "$out" frame_count=$((frame_count + 1)) if [ "$frame_count" -ge "$MAX_FRAMES" ]; then break fi fi done mapfile -t FRAMES < <(find "$FRAMES_DIR" -type f -name '*.jpg' | sort) if [ "${#FRAMES[@]}" -eq 0 ]; then echo "No frames produced from media" >&2 exit 1 fi if [ -z "$TITLE" ]; then TITLE="$(basename "$IN_DIR")" fi ADDITIONAL_PROMPT_BLOCK="" if [ -n "$ADDITIONAL_PROMPT" ]; then ADDITIONAL_PROMPT_BLOCK=$(cat <