affaan-m/ECC · error · ValueError
source fps/retime ambiguity
Error message
source fps/retime ambiguity
What it means
Raised by _binding when the binding item's fps does not equal the baseline fps after _rate normalization. Mismatched or fractional frame rates would create retime ambiguity when mapping source frames onto the native timeline.
Solutions
- Set item['fps'] to the same value as the project baseline fps before binding.
- Conform or re-render source media to the timeline frame rate, or fix the caller that stamps fps on binding items.
- Inspect _rate in integration.py to match the exact rate representation (rational vs float) it expects.
Example fix
// before
{'fps': 29.97} # baseline is 30
// after
{'fps': 30} Defensive patterns
Strategy: validation
Validate before calling
from fractions import Fraction
assert Fraction(str(item['fps'])) == Fraction(str(baseline['fps'])), \
f"fps mismatch: {item['fps']} vs baseline {baseline['fps']}" Type guard
def fps_matches(item_fps, baseline_fps) -> bool:
return Fraction(str(item_fps)).limit_denominator(1001) == Fraction(str(baseline_fps)).limit_denominator(1001) Try / catch
try:
bundle = build_application_bundle(...)
except ValueError as e:
if 'source fps/retime ambiguity' in str(e):
log.error('fps mismatch', item_fps=item['fps'], baseline_fps=baseline['fps'])
raise Prevention
- Conform source media to the project frame rate before binding
- Use rational/integer fps representations consistently
- Stamp bindings with the project baseline fps, not the source file's fps
When it happens
Trigger: item['fps'] is 29.97/30000 (or a float 29.97) while baseline is 30, or two different rational representations that _rate treats as unequal, e.g. NTSC vs integer frame rate.
Common situations: Source media shot at a different frame rate than the project timeline; NTSC drop-frame rates vs round numbers; fps serialized as float in one place and as a num/den tuple in another.
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
- source path does not match native clip
- wrong source/audio track kind
- apply_local needs at least one media clip
- asset contains too few frames
- asset requires verified alpha
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/84e20227cfe03c9c.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/integration.py:252
_text(clip["name"])
if type(clip["enabled"]) is not bool or not isinstance(clip["properties"], dict):
raise ValueError("native clip state is incomplete")
def _binding(item: Any, tracks: dict, baseline: dict, *, audio: bool = False) -> tuple:
_object(item, {"media", "track", "clip_index", "media_frames", "fps",
"source_range", "timeline_range"})
track, index = _text(item["track"]), _integer(item["clip_index"])
if not track.startswith("audio" if audio else "video"):
raise ValueError("wrong source/audio track kind")
if track not in tracks or index >= len(tracks[track]):
raise ValueError("source binding has no native clip")
clip = tracks[track][index]
_artifact(item["media"])
if item["media"]["path"] != clip["path"]:
raise ValueError("source path does not match native clip")
if _rate(item["fps"]) != _rate(baseline["fps"]):
raise ValueError("source fps/retime ambiguity")
duration = clip["end"] - clip["start"]
capacity = _integer(item["media_frames"], 1)
if capacity != clip["left_offset"] + duration + clip["right_offset"]:
raise ValueError("source capacity does not match native offsets")
source = _range(item["source_range"], [clip["left_offset"], clip["left_offset"] + duration])
target = _range(item["timeline_range"], [clip["start"], clip["end"]])
mapped = [clip["start"] + f - clip["left_offset"] for f in source]
if mapped != target or (audio and target != [clip["start"], clip["end"]]):
raise ValueError("source/audio placement must preserve native timing")
return track, index
def _preserved_stack(protected: Any, tracks: dict, bounds: list[int]) -> list[dict]:
if not isinstance(protected, list) or not protected:
raise ValueError("protected intervals must be explicit and nonempty")
for item in protected:
_object(item, {"range", "reason"})
_range(item["range"], bounds)View on GitHub (pinned to 8321021c54)