{"record":{"id":"84e20227cfe03c9c","repo":"affaan-m/ECC","slug":"source-fps-retime-ambiguity","errorCode":null,"errorMessage":"source fps/retime ambiguity","messagePattern":"source fps/retime ambiguity","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/integration.py","lineNumber":252,"sourceCode":"    _text(clip[\"name\"])\n    if type(clip[\"enabled\"]) is not bool or not isinstance(clip[\"properties\"], dict):\n        raise ValueError(\"native clip state is incomplete\")\n\n\ndef _binding(item: Any, tracks: dict, baseline: dict, *, audio: bool = False) -> tuple:\n    _object(item, {\"media\", \"track\", \"clip_index\", \"media_frames\", \"fps\",\n                   \"source_range\", \"timeline_range\"})\n    track, index = _text(item[\"track\"]), _integer(item[\"clip_index\"])\n    if not track.startswith(\"audio\" if audio else \"video\"):\n        raise ValueError(\"wrong source/audio track kind\")\n    if track not in tracks or index >= len(tracks[track]):\n        raise ValueError(\"source binding has no native clip\")\n    clip = tracks[track][index]\n    _artifact(item[\"media\"])\n    if item[\"media\"][\"path\"] != clip[\"path\"]:\n        raise ValueError(\"source path does not match native clip\")\n    if _rate(item[\"fps\"]) != _rate(baseline[\"fps\"]):\n        raise ValueError(\"source fps/retime ambiguity\")\n    duration = clip[\"end\"] - clip[\"start\"]\n    capacity = _integer(item[\"media_frames\"], 1)\n    if capacity != clip[\"left_offset\"] + duration + clip[\"right_offset\"]:\n        raise ValueError(\"source capacity does not match native offsets\")\n    source = _range(item[\"source_range\"], [clip[\"left_offset\"], clip[\"left_offset\"] + duration])\n    target = _range(item[\"timeline_range\"], [clip[\"start\"], clip[\"end\"]])\n    mapped = [clip[\"start\"] + f - clip[\"left_offset\"] for f in source]\n    if mapped != target or (audio and target != [clip[\"start\"], clip[\"end\"]]):\n        raise ValueError(\"source/audio placement must preserve native timing\")\n    return track, index\n\n\ndef _preserved_stack(protected: Any, tracks: dict, bounds: list[int]) -> list[dict]:\n    if not isinstance(protected, list) or not protected:\n        raise ValueError(\"protected intervals must be explicit and nonempty\")\n    for item in protected:\n        _object(item, {\"range\", \"reason\"})\n        _range(item[\"range\"], bounds)","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/integration.py#L234-L270","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n{'fps': 29.97}  # baseline is 30\n// after\n{'fps': 30}","handlingStrategy":"validation","validationCode":"from fractions import Fraction\nassert Fraction(str(item['fps'])) == Fraction(str(baseline['fps'])), \\\n    f\"fps mismatch: {item['fps']} vs baseline {baseline['fps']}\"","typeGuard":"def fps_matches(item_fps, baseline_fps) -> bool:\n    return Fraction(str(item_fps)).limit_denominator(1001) == Fraction(str(baseline_fps)).limit_denominator(1001)","tryCatchPattern":"try:\n    bundle = build_application_bundle(...)\nexcept ValueError as e:\n    if 'source fps/retime ambiguity' in str(e):\n        log.error('fps mismatch', item_fps=item['fps'], baseline_fps=baseline['fps'])\n    raise","preventionTips":["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"],"tags":["binding","framerate","media"],"backgroundTag":"invalid-argument-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}