chore(refactor): unify the two ClipEntry implementations

Author: alexandremendoncaalvaroCreated May 1, 2026Updated May 1, 2026

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 library ClipEntry.
  • backend.service.CorridorKeyService (the layer the GUI talks to) → consumes the service ClipEntry.

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 library ClipEntry.
  • backend/service.py:_peek_first_frame_for_color — works against the service ClipEntry.

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:

  1. Scope. PR #241 was already touching 9 files and ~700 lines of code. Adding a foundational refactor to it would have made review impossible.
  2. 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.
  3. 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 ClipEntry and one ClipAsset definition in the codebase, importable from a single canonical location.
  • clip_manager.run_inference and backend.service.CorridorKeyService.run_inference both 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 ClipState lifecycle (RAW → READY → COMPLETE), which the library version lacks.
  • get_frame_files() belongs in the model, not in scattered os.listdir calls.
  • The GUI is the more demanding consumer; library callers can adapt to the richer surface easily.

Step 2 — bridge the field rename (typeasset_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_length logic in clip_manager.ClipAsset and backend.clip_state.ClipAsset is 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 resulting ClipEntry lists.

Out of scope for this issue

  • Renaming the discovery functions themselves (scan_clips vs scan_clips_dir).
  • Adding a ClipState lifecycle to the CLI library path. That's a separate UX decision.
  • Migrating ClipsForInference directory layout in any way.

Source: nikopueringer/CorridorKey