{"record":{"id":"e65c71164692e0e8","repo":"affaan-m/ECC","slug":"lut-not-found-lut-grade","errorCode":null,"errorMessage":"LUT not found: {lut}","messagePattern":"LUT not found: (.+?)","errorType":"exception","errorClass":"FileNotFoundError","httpStatus":null,"severity":"error","filePath":"skills/taste-distillation/scripts/taste/grade.py","lineNumber":663,"sourceCode":"    lut: str | Path,\n    strength: float = 1.0,\n    crf: int = 16,\n) -> Path:\n    \"\"\"Apply a pack's .cube to a clip with ffmpeg. This is where the look happens.\n\n    Measured on three generations against the flashethereal pack: prompting\n    for the grade moved midtone a* from +1.9 to +2.8 across two paid attempts\n    and never touched contrast (23.4 / 19.3 / 19.2 against a target of 34.7).\n    Running the same footage through this function put chroma within a mean\n    absolute error of 1.4 and contrast at 34.9 against 34.7 - in one pass, at\n    no marginal cost, and identically every time.\n\n    ``strength`` below 1.0 blends the graded result back toward the original,\n    for when the full pack look is too much for a particular shot.\n    \"\"\"\n    src, dst, lut = Path(src), Path(dst), Path(lut)\n    if not lut.exists():\n        raise FileNotFoundError(f\"LUT not found: {lut}\")\n    dst.parent.mkdir(parents=True, exist_ok=True)\n\n    s = max(0.0, min(1.0, float(strength)))\n    if s >= 0.999:\n        vf = f\"lut3d=file='{lut.as_posix()}'\"\n    else:\n        # Blend graded over original so partial looks stay available.\n        vf = (\n            f\"split=2[a][b];[b]lut3d=file='{lut.as_posix()}'[g];\"\n            f\"[a][g]blend=all_mode=normal:all_opacity={s:.3f}\"\n        )\n\n    cmd = [\n        \"ffmpeg\", \"-nostdin\", \"-loglevel\", \"error\", \"-y\", \"-i\", str(src),\n        \"-vf\", vf, \"-c:v\", \"libx264\", \"-crf\", str(crf), \"-pix_fmt\", \"yuv420p\",\n        \"-c:a\", \"copy\", str(dst),\n    ]\n    proc = subprocess.run(cmd, capture_output=True, text=True)","sourceCodeStart":645,"sourceCodeEnd":681,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-distillation/scripts/taste/grade.py#L645-L681","documentation":"grade_clip applies a 3D LUT to a video clip via ffmpeg's lut3d filter. Before doing anything it resolves the LUT path to a Path and checks lut.exists(); if the file is not on disk it raises FileNotFoundError so the caller fails fast instead of letting ffmpeg die with a cryptic filter error. It is a guard against an invalid user-supplied path, not an internal fault.","triggerScenarios":"Calling grade_clip(src, dst, lut=...) with a lut argument that points to a missing file: a typo in the filename, a .cube file that was never minted/copied into the pack, a relative path resolved from the wrong working directory, or a pack directory that was moved or deleted after minting.","commonSituations":"Hand-writing LUT paths in a pipeline config instead of taking them from the StylePack manifest; running the script from a different cwd than during testing so relative paths break; case-sensitive filesystems where 'Look.CUBE' vs 'look.cube' matters on Linux; partial style packs where LUT generation was skipped.","solutions":["Print and inspect the exact path: resolve it (Path(lut).resolve()) and check it exists with ls before re-running.","Correct the path - fix typos and match case exactly; on Linux/macOS the filename is case-sensitive.","If the pack is missing or incomplete, regenerate it by running mint.py so the .cube LUT is written to disk.","Use an absolute path (or resolve relative paths against the pack root) instead of a cwd-dependent relative path.","Wrap the call in try/except FileNotFoundError to surface a friendly message listing valid LUTs from the pack manifest."],"exampleFix":"// before\ngrade_clip('shot.mp4', 'graded.mp4', lut='looks/teal_orange.CUBE')\n// after\nfrom pathlib import Path\nlut = Path('looks/teal_orange.cube')\nif not lut.exists():\n    raise SystemExit(f'LUT missing: {lut.resolve()}')\ngrade_clip('shot.mp4', 'graded.mp4', lut=lut)","handlingStrategy":"validation","validationCode":"from pathlib import Path\nlut = Path(lut_arg)\nif not lut.is_file():\n    raise SystemExit(f'LUT does not exist: {lut.resolve()}')\nif lut.suffix.lower() != '.cube':\n    raise SystemExit(f'expected a .cube LUT, got: {lut.name}')","typeGuard":null,"tryCatchPattern":"try:\n    grade_clip(src, dst, lut=lut)\nexcept FileNotFoundError as e:\n    logger.error('LUT missing, check pack manifest: %s', e)\n    available = [p.name for p in pack_dir.glob('*.cube')]\n    raise SystemExit(f'available LUTs: {available}') from e","preventionTips":["Always take LUT paths from the StylePack manifest rather than typing them.","Use absolute paths (Path(...).resolve()) so the result is cwd-independent.","After minting a pack, assert every manifest LUT exists before starting a batch grade.","Remember case sensitivity on Linux/macOS when naming .cube files."],"tags":["file-io","path","video","ffmpeg"],"backgroundTag":"file-not-found","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"}