{"record":{"id":"2621632acf2929a9","repo":"affaan-m/ECC","slug":"crop-must-fit-within-normalized-image-coordinates","errorCode":null,"errorMessage":"Crop must fit within normalized image coordinates","messagePattern":"Crop must fit within normalized image coordinates","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/media/stills.py","lineNumber":17,"sourceCode":"\"\"\"Animate an image between normalized crop rectangles using local FFmpeg.\"\"\"\n\nimport argparse\nimport math\nimport subprocess\nfrom pathlib import Path\n\nfrom .common import geometry, output_file\n\n\ndef filter_graph(start, end, frames, width, height, fps):\n    for crop in (start, end):\n        if len(crop) != 4 or any(not math.isfinite(v) for v in crop):\n            raise ValueError(\"Crop must contain four finite values\")\n        x, y, w, h = crop\n        if min(x, y) < 0 or min(w, h) <= 0 or x + w > 1 or y + h > 1:\n            raise ValueError(\"Crop must fit within normalized image coordinates\")\n    # Coordinates refer to the image after scale-cover to output aspect.\n    # Expand each requested rectangle to output aspect, then interpolate its\n    # width and center; zoompan clamps the window at image boundaries.\n    fraction = f\"on/{max(1, frames - 1)}\"\n\n    def interpolate(a, b):\n        return f\"({a}+({b}-{a})*{fraction})\"\n\n    zoom = f\"1/{interpolate(max(start[2], start[3]), max(end[2], end[3]))}\"\n    cx = interpolate(start[0] + start[2] / 2, end[0] + end[2] / 2)\n    cy = interpolate(start[1] + start[3] / 2, end[1] + end[3] / 2)\n    return (\n        f\"scale={width * 2}:{height * 2}:force_original_aspect_ratio=increase,\"\n        f\"crop={width * 2}:{height * 2},zoompan=z='{zoom}':\"\n        f\"x='iw*{cx}-iw/zoom/2':y='ih*{cy}-ih/zoom/2':\"\n        f\"d={frames}:s={width}x{height}:fps={fps},format=yuv420p\"\n    )\n","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/media/stills.py#L1-L35","documentation":"TasteForge validates that every crop rectangle for a still-image zoom clip consists of exactly four finite numbers and lies fully inside the normalized [0,1] image coordinate space (x,y >= 0, w,h > 0, x+w <= 1, y+h <= 1). filter_graph raises this error before building the ffmpeg filter graph when a start or end crop extends beyond the image. This prevents zoompan from producing out-of-bounds sampling.","triggerScenarios":"Calling make_clip with a start_crop or end_crop where x<0, y<0, w<=0, h<=0, x+w>1, or y+h>1 (e.g. [0.1, 0.1, 0.9, 0.9] summing to 1.0 exactly is fine, but [0.5, 0.5, 0.6, 0.6] exceeds 1). Also raised when a crop value is non-finite (inf/nan) — though that yields the sibling 'four finite values' error first.","commonSituations":"Hand-computing crop rectangles in pixel units instead of normalized 0-1 fractions; forgetting that width and height add to x and y; deriving crops from aspect-ratio math that rounds to slightly over 1.0; passing NaN from an upstream computation.","solutions":["Normalize the crop: clamp x,y to >= 0 and shrink w,h so x+w <= 1 and y+h > 0, e.g. [0.2, 0.2, 0.5, 0.5]","Convert pixel coordinates to fractions: divide x and w by image width, y and h by image height before calling make_clip","Add an assertion/clamp helper in your pipeline that keeps crop rectangles within the unit square"],"exampleFix":"// before\nmake_clip(\"in.jpg\", \"out.mp4\", start_crop=[0.4, 0.4, 0.7, 0.7], end_crop=[0.1, 0.1, 0.5, 0.5])  # 0.4+0.7 > 1\n// after\nmake_clip(\"in.jpg\", \"out.mp4\", start_crop=[0.2, 0.2, 0.5, 0.5], end_crop=[0.1, 0.1, 0.5, 0.5])","handlingStrategy":"validation","validationCode":"def valid_crop(c):\n    return (isinstance(c, (list, tuple)) and len(c) == 4\n            and all(isinstance(v, (int, float)) and math.isfinite(v) for v in c)\n            and c[0] >= 0 and c[1] >= 0 and c[2] > 0 and c[3] > 0\n            and c[0] + c[2] <= 1 and c[1] + c[3] <= 1)\nassert valid_crop(start_crop) and valid_crop(end_crop)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep crop values as fractions of image dimensions, never pixels","Clamp with min(x, 1 - w) style logic before use","Add a unit test asserting x+w <= 1 and y+h <= 1 for all crop presets"],"tags":["validation","geometry","ffmpeg","crops"],"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"}