muvid.footage.select_score

The score-driven weighted selection strategy: a beat-snapped semi-Markov Viterbi DP.

Given the fused score tensor S[clip, frame, metric] (from muvid.footage.scoring.grid) and the master beat grid, choose which clip is on-air over each span of the song so that the weighted composite reward is maximized, cuts land only on beat (or beat∪shot) boundaries, shot lengths obey [L_min, L_max], and each switch pays a Potts penalty λ_switch. The same optimizer the Phase-2 editor steers (a manual pin becomes a hard constraint a re-solve fills around).

The recurrence is the corrected form from the design’s algorithm review (misc/docs/footage_scoring_design.md §4c′) — the load-bearing details:

  • Feasibility is CONTINUOUS containment (b_i ≥ offset_c − _EPS and b_j ≤ offset_c + duration_c + _EPS), the exact check validate_edl runs — NOT a grid-frame mask (a sub-hop overhang would slip past a 10 Hz mask and make validate_edl raise). So the emitted EDL passes validate_edl by construction.

  • Reward is a TIME integral of the normalized composite ĝ = Σ_m w_m·S·M / W ∈ [0,1]: R(c,i,j) = hop_s·(P[c][κ(b_j)] − P[c][κ(b_i)]) (prefix-summed → O(1) per segment, hop/tempo-independent). A switch then costs λ_switch directly in those units (“a cut must earn λ_switch seconds of perfect-footage reward”).

  • L_min is relaxed on the first & last segment; an infeasible timeline is classified (coverage gap vs dwell-infeasible) and retried with L_min→0 before any fallback — never a silent junk EDL.

  • The allowed(i,j) clip-domain hook + the injectable boundary set make the Phase-2 manual-pin re-solve a pruning, not a reformulation.

  • An unvouched alignment is a reward penalty, not a pruning (muvid#88): a clip the aligner will not vouch for is charged UNVOUCHED_REWARD_PENALTY reward-seconds per second, which is larger than the composite’s whole range and therefore ranks trust above every METRIC, while leaving the clip reachable where it is the only coverage. Pruning it would be a second trust gate; scoring it as a metric column would let sharpness outbid it. It does not dominate the DP’s non-composite terms — see the constant, which says what it does and does not buy.

numpy only (no cv2/torch): registered LAZILY in muvid.footage.strategy so import muvid.footage never pulls numpy.

Module Attributes

DEFAULT_WEIGHTS

Default per-metric weights.

PRESETS

Named presets — a filled-in config.

UNVOUCHED_REWARD_PENALTY

Reward-seconds per second charged to a clip the aligner will not vouch for.

Functions

resolve_config(*[, preset, weights, config])

Build a WeightedSelectionConfig from an optional preset + overrides.

run_weighted(alignments, song_duration, context)

Run the DP and return (edl, meta); meta carries any fallback + its cause.

selection_margin(alignments, tensor, *[, ...])

Per-frame best_composite − 2nd_best_composite over clips COVERING that frame.

weighted_selection(alignments, song_duration, *)

The weighted SelectionStrategy (score-driven DP).

Classes

SelectionContext(tensor[, beat_times, ...])

Everything the score-driven strategy needs beyond (alignments, song_duration).

WeightedSelectionConfig([weights, ...])

The score-driven "strategy" as a pure config object (open-closed).

muvid.footage.select_score.DEFAULT_WEIGHTS: dict[str, float] = {'exposure': 0.3, 'face_framing': 0.4, 'lip_sync_lse_c': 1.0, 'motion_beat_bas': 0.8, 'motion_onset_xcorr': 0.5, 'sharpness': 0.4, 'stability_shake': 0.3}

Default per-metric weights. A metric absent from the tensor collapses to weight 0 (its column simply doesn’t exist), so a project scored without the lip-sync tier still selects cleanly on the metrics it has.

muvid.footage.select_score.PRESETS: dict[str, WeightedSelectionConfig] = {'contemplative': WeightedSelectionConfig(weights={'lip_sync_lse_c': 1.0, 'motion_beat_bas': 0.8, 'motion_onset_xcorr': 0.5, 'sharpness': 0.6, 'exposure': 0.3, 'face_framing': 0.6, 'stability_shake': 0.3}, lambda_switch=0.6, l_min_s=3.0, l_max_s=12.0, l_max_overrun_penalty=0.05, boundary_mode='beats', beat_unit='beat'), 'energetic': WeightedSelectionConfig(weights={'lip_sync_lse_c': 1.0, 'motion_beat_bas': 1.0, 'motion_onset_xcorr': 0.8, 'sharpness': 0.4, 'exposure': 0.3, 'face_framing': 0.4, 'stability_shake': 0.3}, lambda_switch=0.2, l_min_s=0.8, l_max_s=4.0, l_max_overrun_penalty=0.25, boundary_mode='beats', beat_unit='beat')}

Named presets — a filled-in config. “energetic” = many short cuts; “contemplative” = long dwells, few cuts.

class muvid.footage.select_score.SelectionContext(tensor, beat_times=(), downbeat_times=(), shot_boundaries=None, pins=None, config=<factory>)[source]

Bases: object

Everything the score-driven strategy needs beyond (alignments, song_duration).

tensor is the fused ScoreTensor (or None → the strategy raises a clear “run scoring first”). shot_boundaries (clip_id → song-times) is consumed only when config.boundary_mode == "beats+shots". pins (Phase 2) is a list of (song_start, song_end, clip_id) hard constraints.

muvid.footage.select_score.UNVOUCHED_REWARD_PENALTY = 2.0

Reward-seconds per second charged to a clip the aligner will not vouch for. The composite ĝ is normalized to [0, 1], so anything > 1.0 dominates it: over the same span a vouched clip at ĝ=0 outscores an unvouched one at ĝ=1, whatever the weights say. It is a demotion and not a prohibition — where nothing else covers the span the DP must still use the clip, pays the penalty on every path equally, and the choice is unaffected.

What it does NOT buy, measured rather than assumed. “No weighting can buy an unvouched clip a span another clip covers” is true of the reward INTEGRAL and false of the DP as a whole, because two of the DP’s terms are not composite reward at all:

  • l_max_overrun_penalty is caller-settable through config. At 0.9, a single 40 s vouched clip is cut away from every 10 s: measured, BAD at 10-12, 20-22 and 30-32 s, all of them spans the vouched clip covers.

  • _viterbi’s transition window max_seg_s (4x l_max = 32 s at the defaults) is a PERFORMANCE bound, and consecutive segments must be different clips — so a vouched take longer than the cap cannot be one segment and the optimizer is forced to cut away and back. Measured under the default config, a 2 s opener plus one 58 s vouched clip gives [(0,2,O), (2,34,V), (34,36,BAD), (36,60,V)].

Raising the penalty does not fix either: both are structural, not a scoring tie the penalty is competing in. What repairs them is _absorb_neighbour(), which hands such a span back to the vouched cut on either side of it — losslessly, because that clip already covers it — rather than letting the recovery gap footage the shoot actually has. Keep that pairing in mind before changing either half.

Not a config field, and not a tensor column, deliberately. A column would be commensurate with sharpness and lip-sync — a sharp unvouched clip could outrank a dull vouched one, which is exactly the trade muvid#88 says must not be available — and a config field would let the preference be tuned away silently by a caller passing config= to the MCP tool.

class muvid.footage.select_score.WeightedSelectionConfig(weights=<factory>, lambda_switch=0.35, l_min_s=1.2, l_max_s=8.0, l_max_overrun_penalty=0.15, boundary_mode='beats', beat_unit='beat')[source]

Bases: object

The score-driven “strategy” as a pure config object (open-closed).

Adding a metric = a tensor column + a weight; no new strategy code.

l_max_s is a SOFT target: a shot longer than it pays l_max_overrun_penalty per second of overrun (in the same “reward-seconds” units as lambda_switch), rather than being forbidden. So a lone/best clip rolls past l_max_s when the only alternative is materially worse (no forced cutaway to bad footage), yet comparable clips still cut around l_max_s for variety. Segments are always different-clip (each = one shot).

muvid.footage.select_score.resolve_config(*, preset=None, weights=None, config=None)[source]

Build a WeightedSelectionConfig from an optional preset + overrides.

Precedence: preset (or the default) < config dict fields < explicit weights.

Return type:

WeightedSelectionConfig

muvid.footage.select_score.run_weighted(alignments, song_duration, context)[source]

Run the DP and return (edl, meta); meta carries any fallback + its cause.

Return type:

tuple[list[EdlEntry], dict]

muvid.footage.select_score.selection_margin(alignments, tensor, *, weights=None)[source]

Per-frame best_composite − 2nd_best_composite over clips COVERING that frame.

A LOCAL “where a human should decide” proxy (small margin = a toss-up), distinct from the DP’s global path optimality. NaN where fewer than 2 clips cover the frame.

Return type:

ndarray

muvid.footage.select_score.weighted_selection(alignments, song_duration, *, context=None)[source]

The weighted SelectionStrategy (score-driven DP).

Requires a SelectionContext carrying a score tensor. Raises a clear error if scores are absent so the MCP layer can say “run muvid_score_footage first”.

Return type:

list[EdlEntry]