spec: fold in operator approval + pano-projector design change

Operator approved the spec and confirmed first-ship archives
(LibriVox + NASA + IA). Folds in the 2026-06-04 design change (single
panoramic projector across all three walls, nature-video content):
demotes dominant_color to optional/opt-in (its only consumer was the
procedural side walls), ffmpeg-only when computed, never an image lib.
Flags the design-spec/ROADMAP follow-up as out of scope here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-04 06:06:45 -07:00
parent f80ec65553
commit 4c1ec6e02e
@@ -22,7 +22,8 @@ calls for (§5), under `tools/`:
pool and write a catalog record.
2. **Mechanical tagging** — tooling auto-fills the non-curatorial fields: `mode`
(via `ffprobe`), `license`/`attribution`/`source_*` (from origin),
`duration_s`, `resolution`, and `dominant_color` (computed from the video).
`duration_s`, `resolution`, and — optionally, opt-in only — `dominant_color`
(computed from the video; see §5.2 design-change note).
3. **Coordinate drafting** — a proposer suggests `left/right/dark/light` with a
one-line `rationale`, written as `review_status: proposed`.
4. **Review CLI** — walks `proposed` records one at a time (proposed coords +
@@ -193,29 +194,35 @@ derives:
`tools/probe.py` takes the subprocess runner as an injectable dependency
(default: `subprocess.run`); tests feed canned `ffprobe` JSON and never shell out.
### 5.2 `dominant_color` — computed from the video (`tools/mediatools.py`)
### 5.2 `dominant_color` — optional, deferred (computed from the video only on demand)
For `video`/`av` records, extract a representative frame mid-segment and reduce it
to one hex color. **Primary approach is `ffmpeg`-only** (keeps the dependency
surface to just `ffmpeg`/`ffprobe` — no image library):
> **Design-change note (2026-06-04):** `dominant_color`'s only consumer was the
> **procedural side walls** (design §7): hue for the three non-primary walls. The
> installation is moving to a **single panoramic projector that spans all three
> walls showing the real (nature-video) content**, which removes the procedural
> side-wall renderer — and with it the need to *compute* a dominant color. So this
> spec **demotes `dominant_color` from a core mechanical-tagging requirement to an
> optional, opt-in computation.** This is a knock-on of a larger design shift that
> primarily affects sub-projects 3 and 5 and the design spec (see §11 follow-up).
```
# representative frame near the middle, reduced to a single-entry palette
ffmpeg -v quiet -ss <mid> -i <file> -vf "thumbnail,palettegen=max_colors=1" -frames:v 1 -f rawvideo -pix_fmt rgb24 -
```
Concretely:
The 3 output bytes are the dominant palette color → `"#rrggbb"`. (A simpler
`scale=1:1` average-color path is the fallback if `palettegen` is unavailable.)
Audio-only records keep `dominant_color=""`.
- The `dominant_color` **field stays** in the schema (it is already there; default
`""`) — no schema change either way.
- Ingest does **not** compute it by default. An opt-in flag
(`--dominant-color`) enables computation for `video`/`av` records, for the case
the operator still wants it (e.g. a later ambient/lighting use).
- **When enabled, it is `ffmpeg`-only — never an image library.** Extract a
representative mid-segment frame and reduce it to one hex color:
```
ffmpeg -v quiet -ss <mid> -i <file> -vf "thumbnail,palettegen=max_colors=1" -frames:v 1 -f rawvideo -pix_fmt rgb24 -
```
The 3 output bytes are the dominant palette color → `"#rrggbb"` (a `scale=1:1`
average-color path is the fallback if `palettegen` is unavailable). This honors
the operator's "no video manipulation needed" steer: no Pillow, no new dep, and
the work isn't done at all unless explicitly requested.
> **Open decision (logged):** the roadmap assumed "an image lib for dominant
> color." This spec instead recommends the **ffmpeg-only** path so the whole
> sub-project depends on nothing beyond `ffmpeg`/`ffprobe`. **Pillow** (k-means /
> `quantize` over a sampled frame) is the documented fallback if ffmpeg-derived
> color quality proves insufficient in review. Chosen for the lean dependency
> story; reversible.
`dominant_color` feeds the side walls (sub-project 5, design §7).
Audio-only records always keep `dominant_color=""`.
### 5.3 `license` / `attribution` / `source_*` — from origin (`tools/licensing.py`)
@@ -469,8 +476,9 @@ Mirrors sub-project 1's TDD discipline (test-first, stdlib, hermetic).
2. **Mechanical tagging** — canned `ffprobe` JSON → expected `mode`/`duration_s`/
`resolution`; the **cover-art `attached_pic` guard** keeps an art-bearing MP3 at
`mode="audio"`.
3. **Dominant color** — canned ffmpeg `rgb24` bytes → expected `"#rrggbb"`;
audio-only → `""`.
3. **Dominant color (opt-in path)** — with the flag on, canned ffmpeg `rgb24`
bytes → expected `"#rrggbb"`; with the flag off (default), `dominant_color`
stays `""`; audio-only → `""`.
4. **License normalization** — sample per-archive metadata → correct `license`;
`cc_by`/`cc_by_nc` produce non-empty `attribution`; unmappable license is
rejected.
@@ -504,13 +512,17 @@ fixture clip → `mode`/`duration`/`resolution`; real dominant-color extraction.
None block writing the implementation plan; each has a recommended default the
plan will assume unless the operator overrides.
1. **Dominant-color dependency***Recommend:* ffmpeg-only (`palettegen`), no
image lib; Pillow as fallback. (Roadmap had assumed an image lib.)
1. **Dominant-color dependency** — *Decided (operator, 2026-06-04):* `dominant_color`
is **optional / opt-in** and, when computed, **ffmpeg-only — no image lib**,
because the move to a single panoramic projector showing real nature video
removes the procedural side walls that were its only consumer (§5.2).
2. **`file_path` style** — *Recommend:* relative-to-`media-root` for portability;
reconcile the USER_GUIDE's absolute examples with a doc note; player spec
defines mount resolution.
3. **Which archives ship first***Recommend:* LibriVox, NASA, Internet
Archive/Prelinger first; defer Freesound, Musopen, FMA (auth / API-stability tax).
3. **Which archives ship first** — *Decided (operator, 2026-06-04):* **LibriVox,
NASA, Internet Archive/Prelinger** first; defer Freesound, Musopen, FMA (auth /
API-stability tax). (Nature-video content focus weights NASA/IA; the audio
pools still serve the catalog's audio modes.)
4. **CLI surface** — *Recommend:* two `python -m` entry points (`tools.ingest_cli`,
`tools.review_cli`), matching the no-install repo convention; console-script
aliases optional.
@@ -522,14 +534,27 @@ archive-identifier field, `media_sha256` integrity, an `ingested_at` timestamp,
any LLM-backed proposer in the core. All are future, additive extensions; none are
needed for the done-criteria.
**Design-spec follow-up (beyond this sub-project):** the operator's 2026-06-04
decision to use a **single panoramic projector spanning all three walls, showing
real nature video**, supersedes the design spec's §6 (four projectors) / §7
(procedural side-wall renderer) and reshapes sub-projects **3** (player drives one
pano output, not primary + side feed) and **5** (procedural side walls likely
dropped). The design spec
(`2026-06-04-human-experience-filter-design.md`) and ROADMAP should be updated to
reflect this in a separate pass; it is **out of scope for the sub-project-2 spec**,
whose only affected element is the now-optional `dominant_color` (§5.2).
---
## 12. Relationship to the other sub-projects
- **Consumes** sub-project 1 (`hef.catalog`, `hef.selection`) — the only required
change is the additive `validate_catalog` (+ optional `index_by_id`), §3.
- **Feeds** sub-project 3 (the Pi player): a populated, `approved` catalog with
`dominant_color` ready for sub-project 5's side walls. The player resolving
- **Feeds** sub-project 3 (the Pi player): a populated, `approved` catalog the
player drives to the (now single, panoramic) projector. The player resolving
`file_path` against its drive mount (§6.3) is a player-spec concern.
- **Side walls (sub-project 5)** are likely **dropped** under the pano-projector
design change (§11 follow-up); `dominant_color`, their only input, is
correspondingly optional here (§5.2).
- **Settles** two roadmap open decisions (download/caching layout §6.3; ingest
language = Python under `tools/`, §4).