color-grade · git:20261015.07749c7 · 2026-10-15 · sha256 d8298851cdbed057
color-grade git:20261015.07749c7A
Immutable. This exact content never changes and is served at /api/v1/blob/d8298851cdbed057.
--- name: color-grade description: Colour-grade a BashCut edit by measurement: probe, try looks or a generated .cube LUT on real frames, check for damage and back off, apply with adjustment layers and library looks. Triggers: "tone màu", "chỉnh màu", "color grade", "LUT", "màu phim", "màu cinematic", "hình nhạt", "hình gắt". --- # Colour grade Grade after picture lock, before captions (T12 §7). The loop: **probe → measure → candidates → check → back off**. Numbers are readings with their source, not targets: what to change and how much follows the footage and intent. ## BashCut's colour tools Whole video: `library place LOOK --at-frame 0 --duration TOTAL_FRAMES`. A range: `adjustment add --look matte-cinematic --at-frame F --duration D` (grades every layer below). One clip: its `color`. A LUT: `luts import /abs/look.cube --name "Matte" --base-rev N` (ID in `timeline get` `luts`). Built-in looks: `original`, `vivid`, `muted-film`, `black-white`, `bright-airy`, `moody`. Grade picture layers only; adjustments never touch text layers above them. White balance and black/white points need a generated LUT (`grade.py`). ## 1. Probe `media analyze` (job), then `media analysis --media ID`: a `tech.transferKind` other than `sdr` (iPhone shoots HLG by default) needs handling first; the looks were fitted on Rec709. No tone-mapping control yet: tell the user which clips are HLG/PQ/log instead of grading over them (T12 §4, §5). ## 2. Measure `color measure`, `--by clip` (outliers), `--samples N` for long or changing clips. Find the **brightest, darkest and skin-heavy** clips: check them later. Never judge from one frame (T12 §4). | Reading | Range seen | What moves it | Source | |---|---|---|---| | Mid luma, daylight vlog | ~30–50 | lower on purpose for night, low-key, silhouettes: not "too dark" | T12 §3, §7 | | Clipped highlights | share near 0 | deliberate sun or sky may clip | T12 §7 | | Matte blacks / rolled whites | black > 3 reads as matte; white < 90 as rolled | examples, not targets | style-study (T15 §3) | | Saturation | < 30 reads muted, > 45 punchy | genre: food punchy, cinematic muted | style-study (T15 §3), T12 §3 | | Split tone | shadow R−B < 0 with highlight R−B > 0 = teal-orange | — | style-study (T15 §3) | | Shot match | each clip's difference from the median clip | you choose how far to pull an outlier | drama-skills (T12 §3) | A recipe's *intent* (`recipe.skill` in `project get`), e.g. "bright, warm, skies kept": turn it into which readings matter, never into fixed numbers. ## 3. Candidates **Default: one sheet, one pick.** Turn the intent into a target, then let the scripts rank, check and draw: ```sh uv run "<skill_dir>/fetch_luts.py" # once per Mac (setup, or a new --folder of your LUTs): prints one line uv run "<skill_dir>/grade.py" candidates --target warm,film [/abs/clip.mp4 | MEDIA_ID ...] # sheet path + 1 line each uv run "<skill_dir>/grade.py" candidates --pick 3 --strength 0.7 # luts import + adjustment add over the edit ``` `--target`: tags (warm, cool, teal-orange, matte, film, bw, vivid, muted, punchy, soft, bright, dark, moody, clean, green), a look name below, or a `bc:style-study` profile JSON. It ranks the LUT library (log LUTs only with `--log`), tries the best on 3 frames (brightest, darkest, most skin), drops damage past `--max-clip` (default 1 % of detailed pixels; 0.5–2 seen), `--max-crush` (2 %; 1–3) and `--max-skin` (8°; 5–12), and adds `--gen` 3 looks below fitted to the footage. Look at the sheet once, pick a number (`N@frame:duration` for a range). Picks and skips, remembered per recipe (`--for`), move the ranking. Never read a LUT list or `.cube`. | Look | Use for | Fitted reference (`looks.json`) | |---|---|---| | `matte-cinematic` | default cinematic: street, daytime, travel | black ~7, white ~84, sat ~28; teal shadows, peach highlights, greens to olive, skin protected | | `warm-film` | warm indoor light, late afternoon, personal stories | deep blacks (~2.6), highlights ~66–76, sat ~29, warm mids | | `faded-memory` | hook, flashback or emotional low point, then back to colour | near black-and-white, lifted blacks | | `teal-orange`, `clean-bright`, `moody-green`, `bw`, `film-print` | blockbuster/travel; food, product, tutorial; forest, rain, night; drama; narrative | set by hand, not fitted (`grade.py looks`) | Also `library list --kind look`. - **Strength** 0.5–1.0; 0.6–0.8 is typical (openmontage). Lower for log-like or already saturated footage, skin-heavy shots and mixed light (T12 §3). - **One look for the video**; fix outliers with per-clip `color` or a second adjustment over them (T12 §4). - A LUT is half the look, light when shooting the other half. Harsh noon sun stays harsh: say so instead of pushing the grade until skin breaks. - A matte look lifts pure black backgrounds to dark grey; use another look over those ranges if true black matters. ### Generated looks by hand (`grade.py`, when the sheet has nothing close) ```sh G="<skill_dir>/grade.py" # the folder this SKILL.md is in; run with uv (installs numpy + Pillow) uv run "$G" looks uv run "$G" match /abs/clip.mp4 matte-cinematic --exposure S --wb R,G,B # JSON report, writes nothing uv run "$G" lut matte-cinematic --exposure S --wb R,G,B -o /abs/project/luts-src/matte.cube uv run "$G" preview /abs/clip.mp4 /abs/project/luts-src/matte.cube -o /tmp/prev.jpg --n 6 ``` `match` reports the footage through the look against the look's `ref` and `range` in `looks.json` (`outside`: 0 inside, − below, + above, null with no range; each range has its `why`); it corrects nothing. You decide the exposure (stops added to the look's) and white balance (R,G,B multipliers on the look's), try them with `match` again, then write the LUT with `lut`. Leave the white balance alone on scenes meant to be coloured (sunset, neon, screen recordings, graphics): the reference tint is the look's, not the scene's (T12 §4). `match` runs on the source file only; the real check is BashCut's composite in step 4. Never read a `.cube` body; judge a LUT by its frames (T12 §4). Your own look: a params JSON (keys in `grade.py -h`) in place of a look name. Then `luts import` the `.cube` and `adjustment add --lut LUT_ID --lut-strength 0.8` over the edit. ## 4. Check `color measure --compare source`, `ui frames --compare graded --frames F1,F2,F3` (brightest, darkest, skin-heavy), `ui frame F --phone`. Read the damage facts in `change` **relative to the source**, then look at the frames. One worked example (DaVinci Resolve's grade checker, thresholds are parameters): `clippedGrowth` 0.5 % of pixels, washed-out `chromaRatio` 0.60, milky `blackLift` L* 8, and a grade that moved `meanDeltaE` 6 without any flag "is worth a second opinion": undamaged is not the same as good (T12 §2, §3). A matte look lifts blacks on purpose; a warm look moves the mid tint on purpose: compare with the intent. Check every sampled clip, not the average; name the clip and frame that fails. ## 5. Back off On damage, lower before adding: `lutStrength` first, then the exposure or white balance you passed. Resolve backs off ×0.8 per try down to 0.5 (T12 §2). Change the adjustment's `color` (send every key) or `timeline undo` and add it again. **At most 3 attempts.** Then stop: show the user `ui frames --compare graded` of the gentlest attempt with its numbers and ask (`ui view --compare on` toggles before/after in the viewer for them). ## Keep what worked - `library place ID` (an adjustment) or `library apply ID --item ITEM` (one clip's grade) copies its LUT in. - Once the user approves: `library save-selection --kind look --item ADJUSTMENT`, or from a LUT: `library add --kind look --file /abs/matte.cube`. A correction: `library update ID`, or `--as NEW_ID` for a built-in or plugin look. - Matching a reference channel: measure several of its frames with `grade.py measure --json` (videos the user has the right to use), compare with your footage, and move a look's params toward them; see `bc:style-study`.