Increased word-character numbers

This commit is contained in:
hbrain 2026-05-31 10:36:16 +02:00
parent df2bcca66e
commit c827a2ae03
3 changed files with 8 additions and 254 deletions

View file

@ -1,242 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
IN_DIR="${1:?Usage: $0 <input-dir> [\"Title\"] [\"Location\"] [max_frames]}"
TITLE="${2:-}"
LOCATION="${3:-}"
MAX_FRAMES="${4:-12}"
# Source system-wide Gemini API key if present so the webserver can run this as www-data.
if [ -f /etc/mvlog/gemini.env ]; then
# shellcheck disable=SC1090
. /etc/mvlog/gemini.env
fi
: "${GEMINI_API_KEY:?Set GEMINI_API_KEY first in the environment}"
MODEL="${GEMINI_MODEL:-gemini-2.5-flash}"
TMPDIR="${TMPDIR:-$(mktemp -d)}"
FRAMES_DIR="$TMPDIR/frames"
mkdir -p "$FRAMES_DIR"
# Print TMPDIR for debugging when run interactively
echo "DEBUG: TMPDIR=$TMPDIR" >&2
cleanup() {
rm -rf "$TMPDIR"
}
trap cleanup EXIT
# Collect media files (natural sort)
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
# Helper: extract scaled JPEG from image
extract_image_frame() {
local src="$1" dst="$2"
ffmpeg -hide_banner -loglevel error -y -i "$src" -vf "scale=1024:-1" -q:v 4 "$dst"
}
# Helper: extract frame from video at time
extract_video_frame_at() {
local src="$1" time="$2" dst="$3"
# Use -ss before -i for fast seek
ffmpeg -hide_banner -loglevel error -y -ss "$time" -i "$src" -frames:v 1 -vf "scale=1024:-1" -q:v 4 "$dst"
}
# Process files: images -> one frame each; videos -> up to 3 frames evenly spaced (subject to MAX_FRAMES)
for f in "${FILES[@]}"; do
full="$IN_DIR/$f"
ext="${f##*.}"
ext_l="${ext,,}"
if [[ "$ext_l" =~ ^(mp4|mov|m4v|webm|mkv)$ ]]; then
# video: probe duration
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$full" 2>/dev/null || echo 0)
duration=${duration:-0}
# choose frames per video
if awk "BEGIN{print ($duration >= 6)}" | grep -q 1; then
nframes=3
elif awk "BEGIN{print ($duration >= 3)}" | grep -q 1; then
nframes=2
else
nframes=1
fi
# limit to remaining slots
remaining=$((MAX_FRAMES - frame_count))
if [ "$remaining" -le 0 ]; then break; fi
if [ "$nframes" -gt "$remaining" ]; then nframes=$remaining; fi
# compute times and extract
i=1
while [ $i -le $nframes ]; do
# time = (i/(n+1)) * duration
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
# image
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
# Gather produced frames
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
# Build prompt for Gemini
if [ -z "$TITLE" ]; then
# Default title: directory basename
TITLE="$(basename "$IN_DIR")"
fi
PROMPT=$(cat <<EOF
You write short descriptions for my motorcycle/travel blog.
You should talk like the main character from TV show Shoresy - profane, passionate,
and hilariously obsessive titular protagonist of the acclaimed Canadian ice hockey comedy series.
Use real quotes from the show as much as possible.
Title: "$TITLE"
Location: "$LOCATION"
Look at these captured frames and write JSON only.
Required JSON format:
{
"description": "2-3 relaxed human sentences",
"teaser": "one short teaser sentence",
"tags": ["tag1", "tag2", "tag3", "tag4", "tag5"]
}
Style:
personal, natural, slightly casual, not corporate, not clickbait.
Do not invent facts that are not visible in the images or provided in title/location.
EOF
)
PARTS_FILE="$TMPDIR/parts.json"
# Create parts with the prompt
if ! command -v jq >/dev/null 2>&1; then
echo "jq is required" >&2
exit 1
fi
# Build parts and request JSON using Python to avoid argument-list limits
PROMPT_FILE="$TMPDIR/prompt.txt"
printf "%s" "$PROMPT" > "$PROMPT_FILE"
FRAMES_LIST="$TMPDIR/frames.list"
printf "%s\n" "${FRAMES[@]}" > "$FRAMES_LIST"
REQUEST_FILE="$TMPDIR/request.json"
RESPONSE_FILE="$TMPDIR/response.json"
python3 - "$PROMPT_FILE" "$REQUEST_FILE" "$FRAMES_LIST" <<'PY'
import sys, json, base64
prompt_file = sys.argv[1]
request_file = sys.argv[2]
frames_list = sys.argv[3]
with open(prompt_file, 'r', encoding='utf-8') as f:
prompt = f.read()
parts = [{"text": prompt}]
with open(frames_list, 'r', encoding='utf-8') as fl:
for line in fl:
img = line.strip()
if not img:
continue
with open(img, 'rb') as fh:
b64 = base64.b64encode(fh.read()).decode('ascii')
parts.append({"inline_data": {"mime_type": "image/jpeg", "data": b64}})
req = {"contents": [{"parts": parts}], "generationConfig": {"temperature": 0.7, "responseMimeType": "application/json"}}
with open(request_file, 'w', encoding='utf-8') as rf:
json.dump(req, rf, ensure_ascii=False)
PY
echo "Calling Gemini API..."
echo "DEBUG_MODEL=$MODEL" >&2
curl -sS \
"https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent?key=${GEMINI_API_KEY}" \
-H "Content-Type: application/json" \
-X POST \
-d @"$REQUEST_FILE" \
> "$RESPONSE_FILE"
# debug: save request/response for inspection
cp "$REQUEST_FILE" "$IN_DIR/debug_request.json" || true
cp "$RESPONSE_FILE" "$IN_DIR/debug_response.json" || true
chown www-data:www-data "$IN_DIR/debug_request.json" "$IN_DIR/debug_response.json" 2>/dev/null || true
TEXT=$(jq -r '.candidates[0].content.parts[0].text // empty' "$RESPONSE_FILE")
if [ -z "$TEXT" ]; then
echo "Gemini returned no text. Full response:" >&2
cat "$RESPONSE_FILE" >&2
exit 1
fi
# Save raw Gemini JSON for reference
OUT_JSON="$IN_DIR/gemini_generated.json"
echo "$TEXT" | jq . > "$OUT_JSON"
# Extract fields from Gemini JSON
DESCRIPTION=$(echo "$TEXT" | jq -r '.description // empty')
TEASER=$(echo "$TEXT" | jq -r '.teaser // empty')
# tags as comma-separated
TAGS=$(echo "$TEXT" | jq -r '.tags // [] | join(", ")')
# Determine a Date string (Month Year) from first media file mtime
firstfile="$(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 '%p\n' | sort | head -n1)"
DATE_STR=""
if [ -n "$firstfile" ]; then
DATE_STR=$(date -r "$firstfile" +"%B %Y")
fi
# Update data.txt: only add a Description block if none exists already. Do NOT modify other fields.
DATA_FILE="$IN_DIR/data.txt"
# If Gemini provided no explicit 'description' field, fall back to the full response text.
if [ -z "$DESCRIPTION" ]; then
DESCRIPTION="$TEXT"
fi
if [ -f "$DATA_FILE" ]; then
if grep -qi '^[[:space:]]*description[[:space:]]*:' "$DATA_FILE"; then
echo "data.txt already contains a Description; leaving unchanged" >&2
else
{
echo ""
echo "Description: |"
printf '%s\n' "$DESCRIPTION" | sed 's/^/ /'
} >> "$DATA_FILE"
chown www-data:www-data "$DATA_FILE" 2>/dev/null || true
chmod 664 "$DATA_FILE" 2>/dev/null || true
echo "Appended Description to $DATA_FILE" >&2
fi
else
{
echo "Description: |"
printf '%s\n' "$DESCRIPTION" | sed 's/^/ /'
} > "$DATA_FILE"
chown www-data:www-data "$DATA_FILE" 2>/dev/null || true
chmod 664 "$DATA_FILE" 2>/dev/null || true
echo "Created $DATA_FILE with Description" >&2
fi
# Save the full Gemini output separately (already done) and exit
echo "Gemini JSON saved: $OUT_JSON"
exit 0

