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
- Deduplicate media by normalized path before calling apply_local (use a dict/set keyed on the normalized path)
- Fix the manifest generator to skip clips whose path was already added
- If repetition is intended, do not use the no-repeat mode / disable the no-repeat constraint
- 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
- Deduplicate media by normalized absolute path before applying
- Resolve symlinks (os.path.realpath) so aliases collapse to one entry
- Deduplicate at manifest-generation time, not application time
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
- no-repeat plan requires more unique source clips
- source clip is too short for its no-repeat cadence slot
- Ambiguous duplicate bundle request
- apply_local needs at least one media clip
- asset contains too few frames
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)