affaan-m/ECC · error · ValueError

at least one placement is required

Error message

at least one placement is required

What it means

allocate_placements refuses to produce a plan with zero placements. After validating each entry, if none survived (or the input list was empty), it throws because downstream allocation, track assignment, and readback all assume at least one event.

Solutions

  1. Pass at least one placement dict with asset, start, end
  2. Guard the call site: skip apply entirely when the computed list is empty
  3. Fix the upstream filtering logic that removed all placements
  4. Validate placements input before calling

Example fix

// before
apply_placements(timeline, pool, placements=filtered)  # filtered == []
// after
if not filtered:
    return  # nothing to apply
apply_placements(timeline, pool, placements=filtered)
Defensive patterns

Strategy: validation

Validate before calling

if not placements:
    raise ValueError("no placements to apply; check upstream filtering")

Type guard

def is_nonempty_list(v) -> bool:
    return isinstance(v, list) and len(v) > 0

Prevention

When it happens

Trigger: Calling allocate_placements/apply_placements/plan with an empty placements list, or with a list where all entries were filtered out before this check.

Common situations: Programmatically built placement lists that ended up empty due to an upstream filter; config file with a placeholder empty array; a caller passing None coerced to [].

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/resolve.py:143

        info = metadata[key]
        if _fps(info.get("fps")) != rate:
            raise ValueError(f"asset fps differs from timeline: {asset}")
        if _integer(info.get("frames"), "asset frames", 1) < frames:
            raise ValueError(f"asset contains too few frames: {asset}")
        if alpha and info.get("has_alpha") is not True:
            raise ValueError(f"asset requires verified alpha: {asset}")
        checked.append(
            {
                "id": identifier,
                "asset": key,
                "record_frame": start,
                "frames": frames,
                "opacity": opacity,
                "composite": composite,
            }
        )
    if not checked:
        raise ValueError("at least one placement is required")
    ends, allocated = [], {}
    for event in sorted(checked, key=lambda entry: entry["record_frame"]):
        index = next(
            (i for i, end in enumerate(ends) if end <= event["record_frame"]), len(ends)
        )
        if index == len(ends):
            ends.append(0)
        ends[index] = event["record_frame"] + event["frames"]
        allocated[event["id"]] = {**event, "track": base_track_count + index + 1}
    return [allocated[event["id"]] for event in checked]


def _path(item):
    media = item.GetMediaPoolItem()
    raw = media.GetClipProperty("File Path") if media else None
    return str(Path(raw).expanduser().resolve()) if raw else None

View on GitHub (pinned to 8321021c54)