affaan-m/ECC · error · ValueError

clip has no 'path

Error message

clip {i} has no 'path'

What it means

_normalise() requires every clip dict to have a non-empty 'path' key; the path is the media file each timeline event points to. A clip without one cannot be represented in FCPXML or EDL, so a ValueError naming the clip index is raised.

Solutions

  1. Add a valid media 'path' to each clip dict before building the timeline
  2. Filter out incomplete clips: clips = [c for c in clips if c.get('path')]
  3. Validate the clip manifest schema before calling the builder
  4. Check the producer of the clip list for keys like 'file' vs 'path' naming mismatches

Example fix

// before
clips = [{"duration": 2.0}]
// after
clips = [{"path": "/media/clip1.mp4", "duration": 2.0}]
Defensive patterns

Strategy: validation

Validate before calling

invalid = [i for i, c in enumerate(clips) if not str(c.get('path') or '').strip()]
if invalid:
    raise ValueError(f'clips missing path: {invalid}')

Type guard

def is_valid_clip(c: object) -> bool:
    return isinstance(c, dict) and bool(str(c.get('path') or '').strip())

Try / catch

try:
    write_timeline(clips, out, fmt='fcpxml')
except ValueError as e:
    if "has no 'path'" in str(e):
        logger.error('clip manifest incomplete: %s', e)
        raise

Prevention

When it happens

Trigger: Passing build_fcpxml/build_edl a clips list containing a dict like {"duration": 2.0} with no 'path', or a clip where path is None or empty string.

Common situations: Downstream tooling emitting partial clip records; JSON clip manifests with missing fields; constructing clips programmatically and forgetting the key.

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/505271a43896c03c. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-distillation/scripts/taste/timeline.py:218

# ---------------------------------------------------------------------------
# clip normalisation
# ---------------------------------------------------------------------------


def _normalise(clips: Iterable[dict], fps: float | Fraction) -> list[dict]:
    """Validate clips and pre-compute integer frame counts and offsets.

    Returns dicts with ``path``, ``name``, ``frames`` (int, >= 1) and
    ``offset_frames`` (int). Working in frames from here down is what makes the
    sequence duration exactly the sum of the clip durations.
    """
    out: list[dict] = []
    offset = 0
    for i, c in enumerate(clips):
        path = str(c.get("path") or "")
        if not path:
            raise ValueError(f"clip {i} has no 'path'")
        dur = float(c.get("duration") or 0.0)
        if dur <= 0:
            raise ValueError(f"clip {i} ({path}) has non-positive duration {dur!r}")
        frames = max(1, seconds_to_frames(dur, fps))  # never emit a zero-length event
        name = str(c.get("name") or Path(path).stem)
        out.append(
            {
                "path": path,
                "name": name,
                "frames": frames,
                "offset_frames": offset,
                "seconds": dur,
            }
        )
        offset += frames
    if not out:
        raise ValueError("no clips to write - a timeline needs at least one event")
    return out

View on GitHub (pinned to 8321021c54)