View file

@ -107,19 +107,20 @@ if [ -z "$TITLE" ]; then
fi fi
PERSONALITIES=( PERSONALITIES=(
"You should talk like the main character from TV show Shoresy - profane, passionate, and hilariously obsessive titular protagonist "You should talk like the main character from TV show Shoresy - profane, passionate, and hilariously obsessive titular protagonist of the acclaimed Canadian ice hockey comedy series. Be funny and original."
of the acclaimed Canadian ice hockey comedy series. Use real quotes from the show. Be funny and original."
"You are Marvin, the paranoid Android - highly intelligent but chronically depressed robot from The Hitchhiker's Guide to the Galaxy. "You should talk like Winston Churchill when he was giving motivational speeches during WWII."
Try to be funny."
"You are Marvin, the Paranoid Android - the highly intelligent but chronically depressed robot from The Hitchhiker's Guide to the Galaxy. Try to be funny."
) )
# Pick random prompt # Pick a random personality
PERSONALITY="${PERSONALITIES[$RANDOM % ${#PERSONALITIES[@]}]}" idx=$(( RANDOM % ${#PERSONALITIES[@]} ))
PERSONALITY="${PERSONALITIES[$idx]}"
PROMPT=$(cat <<EOF PROMPT=$(cat <<EOF
"$PERSONALITY" "$PERSONALITY"
Look at these captured frames from a trip. Synthesize them into one single, coherent description (not less than 100 or more than 140 words) Look at these captured frames from a trip. Synthesize them into one single, coherent description (make it between 1280 to 1920 characters)
that summarizes the overall experience for my motorcycle/travel blog. that summarizes the overall experience for my motorcycle/travel blog.
Title: "$TITLE" Title: "$TITLE"

View file

@ -1,5 +0,0 @@
#!/usr/bin/env bash
TMPDIR=/tmp/gendata_debug_run
mkdir -p "$TMPDIR"
export TMPDIR
/var/www/html/mvlog/bin/generate_data.sh "/var/www/html/mvlog/in-dir/20230630_napoli" "Naples" "Naples, Italy" 8