diff --git a/.gitignore b/.gitignore index 1b26cd1..e226dc7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ *.pyc .venv/ node_modules/ +.worktrees/ # Local agent/browser state .local/ diff --git a/docs/superpowers/plans/2026-04-26-asset-library.md b/docs/superpowers/plans/2026-04-26-asset-library.md new file mode 100644 index 0000000..721bd63 --- /dev/null +++ b/docs/superpowers/plans/2026-04-26-asset-library.md @@ -0,0 +1,251 @@ +# Asset Library Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Generate a media-type-first asset library under each run folder so humans can browse videos, images, logos, poster frames, thumbnails, and variants without navigating source-mirrored URLs. + +**Architecture:** Extend `scripts/save_visual_assets.py` after the existing raw download path. The raw `assets/discovered/` archive remains the canonical downloaded copy, and a new library layer copies retrieved files into `assets/library/` with readable filenames plus JSON/TSV manifests under `assets/manifests/`. + +**Tech Stack:** Python standard library, `unittest`, existing shell/Python watch scripts. + +--- + +### Task 1: Metadata Extraction Helpers + +**Files:** +- Modify: `scripts/save_visual_assets.py` +- Test: `tests/test_save_visual_assets.py` + +- [ ] **Step 1: Write failing tests** + +Add tests that build a fake run with WordPress media JSON containing: + +```python +{ + "id": 433313, + "slug": "3-in-1-2", + "mime_type": "video/mp4", + "post": 433326, + "source_url": "https://komodostation.com/wp-content/uploads/2026/04/3-in-1.mp4", + "title": {"rendered": "3 in 1"}, + "media_details": { + "width": 1920, + "height": 850, + "filesize": 12242748, + "sizes": {}, + }, +} +``` + +Expected helper output: + +```python +{ + "media_id": "433313", + "parent_id": "433326", + "title": "3 in 1", + "width": 1920, + "height": 850, + "filesize": 12242748, +} +``` + +- [ ] **Step 2: Run tests and confirm failure** + +Run: + +```bash +python3 -m unittest tests/test_save_visual_assets.py +``` + +Expected: failure because metadata helper functions do not exist. + +- [ ] **Step 3: Implement metadata helpers** + +Add small helpers in `scripts/save_visual_assets.py`: + +```python +def extract_media_metadata(obj: dict, source_path: str) -> dict: + details = obj.get("media_details") if isinstance(obj.get("media_details"), dict) else {} + title = obj.get("title") if isinstance(obj.get("title"), dict) else {} + return { + "media_id": str(obj.get("id") or ""), + "parent_id": str(obj.get("post") or ""), + "title": title.get("rendered") or obj.get("slug") or "", + "slug": obj.get("slug") or "", + "mime": obj.get("mime_type") or "", + "width": details.get("width") or "", + "height": details.get("height") or "", + "filesize": details.get("filesize") or "", + "source_json": source_path, + } +``` + +Update collection to store metadata alongside URL and MIME. + +- [ ] **Step 4: Run tests and confirm pass** + +Run: + +```bash +python3 -m unittest tests/test_save_visual_assets.py +``` + +Expected: all tests pass. + +### Task 2: Library Classification And Filenames + +**Files:** +- Modify: `scripts/save_visual_assets.py` +- Test: `tests/test_save_visual_assets.py` + +- [ ] **Step 1: Write failing tests** + +Add tests for these classification outcomes: + +```python +("video/mp4", "3-in-1.mp4", "3 in 1", 1920, 850) -> "videos" +("image/svg+xml", "controllerLogo.svg", "controllerLogo", "", "") -> "logos" +("image/png", "videoframe_789.png", "videoframe_789", 1920, 1080) -> "poster-frames" +("image/avif", "d760...-150x150.avif", "spec image", 150, 150) -> "thumbnails" +("image/avif", "d760...-2048x988.avif", "spec image", 2048, 988) -> "variants" +("image/avif", "d760....avif", "spec image", 2048, 988) -> "images" +``` + +Add a filename test: + +```python +build_library_filename(metadata, url, "steam-controller") == "steam-controller_433313_3-in-1_1920x850.mp4" +``` + +- [ ] **Step 2: Run tests and confirm failure** + +Run: + +```bash +python3 -m unittest tests/test_save_visual_assets.py +``` + +Expected: failure because classification and filename helpers do not exist. + +- [ ] **Step 3: Implement helpers** + +Add helpers: + +- `slugify(value: str) -> str` +- `has_size_suffix(url: str) -> bool` +- `is_thumbnail_size(width, height, url) -> bool` +- `infer_hardware(metadata, url, sources) -> str` +- `classify_library_category(metadata, url) -> str` +- `build_library_filename(metadata, url, hardware) -> str` + +Use `steam-controller`, `steam-machine`, `steam-frame`, and `unknown` hardware slugs. + +- [ ] **Step 4: Run tests and confirm pass** + +Run: + +```bash +python3 -m unittest tests/test_save_visual_assets.py +``` + +Expected: all tests pass. + +### Task 3: Generate Library Files And Manifests + +**Files:** +- Modify: `scripts/save_visual_assets.py` +- Test: `tests/test_save_visual_assets.py` + +- [ ] **Step 1: Write failing integration test** + +Create a test fake run containing a video, logo, poster frame, thumbnail, and variant. Mock downloads with valid bytes. Run `save_visual_assets.main()` and assert: + +```text +assets/library/videos/steam-controller_433313_3-in-1_1920x850.mp4 +assets/library/logos/steam-controller_413728-controller-logo.svg +assets/library/poster-frames/steam-controller_433298_videoframe-789_1920x1080.png +assets/library/thumbnails/steam-controller_433298_videoframe-789_150x150.png +assets/library/variants/steam-controller_433477_spec-image_2048x988.avif +assets/manifests/asset-library.json +assets/manifests/asset-library.tsv +``` + +Assert manifest rows include original URL, raw path, library path, category, hardware, media ID, parent ID, dimensions, MIME, detected format, and source JSON. + +- [ ] **Step 2: Run tests and confirm failure** + +Run: + +```bash +python3 -m unittest tests/test_save_visual_assets.py +``` + +Expected: failure because library generation does not exist. + +- [ ] **Step 3: Implement library generation** + +After a download is retrieved or found cached: + +1. Build metadata. +2. Choose category. +3. Build a readable filename. +4. Copy the raw downloaded file into `assets/library//`. +5. Resolve filename collisions by appending `-2`, `-3`, etc. +6. Write JSON and TSV manifests under `assets/manifests/`. + +- [ ] **Step 4: Run tests and confirm pass** + +Run: + +```bash +python3 -m unittest tests/test_save_visual_assets.py +``` + +Expected: all tests pass. + +### Task 4: Regenerate Current Run And Verify + +**Files:** +- Runtime artifacts: `/Users/sean/steam_hardware_watch/2026-04-26/assets/library` +- Runtime artifacts: `/Users/sean/steam_hardware_watch/2026-04-26/assets/manifests` + +- [ ] **Step 1: Regenerate assets** + +Run: + +```bash +python3 scripts/save_visual_assets.py --run-dir /Users/sean/steam_hardware_watch/2026-04-26 --base-dir /Users/sean/steam_hardware_watch +``` + +- [ ] **Step 2: Verify media-type folders** + +Run: + +```bash +find /Users/sean/steam_hardware_watch/2026-04-26/assets/library -maxdepth 2 -type f | sed 's#.*/assets/library/##' | sort | head -n 80 +``` + +Expected: files appear under `videos/`, `images/`, `logos/`, `poster-frames/`, `thumbnails/`, and `variants/`. + +- [ ] **Step 3: Verify manifests** + +Run: + +```bash +python3 -m json.tool /Users/sean/steam_hardware_watch/2026-04-26/assets/manifests/asset-library.json >/dev/null +test -s /Users/sean/steam_hardware_watch/2026-04-26/assets/manifests/asset-library.tsv +``` + +Expected: exit 0. + +- [ ] **Step 4: Run final tests** + +Run: + +```bash +python3 -m py_compile scripts/save_visual_assets.py +python3 -m unittest tests/test_save_visual_assets.py +``` + +Expected: all tests pass.