{"record":{"id":"bd66a2643eaecd05","repo":"affaan-m/ECC","slug":"cannot-read-overlay-image-image-assemble","errorCode":null,"errorMessage":"cannot read overlay image: {image}","messagePattern":"cannot read overlay image: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-distillation/scripts/taste/assemble.py","lineNumber":113,"sourceCode":"    the same mark in the same place every time.\n\n    ``screen`` is the default because plates are premultiplied against black,\n    so screen drops their blacks for free and no matte is needed.\n    \"\"\"\n    clip, image, dst = Path(clip), Path(image), Path(dst)\n    if width is None or height is None:\n        from . import frames as _fm\n        info = _fm.probe(clip)\n        width, height = info.width, info.height\n\n    # Resolve the element's pixel size here rather than in ffmpeg expressions.\n    # pad() rejects a negative offset and cannot pad to a size smaller than its\n    # input, so an element that lands oversized or off-frame kills the whole\n    # filtergraph - which it did on the first attempt.\n    import cv2 as _cv2\n    _im = _cv2.imread(str(image), _cv2.IMREAD_UNCHANGED)\n    if _im is None:\n        raise ValueError(f\"cannot read overlay image: {image}\")\n    ih0, iw0 = _im.shape[:2]\n    ew = max(2, int(width * max(0.02, min(1.0, scale))))\n    eh = max(2, int(ew * ih0 / max(1, iw0)))\n    if eh > height:  # fit tall elements to the frame instead of overflowing\n        eh = height\n        ew = max(2, int(eh * iw0 / max(1, ih0)))\n    ew, eh = min(ew, width), min(eh, height)\n    if isinstance(position, tuple):\n        px = int(width * position[0])\n        py = int(height * position[1])\n    else:\n        anchors = {\n            \"center\": (0.5, 0.5), \"top\": (0.5, 0.12), \"bottom\": (0.5, 0.88),\n            \"left\": (0.14, 0.5), \"right\": (0.86, 0.5),\n            \"topleft\": (0.16, 0.16), \"topright\": (0.84, 0.16),\n            \"bottomleft\": (0.16, 0.84), \"bottomright\": (0.84, 0.84),\n        }\n        ax, ay = anchors.get(position, (0.5, 0.5))","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-distillation/scripts/taste/assemble.py#L95-L131","documentation":"overlay() reads the overlay image with OpenCV (cv2.imread with IMREAD_UNCHANGED) before building the ffmpeg filtergraph; imread returns None for unreadable files, which this code converts into ValueError('cannot read overlay image: ...'). The read also supplies the source image dimensions used to scale the overlay element.","triggerScenarios":"overlay(...) called with an image path that does not exist, is a directory, has an unsupported/corrupt format (e.g. WEBP with alpha on an old OpenCV), or has unreadable permissions — cv2.imread returns None and the raise fires.","commonSituations":"Typo in the logo/watermark path; asset generated by a previous step failed silently so file is missing; image is in a format the installed OpenCV build can't decode (missing codec support); relative path resolved from the wrong working directory.","solutions":["Check the path in the message exists and is a readable image file (ls -l / file <path>).","Convert the image to PNG or JPEG (ffmpeg -i in.webp out.png) if the format is unsupported by the installed OpenCV.","Fix the path to be absolute or ensure the script's working directory matches the asset location.","Regenerate the overlay asset if the producing step failed and left no file.","Pre-validate in the caller: assert Path(image).is_file() before calling overlay()."],"exampleFix":"// before\nassemble.overlay(video, \"lgoo.png\", ...)  # typo -> ValueError\n\n// after\nfrom pathlib import Path\nimg = Path(\"logo.png\")\nif not img.is_file():\n    raise SystemExit(f\"overlay asset missing: {img}\")\nassemble.overlay(video, str(img), ...)","handlingStrategy":"validation","validationCode":"from pathlib import Path\nimport cv2\nimg = cv2.imread(str(overlay_path), cv2.IMREAD_UNCHANGED)\nif img is None:\n    raise SystemExit(f\"overlay not readable: {overlay_path}\")","typeGuard":"def is_readable_image(path) -> bool:\n    import cv2\n    from pathlib import Path\n    p = Path(path)\n    return p.is_file() and cv2.imread(str(p), cv2.IMREAD_UNCHANGED) is not None","tryCatchPattern":"try:\n    assemble.overlay(video, logo_path, ...)\nexcept ValueError as exc:\n    if str(exc).startswith(\"cannot read overlay image\"):\n        log.error(\"bad overlay asset: %s\", exc)\n    raise","preventionTips":["Resolve overlay paths to absolute paths before calling overlay()","Convert assets to PNG/JPEG for maximum OpenCV compatibility","Verify asset-producing steps succeeded (file exists, size > 0) before use","cv2.imread returns None, not an exception — always check for None","Validate image readability in a preflight check across all assets"],"tags":["opencv","file-not-found","image","validation"],"backgroundTag":"file-read-failed","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"}