affaan-m/ECC · error · ValueError

placement id must be unique and nonempty

Error message

placement id must be unique and nonempty

What it means

allocate_placements validates each placement event dict; it raises ValueError('placement id must be unique and nonempty') when an event's 'id' is missing, not a string, an empty string, or duplicates an id already seen in the same call. Ids identify composited placements downstream, so they must be present and unique.

Solutions

  1. Ensure every placement dict has a nonempty string 'id' and that ids are unique within the list
  2. When generating placements in a loop, append an index or uuid: f'overlay-{i}' or str(uuid4())
  3. Check for casing/key typos: the key must be exactly 'id'
  4. Deduplicate or rename conflicting ids before calling, e.g. by prefixing duplicates

Example fix

# before
placements = [{'id': 'logo', 'asset': 'a.webm'}, {'id': 'logo', 'asset': 'b.webm'}]
# after
placements = [{'id': 'logo-a', 'asset': 'a.webm'}, {'id': 'logo-b', 'asset': 'b.webm'}]
Defensive patterns

Strategy: validation

Validate before calling

def validate_placement_ids(placements):
    seen = set()
    for ev in placements:
        pid = ev.get('id')
        if not isinstance(pid, str) or not pid or pid in seen:
            raise ValueError(f'bad placement id: {pid!r}')
        seen.add(pid)

validate_placement_ids(placements)

Type guard

def has_valid_id(ev) -> bool:
    pid = ev.get('id')
    return isinstance(pid, str) and bool(pid)

Try / catch

try:
    resolve.allocate_placements(fps, base_track_count, placements)
except ValueError as e:
    if 'placement id' in str(e):
        raise PlacementError('duplicate or missing placement id') from e
    raise

Prevention

When it happens

Trigger: Calling allocate_placements/apply_placements/plan with placements where one event lacks 'id', has id='' or id=None, has a numeric id, or two events share the same id (e.g. a template loop forgot to uniquify ids).

Common situations: Programmatically generated placement lists reusing the default id across iterations; JSON configs with a typo'd key ('Id' vs 'id'); copying a placement entry without renaming its id.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

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

        "fps": str(average),
        "frames": int(stream["nb_read_frames"]),
        "has_alpha": alpha,
    }


def allocate_placements(placements, *, fps, base_track_count, probe=probe_asset):
    """Validate assets and interval-color overlays above preserved video tracks.

    record_frame is an absolute timeline frame; intervals are [start, end).
    Optional requires_alpha=True enforces a decoded alpha-capable pixel format.
    """
    rate = _fps(fps)
    _integer(base_track_count, "base_track_count")
    checked, seen, metadata = [], set(), {}
    for event in placements:
        identifier = event.get("id")
        if not isinstance(identifier, str) or not identifier or identifier in seen:
            raise ValueError("placement id must be unique and nonempty")
        seen.add(identifier)
        start = _integer(event.get("record_frame"), "record_frame")
        frames = _integer(event.get("frames"), "frames", 1)
        opacity = event.get("opacity")
        if (
            isinstance(opacity, bool)
            or not isinstance(opacity, (int, float))
            or not math.isfinite(opacity)
            or not 0 <= opacity <= 100
        ):
            raise ValueError("explicit opacity must be finite within 0..100")
        composite = _integer(event.get("composite"), "composite")
        raw_asset = event.get("asset")
        if not isinstance(raw_asset, (str, Path)) or not str(raw_asset):
            raise ValueError("asset must be a local regular file")
        asset = Path(raw_asset).expanduser().resolve()
        if not asset.is_file():
            raise ValueError(f"asset must be a local regular file: {asset}")

View on GitHub (pinned to 8321021c54)