{"record":{"id":"bb31fa08454ac369","repo":"affaan-m/ECC","slug":"cadence-shot-cannot-occupy-a-whole-frame","errorCode":null,"errorMessage":"cadence shot cannot occupy a whole frame","messagePattern":"cadence shot cannot occupy a whole frame","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/apply.py","lineNumber":71,"sourceCode":"\n\ndef _strict_assign(\n    planned: list[float], media: list[dict[str, Any]], target: float, fps: float\n) -> list[tuple[dict[str, Any], int]]:\n    \"\"\"Fill the target frame count, then match whole shots to unique sources.\"\"\"\n    target_frames = timeline.seconds_to_frames(target, fps)\n    if target_frames < 1:\n        raise ValueError(\"target duration must contain at least one frame\")\n    frame_counts = []\n    elapsed = 0.0\n    assigned = 0\n    for duration in planned:\n        if assigned == target_frames:\n            break\n        elapsed += duration\n        boundary = min(target_frames, timeline.seconds_to_frames(elapsed, fps))\n        if boundary <= assigned:\n            raise ValueError(\"cadence shot cannot occupy a whole frame\")\n        frame_counts.append(boundary - assigned)\n        assigned = boundary\n    if assigned < target_frames:\n        frame_counts.append(target_frames - assigned)\n\n    rate = timeline.fps_fraction(fps)\n    sources: dict[str, tuple[dict[str, Any], int]] = {}\n    for clip in media:\n        path = str(Path(clip[\"path\"]).expanduser().resolve())\n        # Floor rational capacity: rounding up could read past the source end.\n        capacity = math.floor(Fraction(str(clip[\"duration\"])) * rate)\n        # Accept a boundary serialized as a float only when the frame duration\n        # itself compares within the supplied duration; no broad epsilon.\n        if float((capacity + 1) / rate) <= clip[\"duration\"]:\n            capacity += 1\n        if path in sources:\n            raise ValueError(\"no-repeat media must contain unique normalized source paths\")\n        sources[path] = ({**clip, \"path\": path}, capacity)","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/apply.py#L53-L89","documentation":"While walking the planned cadence shots, `_strict_assign` accumulates elapsed time and converts it to a frame boundary. If a shot is so short that the boundary does not advance past the current assigned frame (`boundary <= assigned`), the shot cannot occupy a whole frame and the strict no-repeat assignment is impossible, so it raises.","triggerScenarios":"A cadence containing a very short shot (e.g. 0.001s at 30fps, far less than one frame period) such that `seconds_to_frames(elapsed, fps)` does not advance by at least one frame from the previous boundary.","commonSituations":"Over-aggressive shot detection producing sub-frame cuts; camera-flash or strobe artifacts detected as shots; cadence.json generated at a different fps than the one used at apply time (e.g. measured at 60fps, applied at 24fps making shots sub-frame).","solutions":["Remove or merge shots shorter than one frame period (1/fps seconds) from the cadence","Regenerate the cadence with a higher minimum-shot-length threshold (_MIN_SHOT)","Use the same fps for planning and application as during measurement","Filter the planned durations through plan_shots with a floor before strict assignment"],"exampleFix":"// before\n{\"shots\": [{\"duration\": 0.001}, {\"duration\": 0.8}]}\n// after\nmin_shot = 1.0 / fps\n{\"shots\": [{\"duration\": 0.8}]}  # sub-frame shot dropped/merged","handlingStrategy":"validation","validationCode":"frame = 1.0 / fps\nshots = [s for s in cadence.get(\"shots\", []) if s.get(\"duration\", 0) >= frame]\nplanned = plan_shots({\"shots\": shots, \"mean_shot\": cadence.get(\"mean_shot\", frame)}, target)","typeGuard":null,"tryCatchPattern":"try:\n    report = apply_local(...)\nexcept ValueError as exc:\n    if \"whole frame\" in str(exc):\n        cadence = drop_subframe_shots(cadence, 1.0 / fps)\n        report = apply_local(...)  # retry with cleaned cadence\n    else:\n        raise","preventionTips":["Set the shot-detector minimum length to at least one frame period","Use the same fps across measurement, planning, and application","Drop or merge sub-frame shots when loading cadence.json"],"tags":["timeline","frames","cadence"],"backgroundTag":"value-out-of-range","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}