affaan-m/ECC · error · ValueError

no-repeat media must contain unique normalized source paths

Error message

no-repeat media must contain unique normalized source paths

What it means

In the no-repeat mode, each media clip is keyed by its normalized source path in the `sources` dict. If two clips resolve to the same normalized path, the no-repeat invariant (each source used once per slot) would be broken, so a duplicate key raises this error.

Solutions

  1. Deduplicate media by normalized path before calling apply_local (use a dict/set keyed on the normalized path)
  2. Fix the manifest generator to skip clips whose path was already added
  3. If repetition is intended, do not use the no-repeat mode / disable the no-repeat constraint
  4. Normalize paths consistently (resolve symlinks, absolute paths) so duplicates are detected upstream

Example fix

# before
media = [{"path": "a.mp4"}, {"path": "./a.mp4"}]
# after
seen = {}
for c in media:
    p = os.path.normpath(os.path.abspath(c["path"]))
    seen.setdefault(p, {**c, "path": p})
media = list(seen.values())
Defensive patterns

Strategy: validation

Validate before calling

paths = [os.path.normpath(os.path.abspath(c["path"])) for c in media]
if len(paths) != len(set(paths)):
    raise ValueError("duplicate media source paths")

Prevention

When it happens

Trigger: Passing `media` to `apply_local` with duplicate clips pointing at the same normalized source path, e.g. the same file listed twice or via aliases that normalize identically.

Common situations: A media directory scanned twice; the same clip referenced with different relative/absolute paths or `./` prefixes that normalization collapses; duplicate entries in a manifest built by concatenating playlists.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

        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)
    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.

View on GitHub (pinned to 8321021c54)