1457556ce3
_xfade_chain positioned every transition using _audio_dur, which probes format=duration, which is max(video, audio). A clip's audio outlasts its video by about a frame, so the offset accumulator crept ahead of the real picture timeline. Once the creep exceeded the transition width, xfade emitted the transition and silently discarded the second input and every clip downstream, exiting 0 with nothing on stderr. That is the whole of the shipped chapter's 436.39s of video over 363.67s of audio. Offsets now come from min(video, audio). Every input is floored to a whole frame count and trimmed on both streams, so the accumulator tracks the real timeline instead of estimating it. _check_assembled verifies each encode against the predicted length and against its own audio, because both assembly branches drop stream time without failing. Verified over the 49 real clips of chapter 7c944dd4: the round that turned 359s of video into 100s now loses 0.85s, and the chapter comes out 358.76s video against 358.76s audio. The single-item passthrough was not the cause. Two round-0 groups of 8 fresh clips collapse without one, recorded void in decisions/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
76 lines
4.1 KiB
Markdown
76 lines
4.1 KiB
Markdown
# CLAUDE.md
|
|
|
|
Goal, invariants, and working rules. Read this first.
|
|
|
|
| file | holds |
|
|
| --- | --- |
|
|
| `NEXT.md` | the current state and the live plan |
|
|
| `ROADMAP.md` | the ordered outcomes past the current one |
|
|
| `JOURNAL.md` | what was run and when, append-only |
|
|
| `decisions/` | every settled question, indexed in `decisions/CLAUDE.md` |
|
|
| `caveats/` | every known limit and its revisit trigger, indexed in `caveats/CLAUDE.md` |
|
|
| `AGENTS.md` | commands, with the traps beside them |
|
|
| `AUDIT.md` | the 2026-08-11 pipeline audit, the source of the roadmap |
|
|
| `spec-v3.md` | current quality and look work, marked DONE/TODO per item |
|
|
|
|
Do not restate a finding here. Point at the decision.
|
|
|
|
## The goal
|
|
|
|
This is the **workpc compute half** of a manga to narrated-video pipeline. It holds stateless GPU and
|
|
CPU workers only. State, job scheduling, and stage orchestration live in a separate homesrv
|
|
orchestrator repo (`/mnt/server/home/kami/docker-apps/manga-infra/orchestrator/`). The two talk over a
|
|
fixed HTTP contract. The hard part is that one GPU serves every model, so the schedule, not the model,
|
|
sets the wall time.
|
|
|
|
Machine split: workers run on **workpc** (RX 7900 GRE, ROCm). MinIO and the orchestrator run on
|
|
**homesrv** (`192.168.1.104`, CPU-only). `/mnt/server/home/kami/` is an SSHFS mount of homesrv.
|
|
|
|
## Invariants
|
|
|
|
1. **No durable state in a worker.** No sqlite, no cross-request memory. A worker pulls inputs from
|
|
MinIO by URI, does one stage, pushes outputs back, returns URIs. A `/dev/shm` cache is allowed
|
|
because it may be dropped at any time.
|
|
2. **One warm model at a time.** Every GPU stage takes a lease from `session_manager.py` on 8095. A 409
|
|
is a queue signal, never a stale lease
|
|
(`decisions/audit-phase1.md#no-lease-stealing`).
|
|
3. **Never hold `_lock` across a model load or a health wait** in `session_manager.py`
|
|
(`decisions/audit-phase1.md#unlocked-model-load`).
|
|
4. **A dialogue row's speaker is `speaker_ref`.** The flat `speaker` field is a compatibility value and
|
|
holds a `character_id`, not a panel-local id
|
|
(`decisions/audit-phase1.md#speaker-ref-is-canonical`).
|
|
5. **A stage never raises after setting `awaiting_review`.** The pipeline's catch-all overwrites it
|
|
with `failed` (`decisions/audit-phase1.md#flag-resolution`).
|
|
6. **Never mint a character from an unparseable or out-of-range model answer.** That is `unresolved`
|
|
(`decisions/audit-phase1.md#hallucinated-index`).
|
|
7. **The HTTP contract with the orchestrator is load-bearing.** Changing a worker's request or response
|
|
shape means reconciling the orchestrator in the same session. Neither repo's self-checks can catch a
|
|
contract break, because each asserts its own side.
|
|
8. **`ponytail:` comments mark deliberate simplifications** and name the upgrade path. Respect them.
|
|
9. **Nothing that positions an ffmpeg filter may use `format=duration`.** It reports `max(video, audio)`.
|
|
It hides A/V drift, and it walks xfade offsets past the end of their input. ffmpeg then discards clips
|
|
and still exits 0 (`decisions/chapter-assembly.md#offsets-from-min-stream`).
|
|
|
|
## Working rules
|
|
|
|
```bash
|
|
./start_workers.sh # dev: session_manager + 9 workers, each a uvicorn in a tmux window
|
|
tmux attach -t manga-workers # per-worker logs
|
|
sudo systemd/install.sh # production: one systemd unit per process (User=kami)
|
|
|
|
.venv/bin/python worker_scene.py # every module has an assert-based __main__ self-check
|
|
.venv/bin/python test_vision_parse.py
|
|
|
|
cd /mnt/server/home/kami/docker-apps/manga-infra/orchestrator && pytest -q --ignore=test_api.py
|
|
```
|
|
|
|
There is no lint or build step. `.venv` is the ROCm torch env. Workers import `transport` by module
|
|
name. Ports: crop 8000, vision 8002, identity 8003, scene 8004, script 8005, tts 8006, layers 8007,
|
|
render 8008, session_manager 8095.
|
|
|
|
- Any non-trivial logic gets ONE runnable check in `__main__`, assert-based, no framework. Run the file
|
|
to verify it.
|
|
- Editing a worker's request or response shape means editing the orchestrator too, in the same session.
|
|
- Update `NEXT.md` alongside any change that moves the plan, and append to `JOURNAL.md` after a run.
|
|
- Do not run GPU work or a full pipeline without asking.
|