affaan-m/ECC · error · ValueError
cadence shot cannot occupy a whole frame
Error message
cadence shot cannot occupy a whole frame
What it means
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.
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
Example fix
// before
{"shots": [{"duration": 0.001}, {"duration": 0.8}]}
// after
min_shot = 1.0 / fps
{"shots": [{"duration": 0.8}]} # sub-frame shot dropped/merged Defensive patterns
Strategy: validation
Validate before calling
frame = 1.0 / fps
shots = [s for s in cadence.get("shots", []) if s.get("duration", 0) >= frame]
planned = plan_shots({"shots": shots, "mean_shot": cadence.get("mean_shot", frame)}, target) Try / catch
try:
report = apply_local(...)
except ValueError as exc:
if "whole frame" in str(exc):
cadence = drop_subframe_shots(cadence, 1.0 / fps)
report = apply_local(...) # retry with cleaned cadence
else:
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
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
- target duration must contain at least one frame
- asset contains too few frames
- base_track_count exceeds target video tracks
- base tracks changed during overlay placement
- cadence has no measured shot durations to plan from
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/bb31fa08454ac369.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/apply.py:71
def _strict_assign(
planned: list[float], media: list[dict[str, Any]], target: float, fps: float
) -> list[tuple[dict[str, Any], int]]:
"""Fill the target frame count, then match whole shots to unique sources."""
target_frames = timeline.seconds_to_frames(target, fps)
if target_frames < 1:
raise ValueError("target duration must contain at least one frame")
frame_counts = []
elapsed = 0.0
assigned = 0
for duration in planned:
if assigned == target_frames:
break
elapsed += duration
boundary = min(target_frames, timeline.seconds_to_frames(elapsed, fps))
if boundary <= assigned:
raise ValueError("cadence shot cannot occupy a whole frame")
frame_counts.append(boundary - assigned)
assigned = boundary
if assigned < target_frames:
frame_counts.append(target_frames - assigned)
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)View on GitHub (pinned to 8321021c54)