affaan-m/ECC · error · ValueError
media clips require a local source path
Error message
media clips require a local source path
What it means
Each media clip passed to apply_local must be a dict containing a 'path' key whose value is a str or Path. If a clip is not a dict, or its 'path' is missing or of the wrong type (e.g. a plain string instead of {'path': ...}), this ValueError is raised. It enforces the clip record shape before any planning happens.
Solutions
- Wrap each path in a dict: media=[{'path': 'clip.mp4'}].
- Rename alternate keys ('src', 'file') to 'path' before calling apply_local.
- Filter or convert non-dict entries in the media list before the call.
- Ensure each clip's path value is a str or pathlib.Path, not bytes or None.
Example fix
// before
apply_local(sp, media=["a.mp4", "b.mp4"])
// after
apply_local(sp, media=[{"path": "a.mp4"}, {"path": "b.mp4"}]) Defensive patterns
Strategy: type-guard
Validate before calling
bad = [c for c in media if not (isinstance(c, dict) and isinstance(c.get('path'), (str, Path)))]
if bad:
raise ValueError(f"clips must be dicts with a 'path': {bad}") Type guard
def is_clip(c) -> bool:
return isinstance(c, dict) and isinstance(c.get("path"), (str, Path)) Try / catch
try:
report = apply_local(sp, media=media)
except ValueError as e:
if "local source path" in str(e):
media = normalize_clips(raw_media) # wrap strings into {'path': ...}
report = apply_local(sp, media=media)
else:
raise Prevention
- Normalize external clip formats to {'path': ...} at ingestion boundaries.
- Type-check media lists with a validator (pydantic/typing) before calling.
- Keep one clip-record type shared across your tooling.
When it happens
Trigger: Calling apply_local(sp, media=["clip.mp4"]) with raw path strings, or media=[{'src': 'clip.mp4'}] with a wrong key name, or a non-dict element in the media list.
Common situations: Passing paths directly instead of clip dicts; using an alternate key like 'file' or 'src' from another tool's output format; a JSON config where media entries are strings; a None slipped into the media list.
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
- Invalid issue number
- exceeds the -byte limit
- must be a regular file
- memory body is too large
- memory must be an object.
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/035a18bc43b20843.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/apply.py:166
Returns an application report validated against
``schema.APPLICATION_REPORT_SCHEMA``. The report structurally cannot
claim a provider run: ``provider`` is enum-locked to ``"none"`` and
``dry_run`` to ``true``.
"""
if not media:
raise ValueError("apply_local needs at least one media clip")
if not sp.cadence_path.exists():
raise ValueError("pack has no measured cadence (cadence.json is missing)")
cadence = sp.read_json(sp.cadence_path)
if not isinstance(cadence, dict) or not (cadence.get("shots") or "mean_shot" in cadence):
raise ValueError("cadence.json has no measured shots to plan from")
seq_fps = _positive(fps if fps is not None else cadence.get("fps", _DEFAULT_FPS), "fps")
validated_media = []
for clip in media:
if not isinstance(clip, dict) or not isinstance(clip.get("path"), (str, Path)):
raise ValueError("media clips require a local source path")
if not str(clip["path"]).strip():
raise ValueError("media clips require a local source path")
validated_media.append({**clip, "duration": _positive(clip.get("duration"), "media duration")})
target = _positive(duration if duration is not None else sum(
c["duration"] for c in validated_media
), "target duration")
planned = plan_shots(cadence, target)
assignments = _strict_assign(planned, validated_media, target, seq_fps) if no_repeat else [
(validated_media[i % len(validated_media)], max(1, timeline.seconds_to_frames(d, seq_fps)))
for i, d in enumerate(planned)
]
shots: list[dict[str, Any]] = []
events: list[dict[str, Any]] = []
clock = 0.0
offset_frames = 0
for i, (clip, frames) in enumerate(assignments):
d = float(frames / timeline.fps_fraction(seq_fps)) if no_repeat else planned[i]View on GitHub (pinned to 8321021c54)