Files
timmy-config/tests/test_generate_scenes_from_media.py
Alexander Whitestone a2e61f6def
Some checks failed
Architecture Lint / Linter Tests (pull_request) Successful in 21s
Smoke Test / smoke (pull_request) Failing after 15s
Validate Config / YAML Lint (pull_request) Failing after 18s
Validate Config / JSON Validate (pull_request) Successful in 21s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 1m3s
Validate Config / Python Test Suite (pull_request) Has been skipped
Validate Config / Shell Script Lint (pull_request) Failing after 1m11s
Validate Config / Cron Syntax Check (pull_request) Successful in 15s
Validate Config / Deploy Script Dry Run (pull_request) Successful in 14s
Validate Config / Playbook Schema Validation (pull_request) Successful in 27s
PR Checklist / pr-checklist (pull_request) Failing after 12m35s
Architecture Lint / Lint Repository (pull_request) Failing after 22s
feat: auto-generate scene descriptions from image/video assets (#689)
scripts/generate_scenes_from_media.py:
  Scans assets dir for images/videos (jpg/png/mp4/mov/etc)
  Calls vision model (llava/gpt-4/claude) to describe scenes
  Outputs training pairs: image_path -> scene description
  Includes provenance: model, timestamp, source_session_id
  --assets dir, --output file, --model, --max, --dry-run
  JSON parsing with fallback for plain text responses

tests/test_generate_scenes_from_media.py: 12 tests
  find_media_files: images, videos, max limit, missing dir
  file_hash: consistent, different files
  generate_prompt: image vs video
  parse_description: JSON, plain text
  generate_training_pair: structure, video type

Usage:
  python3 scripts/generate_scenes_from_media.py --assets ~/assets/
  python3 scripts/generate_scenes_from_media.py --assets ~/assets/ --model gpt-4
  python3 scripts/generate_scenes_from_media.py --assets ~/assets/ --dry-run
2026-04-21 07:22:28 -04:00

116 lines
3.8 KiB
Python

"""
Tests for scripts/generate_scenes_from_media.py — Media scene description generator.
"""
import json
import os
import tempfile
import unittest
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from generate_scenes_from_media import (
find_media_files,
file_hash,
generate_description_prompt,
parse_description,
generate_training_pair,
IMAGE_EXTENSIONS,
VIDEO_EXTENSIONS,
)
class TestFindMediaFiles(unittest.TestCase):
def test_finds_images(self):
with tempfile.TemporaryDirectory() as tmpdir:
Path(tmpdir, "test.jpg").touch()
Path(tmpdir, "test.png").touch()
Path(tmpdir, "test.txt").touch() # not media
files = find_media_files(tmpdir)
self.assertEqual(len(files), 2)
def test_finds_videos(self):
with tempfile.TemporaryDirectory() as tmpdir:
Path(tmpdir, "video.mp4").touch()
Path(tmpdir, "video.mov").touch()
files = find_media_files(tmpdir)
self.assertEqual(len(files), 2)
def test_max_limits_results(self):
with tempfile.TemporaryDirectory() as tmpdir:
for i in range(10):
Path(tmpdir, f"img{i}.jpg").touch()
files = find_media_files(tmpdir, max_files=3)
self.assertEqual(len(files), 3)
def test_missing_dir_returns_empty(self):
files = find_media_files("/nonexistent/path")
self.assertEqual(files, [])
class TestFileHash(unittest.TestCase):
def test_consistent_hash(self):
path = Path("/test/file.jpg")
h1 = file_hash(path)
h2 = file_hash(path)
self.assertEqual(h1, h2)
def test_different_files_different_hash(self):
h1 = file_hash(Path("/test/a.jpg"))
h2 = file_hash(Path("/test/b.jpg"))
self.assertNotEqual(h1, h2)
class TestGenerateDescriptionPrompt(unittest.TestCase):
def test_image_prompt(self):
prompt = generate_description_prompt(Path("test.jpg"))
self.assertIn("image", prompt.lower())
self.assertIn("mood", prompt.lower())
self.assertIn("colors", prompt.lower())
def test_video_prompt(self):
prompt = generate_description_prompt(Path("test.mp4"))
self.assertIn("video", prompt.lower())
self.assertIn("camera movement", prompt.lower())
class TestParseDescription(unittest.TestCase):
def test_parses_json(self):
text = '{"mood": "calm", "colors": ["blue", "white"], "composition": "wide shot", "camera": "static", "description": "A serene lake"}'
result = parse_description(text)
self.assertEqual(result["mood"], "calm")
self.assertEqual(result["colors"], ["blue", "white"])
def test_handles_plain_text(self):
text = "This is a description of a scene with mood calm and colors blue, white."
result = parse_description(text)
self.assertIn("description", result)
class TestGenerateTrainingPair(unittest.TestCase):
def test_pair_structure(self):
filepath = Path("/test/photo.jpg")
description = {"mood": "happy", "colors": ["gold"], "composition": "close-up", "camera": "static", "description": "Smiling face"}
pair = generate_training_pair(filepath, description, "llava")
self.assertEqual(pair["source_file"], str(filepath))
self.assertEqual(pair["media_type"], "image")
self.assertEqual(pair["model"], "llava")
self.assertIn("source_session_id", pair)
self.assertIn("timestamp", pair)
self.assertEqual(pair["scene"]["mood"], "happy")
def test_video_pair(self):
filepath = Path("/test/video.mp4")
description = {"mood": "energetic"}
pair = generate_training_pair(filepath, description, "gpt-4")
self.assertEqual(pair["media_type"], "video")
if __name__ == "__main__":
unittest.main()