affaan-m/ECC · error · ValueError

source clip is too short for its no-repeat cadence slot

Error message

source clip is too short for its no-repeat cadence slot

What it means

In the final assignment loop, each unique source clip has a capacity — the maximum whole frames it can fill given its duration and the frame rate (`floor(duration * rate)`, plus one when the boundary fits). If a clip's capacity is less than the frame count its slot requires, the clip is too short for that slot and the strict no-repeat plan fails.

Solutions

  1. Replace the too-short clip with a longer source that covers the slot's frame count
  2. Trim the cadence/mean_shot so slots require fewer frames
  3. Regenerate cadence measurements to match the actual fps of the media
  4. Pre-validate each clip: `floor(clip_duration * fps_fraction) >= frames_needed` before calling

Example fix

# before
{"path": "short.mp4", "duration": 0.05}  # slot needs 3 frames @30fps
# after
{"path": "longer.mp4", "duration": 1.2}
Defensive patterns

Strategy: validation

Validate before calling

rate = timeline.fps_fraction(fps)
planned = plan_shots(cadence, target)
for clip in media:
    cap = math.floor(Fraction(str(clip["duration"])) * rate)
    if cap < 1:
        raise ValueError(f"clip {clip['path']} shorter than one frame")

Prevention

When it happens

Trigger: A media clip shorter than the cadence slot it was matched to, e.g. a 0.1s clip assigned a 5-frame slot at 30fps (needs ~0.167s): `capacity < count` in the zip of sources to frame_counts.

Common situations: Mixing short B-roll clips with long measured shot cadences; cadence.json measured from a high-fps edit applied to short source footage; rounding at low fps making effective capacity smaller than expected.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/715c0012c0e8584a. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/apply.py:95

    rate = timeline.fps_fraction(fps)
    sources: dict[str, tuple[dict[str, Any], int]] = {}
    for clip in media:
        path = str(Path(clip["path"]).expanduser().resolve())
        # Floor rational capacity: rounding up could read past the source end.
        capacity = math.floor(Fraction(str(clip["duration"])) * rate)
        # Accept a boundary serialized as a float only when the frame duration
        # itself compares within the supplied duration; no broad epsilon.
        if float((capacity + 1) / rate) <= clip["duration"]:
            capacity += 1
        if path in sources:
            raise ValueError("no-repeat media must contain unique normalized source paths")
        sources[path] = ({**clip, "path": path}, capacity)
    if len(sources) < len(frame_counts):
        raise ValueError("no-repeat plan requires more unique source clips")
    assignments = []
    for (clip, capacity), count in zip(sources.values(), frame_counts):
        if capacity < count:
            raise ValueError("source clip is too short for its no-repeat cadence slot")
        assignments.append((clip, count))
    return assignments



def plan_shots(cadence: dict[str, Any], target_duration: float) -> list[float]:
    """Propose shot durations filling ``target_duration`` at this cadence.

    Samples from the reference's own shot-length distribution (seeded, like
    the recovered ``Cadence.plan_shots``) so the plan inherits rhythm
    variance instead of flattening into evenly spaced clips.
    """
    target_duration = _positive(target_duration, "target duration")
    durations = [
        _positive(s["duration"], "cadence shot duration")
        for s in cadence.get("shots", [])
        if isinstance(s, dict) and _positive(s.get("duration"), "cadence shot duration") > _MIN_SHOT
    ]

View on GitHub (pinned to 8321021c54)