#!/usr/bin/env python3 """ density_floor.py — synthesise scene boundaries when detection is starved. Flood-fill presence snaps each actor claim to the shot it sits in, so a film whose boundary detector fires almost nothing (Scarface: 1 cut in 171 min) floods every actor across the whole film. This is a safety floor: when a film's DETECTED boundary density is far below what a working detector should produce, fill the long gaps between real detections with uniformly-spaced synthetic boundaries so no flood-fill span can exceed ~1/target-density. Design points (measured on the X-Ray corpus): - The target density is a PRIOR from the central 60 min of films (avoids credits/ intro/outro skew): median ~0.35 scenes/min. - The trigger is detected-vs-prior, not prior-vs-anything: only fire when detected density < TRIGGER_FRAC × prior. Legitimately sparse films (long-scene ensembles like Downton/Many Saints) detect fine and are left alone. - Real detections are never moved or dropped; synthetic boundaries only subdivide gaps that are longer than the target scene length. """ from __future__ import annotations PRIOR_SCENES_PER_MIN = 0.35 # central-60min X-Ray median TRIGGER_FRAC = 0.30 # fire only when detected < 30% of prior def apply_density_floor(boundaries: list[float], duration_sec: float, prior_per_min: float = PRIOR_SCENES_PER_MIN, trigger_frac: float = TRIGGER_FRAC) -> list[float]: """Return boundaries augmented with synthetic ones iff detection is starved. boundaries: detected boundary timestamps (s), any order. duration_sec: film length. Returns a sorted list; unchanged (just sorted) when the film is not starved. """ b = sorted(t for t in boundaries if 0.0 < t < duration_sec) minutes = duration_sec / 60.0 if minutes <= 0: return b detected_density = len(b) / minutes if detected_density >= trigger_frac * prior_per_min: return b # detector produced a reasonable amount — leave it alone target_gap = 60.0 / prior_per_min # seconds per expected scene edges = [0.0] + b + [duration_sec] out = list(b) for lo, hi in zip(edges[:-1], edges[1:]): gap = hi - lo if gap <= target_gap: continue n_insert = int(gap // target_gap) # how many synthetic cuts fit step = gap / (n_insert + 1) for k in range(1, n_insert + 1): out.append(lo + k * step) return sorted(out) if __name__ == "__main__": # self-check on the Scarface failure and a healthy film scar = apply_density_floor([88.0], 171*60) # 1 detected cut, 171 min print(f"Scarface: 1 detected → {len(scar)} after floor " f"({len(scar)/171:.2f}/min, prior {PRIOR_SCENES_PER_MIN})") healthy = apply_density_floor([i*130.0 for i in range(1, 47)], 122*60) print(f"healthy (46 detected/122min={46/122:.2f}/min): " f"{len(healthy)} after floor (unchanged = not triggered)")