chore(refactor): unify the two ClipEntry implementations
Context
The codebase has two parallel ClipEntry classes that look superficially similar but expose subtly different APIs. Both represent the same domain concept — a clip directory containing input frames + an alpha hint — and both are widely used. They predate PR #241; the duplication is pre-existing tech debt.
| Layer | File | Defines | Asset-type field | Has get_frame_files() |
Has ClipState enum |
|---|---|---|---|---|---|
| Library / CLI | clip_manager.py lines ~117–200 |
ClipEntry, ClipAsset |
asset.type |
❌ | ❌ |
| Service / GUI | backend/clip_state.py |
ClipEntry, ClipAsset, ClipState |
asset.asset_type |
✅ | ✅ |
Both are imported and used today:
clip_manager.run_inference,generate_alphas,run_birefnet,run_videomama→ consume the libraryClipEntry.backend.service.CorridorKeyService(the layer the GUI talks to) → consumes the serviceClipEntry.
The two are not interchangeable: a function written against one will break on the other (asset.type vs asset.asset_type is an AttributeError, not a typo).
Why this matters now
PR #241 made the duplication concretely expensive. The blue-screen feature added a "peek the first frame of a clip" helper used in two places:
clip_manager.py:_peek_first_frame_with_alpha— works against the libraryClipEntry.backend/service.py:_peek_first_frame_for_color— works against the serviceClipEntry.
The implementations are 95% identical, but the field names diverge (type vs asset_type) and the file-listing API differs (os.listdir filtered vs get_frame_files()). Any future feature that walks a clip's frames will hit the same wall.
There's also a one-line note in PR #241 acknowledging this: "two helpers exist because the two ClipEntry shapes don't agree on field names."
Why we didn't fix it inline with #241
Three reasons:
- Scope. PR #241 was already touching 9 files and ~700 lines of code. Adding a foundational refactor to it would have made review impossible.
- API decision. The "right" answer requires choosing which API wins as canonical, and that's a decision that should be made deliberately, not piggybacked onto a feature PR. The service version is richer (
ClipState,get_frame_files), but the library version is the public-facing API the README documents. - Risk. Touching every caller of
scan_clips,generate_alphas,run_inference, etc. has real regression risk. It needs its own test pass.
Definition of done
- One
ClipEntryand oneClipAssetdefinition in the codebase, importable from a single canonical location. clip_manager.run_inferenceandbackend.service.CorridorKeyService.run_inferenceboth consume the same type.- The two
_peek_first_frame_*helpers are merged into one shared helper. - All existing tests pass with no behaviour change for users (the GUI keeps working, the CLI keeps working, scan/discovery still finds the same clips).
- README references and docstrings updated where they mention the discovery objects.
Suggested approach
Step 1 — pick the canonical home. Recommendation: keep the richer backend/clip_state.py definition as canonical and have clip_manager.py import ClipEntry/ClipAsset/ClipState from there. Reasons:
- It already supports
ClipStatelifecycle (RAW → READY → COMPLETE), which the library version lacks. get_frame_files()belongs in the model, not in scatteredos.listdircalls.- The GUI is the more demanding consumer; library callers can adapt to the richer surface easily.
Step 2 — bridge the field rename (type → asset_type). Audit every asset.type == and asset.type != site in clip_manager.py, corridorkey_cli.py, and tests. There are ~12 such sites. grep -nE "asset\.type\b" will catch them.
Step 3 — replace os.listdir filters with get_frame_files(). Same approach: grep, replace, verify tests.
Step 4 — fold _peek_first_frame_with_alpha (clip_manager) and _peek_first_frame_for_color (service) into a single helper, probably in backend/clip_state.py next to ClipEntry. Pass it the input_is_linear flag for EXR handling (PR #241's M5 fix should be preserved).
Step 5 — verify all 427 existing tests still pass, then rerun the manual smoke (a green clip + a blue clip end-to-end through the CLI and the service).
Risk and scope estimate
- Files touched:
clip_manager.py,backend/clip_state.py(consolidation),backend/service.py(cleanup), 4–6 test files, README if it documents the field names. - Net diff: roughly −150 / +80 lines (mostly deletions from removing the duplicate class).
- Risk: medium. The
_calculate_lengthlogic inclip_manager.ClipAssetandbackend.clip_state.ClipAssetis similar but not identical (e.g. video frame-count fallback paths differ slightly). Any subtle behavioural divergence here will show up as a clip discovery regression. Recommend a regression matrix: scan the same fixture directory with the old code and the new code, diff the resultingClipEntrylists.
Out of scope for this issue
- Renaming the discovery functions themselves (
scan_clipsvsscan_clips_dir). - Adding a
ClipStatelifecycle to the CLI library path. That's a separate UX decision. - Migrating
ClipsForInferencedirectory layout in any way.
Source: nikopueringer/CorridorKey