{"record":{"id":"fe0767261eb6800a","repo":"affaan-m/ECC","slug":"asset-contains-too-few-frames-asset","errorCode":null,"errorMessage":"asset contains too few frames: {asset}","messagePattern":"asset contains too few frames: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/resolve.py","lineNumber":129,"sourceCode":"            raise ValueError(\"explicit opacity must be finite within 0..100\")\n        composite = _integer(event.get(\"composite\"), \"composite\")\n        raw_asset = event.get(\"asset\")\n        if not isinstance(raw_asset, (str, Path)) or not str(raw_asset):\n            raise ValueError(\"asset must be a local regular file\")\n        asset = Path(raw_asset).expanduser().resolve()\n        if not asset.is_file():\n            raise ValueError(f\"asset must be a local regular file: {asset}\")\n        alpha = event.get(\"requires_alpha\", False)\n        if not isinstance(alpha, bool):\n            raise ValueError(\"requires_alpha must be boolean\")\n        key = str(asset)\n        if key not in metadata:\n            metadata[key] = probe(asset)\n        info = metadata[key]\n        if _fps(info.get(\"fps\")) != rate:\n            raise ValueError(f\"asset fps differs from timeline: {asset}\")\n        if _integer(info.get(\"frames\"), \"asset frames\", 1) < frames:\n            raise ValueError(f\"asset contains too few frames: {asset}\")\n        if alpha and info.get(\"has_alpha\") is not True:\n            raise ValueError(f\"asset requires verified alpha: {asset}\")\n        checked.append(\n            {\n                \"id\": identifier,\n                \"asset\": key,\n                \"record_frame\": start,\n                \"frames\": frames,\n                \"opacity\": opacity,\n                \"composite\": composite,\n            }\n        )\n    if not checked:\n        raise ValueError(\"at least one placement is required\")\n    ends, allocated = [], {}\n    for event in sorted(checked, key=lambda entry: entry[\"record_frame\"]):\n        index = next(\n            (i for i, end in enumerate(ends) if end <= event[\"record_frame\"]), len(ends)","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/resolve.py#L111-L147","documentation":"tasteforge validates every placement's asset media metadata before allocating it onto the timeline. This error means the asset's probed frame count is lower than the number of frames the placement needs (start-to-end span), so appending it would produce a short or invalid clip.","triggerScenarios":"Calling allocate_placements (via apply_placements or plan) with a placement whose frames requirement, derived from record_frame/start and end, exceeds the probe-reported frame count of the asset (e.g. a 3-second clip placed into a 5-second span).","commonSituations":"Reusing a short sting/logo asset for a longer overlay; timeline rescaled to a slower fps increasing frame span; metadata cache holding stale frames values; off-by-one from inclusive vs exclusive end conventions inflating required frames.","solutions":["Shorten the placement (reduce end so required frames <= asset frames)","Replace the asset with a longer take or loop/extend the source media before importing","Re-probe the asset to refresh cached metadata if frames changed on disk","Verify the endpoint convention (inclusive/exclusive) is not inflating the frame count","Check for fps mismatch causing the frame requirement to be computed at the wrong rate"],"exampleFix":"// before\n{\"asset\": \"sting.mov\", \"start\": 0, \"end\": 500}  # asset has 240 frames\n// after\n{\"asset\": \"sting.mov\", \"start\": 0, \"end\": 240}","handlingStrategy":"validation","validationCode":"info = probe(asset)\nframes_needed = end - start\nif info.get(\"frames\", 0) < frames_needed:\n    raise ValueError(f\"asset {asset} has {info.get('frames')} frames, need {frames_needed}\")","typeGuard":"def has_enough_frames(info: dict, needed: int) -> bool:\n    return isinstance(info.get(\"frames\"), int) and info[\"frames\"] >= needed","tryCatchPattern":null,"preventionTips":["Probe assets at import time and store frame counts in a manifest","Keep assets longer than the longest overlay span you plan","Verify inclusive/exclusive end convention once per host to avoid inflated frame needs","Invalidate metadata caches when media files change"],"tags":["validation","media","frames"],"backgroundTag":"value-out-of-range","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"}