affaan-m/ECC · error · ValueError

native fps missing

Error message

native fps missing

What it means

_snapshot reads settings.timelineFrameRate from the native snapshot and requires it to be a string, int or float. If settings is missing, not a dict, or timelineFrameRate is absent/null/bool, the library raises "native fps missing" (immediately wrapped into the composite fps error).

Solutions

  1. Add or restore `"settings": {"timelineFrameRate": <rate>}` in the snapshot JSON, using the same label Resolve exports (e.g. "23.976", "24", or "24000/1001").
  2. Re-export the snapshot from the correct Resolve version so settings.timelineFrameRate is present.
  3. Check the key spelling/casing — it must be exactly `timelineFrameRate`.
  4. Make sure the value is a plain string/number, not null, true/false, or an object.

Example fix

// before
{"project": "P", "timeline": "T"}

// after
{"project": "P", "timeline": "T", "settings": {"timelineFrameRate": "23.976"}}
Defensive patterns

Strategy: validation

Validate before calling

def has_native_fps(snapshot):
    settings = snapshot.get("settings")
    return isinstance(settings, dict) and type(settings.get("timelineFrameRate")) in (str, int, float)

Type guard

def has_fps(snapshot) -> bool:
    s = snapshot.get("settings") if isinstance(snapshot, dict) else None
    return isinstance(s, dict) and type(s.get("timelineFrameRate")) in (str, int, float)

Try / catch

try:
    bundle = build_application_bundle(config, None, local_only=True)
except ValueError as exc:
    if "fps is missing, invalid or contradictory" in str(exc):
        print("snapshot settings.timelineFrameRate missing or wrong type")
    raise

Prevention

When it happens

Trigger: build_application_bundle where the snapshot JSON lacks a `settings` object, or settings exists but has no `timelineFrameRate` key, or its value is null/bool/list/dict.

Common situations: Snapshot exported from a different tool that uses a different key name (e.g. frameRate, fps); an older Resolve version with a different settings layout; a hand-trimmed snapshot where the settings block was deleted.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/integration.py:202

        raise ValueError("invalid JSON artifact") from exc


def _snapshot(baseline: dict) -> dict:
    _object(baseline, {"project_file", "snapshot_file", "project_name", "timeline_name",
                       "fps", "timeline_range"})
    _rate(baseline["fps"])
    bounds = _range(baseline["timeline_range"])
    _artifact(baseline["project_file"])
    snapshot = _artifact(baseline["snapshot_file"], parse_json=True)
    if not isinstance(snapshot, dict):
        raise ValueError("native snapshot must be an object")
    settings = snapshot.get("settings")
    native_fps = settings.get("timelineFrameRate") if isinstance(settings, dict) else None
    # Resolve's conventional decimal NTSC labels represent these exact rates.
    ntsc = {"23.976": "24000/1001", "29.97": "30000/1001", "59.94": "60000/1001"}
    try:
        if type(native_fps) not in (str, int, float):
            raise ValueError("native fps missing")
        label = str(native_fps)
        if not re.fullmatch(r"[0-9]{1,9}(?:\.[0-9]{1,12}|/[1-9][0-9]{0,8})?", label):
            raise ValueError("native fps must use a bounded decimal or rational label")
        native_rate = Fraction(ntsc.get(label, label))
        if native_rate != Fraction(*_rate(baseline["fps"])):
            raise ValueError("native fps differs")
    except (ValueError, ZeroDivisionError) as exc:
        raise ValueError("native snapshot fps is missing, invalid or contradictory") from exc
    for cfg, key in [("project_name", "project"), ("timeline_name", "timeline")]:
        if _text(baseline[cfg]) != snapshot.get(key):
            raise ValueError("native snapshot identity mismatch")
    tracks = snapshot.get("timeline_readback")
    if not isinstance(tracks, dict) or not tracks:
        raise ValueError("native clip snapshot required")
    for track, clips in tracks.items():
        if not re.fullmatch(r"(?:video|audio)[1-9][0-9]*", track) or not isinstance(clips, list):
            raise ValueError("invalid native snapshot track")
        for clip in clips:

View on GitHub (pinned to 8321021c54)