affaan-m/ECC · error · ValueError
native fps must use a bounded decimal or rational label
Error message
native fps must use a bounded decimal or rational label
What it means
The native snapshot's timelineFrameRate, converted to a string, must match a bounded decimal or rational label: up to 9 digits, optionally followed by 1-12 decimal places or /N rational form. Anything else (scientific notation, negative values, extra text, units) is rejected with this error.
Solutions
- Set timelineFrameRate to a plain label: "23.976", "29.97", "59.94", "24", or the rational form "24000/1001".
- If the value is a float, round/serialize it deliberately (e.g. format to a fixed decimal string) instead of relying on str(float).
- Strip units, whitespace and signs from the fps string in the exporter.
- Prefer string or exact rational export from the snapshot producer to avoid float repr artifacts.
Example fix
// before "timelineFrameRate": 2.3976000000000001e+01 // after "timelineFrameRate": "23.976"
Defensive patterns
Strategy: validation
Validate before calling
import re
FPS_RE = re.compile(r"[0-9]{1,9}(?:\.[0-9]{1,12}|/[1-9][0-9]{0,8})?")
def check_fps_label(value):
label = str(value)
if not FPS_RE.fullmatch(label):
raise ValueError(f"fps label {label!r} is not a bounded decimal or rational")
return label Type guard
def is_bounded_fps_label(value) -> bool:
return isinstance(value, (str, int, float)) and bool(re.fullmatch(r"[0-9]{1,9}(?:\.[0-9]{1,12}|/[1-9][0-9]{0,8})?", str(value))) Try / catch
try:
bundle = build_application_bundle(config, None, local_only=True)
except ValueError as exc:
if "bounded decimal or rational label" in str(exc):
print("normalize fps to e.g. '23.976' or '24000/1001'")
raise Prevention
- Serialize fps as strings with fixed formats, never raw floats.
- Round floats to a bounded decimal before export.
- Strip units, signs and whitespace from fps strings.
- Use rational form for NTSC rates when possible.
When it happens
Trigger: timelineFrameRate stringifies to something like "2.4e+01", "24 fps", "-24", "24.0000000000001" (>12 decimals), "24000/10010" (denominator with leading zero pattern), or a float str() artifact like "23.975999999999999".
Common situations: A pipeline storing fps as a float loses the clean label and str() produces long repr; another tool writes scientific notation; someone edits the snapshot and adds units or whitespace; int-typed fps exported as "24.0 fps".
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- candidate fps/duration/retime ambiguity
- fps must be a reduced positive rational
- fps must be finite and positive
- fps must be finite and positive
- fps must be positive, got
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/9828890313320d2d.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/integration.py:205
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:
_check_clip(clip, bounds)
return tracks
View on GitHub (pinned to 8321021c54)