# CHECK — aiproshots topic refresh 2026-10-21

Review page: https://aiproshots-assets-review.pages.dev/topic-refresh/ · review only, nothing uploaded to R2/CDN, live site untouched. No zip.

## A. Expression unify (before-v2)

**Source used**: CDN `before-480/960` are downscales of the smile master `models/model-{id}-smile.jpg` (2048×3072, the same image the afters were generated from). CDN `before-1440` is a *different* render (the `-neutral` master): already closed-mouth, but hair/framing differ slightly (mouth sits ~1% lower). So v2 was built from the smile master: hair/clothes/background match the 480/960 originals and the afters. See `qc/A-expression-audit/cdn-before-960-vs-1440.jpg`.

**Method**: GPT Image 2 (cc.nexcor.ai, `/v1/images/edits` + mask) on a 1024² face crop → aligned back (similarity transform from stable landmarks) → low-frequency color match → composited only inside a feathered mouth + under-eye mask (mediapipe landmarks). Outside the mask, the PNG master is bit-identical to the old image; 480/960 WebP are composited onto the decoded CDN original, so the only outside difference is WebP re-encode noise.

| file | size | check image |
|---|---|---|
| `samples/compare/f01/before-v2-480.webp` | 19.5 KB | `qc/A-f01-old-vs-new.jpg`, `qc/A-f01-v2-plus-3-afters.jpg`, `qc/A-f01-mouth-zoom-2x.jpg` |
| `samples/compare/f01/before-v2-960.webp` | 66.7 KB | `qc/A-f01-old-vs-new.jpg`, `qc/A-f01-v2-plus-3-afters.jpg`, `qc/A-f01-mouth-zoom-2x.jpg` |
| `samples/compare/f01/before-v2-1440.webp` | 94.7 KB | `qc/A-f01-old-vs-new.jpg`, `qc/A-f01-v2-plus-3-afters.jpg`, `qc/A-f01-mouth-zoom-2x.jpg` |
| `samples/compare/m01/before-v2-480.webp` | 19.6 KB | `qc/A-m01-old-vs-new.jpg`, `qc/A-m01-v2-plus-3-afters.jpg`, `qc/A-m01-mouth-zoom-2x.jpg` |
| `samples/compare/m01/before-v2-960.webp` | 66.5 KB | `qc/A-m01-old-vs-new.jpg`, `qc/A-m01-v2-plus-3-afters.jpg`, `qc/A-m01-mouth-zoom-2x.jpg` |
| `samples/compare/m01/before-v2-1440.webp` | 99.3 KB | `qc/A-m01-old-vs-new.jpg`, `qc/A-m01-v2-plus-3-afters.jpg`, `qc/A-m01-mouth-zoom-2x.jpg` |

| model | attempts | outside-mask diff | face SSIM vs old | mask area | result |
|---|---|---|---|---|---|
| f01 | 1/5 | PNG master max **0**; v2-960 vs CDN before-960 mean **0.67** (p99 4); v2-480 mean 0.695 | 960 **0.9355** · 480 0.9238 · master 0.9417 | 2.48% | pass |
| m01 | 1/5 | PNG master max **0**; v2-960 vs CDN before-960 mean **0.714** (p99 5); v2-480 mean 0.686 | 960 **0.9193** · 480 0.9184 · master 0.9306 | 2.94% | pass |

Note: v2-1440 has no matching old 1440 of the same render; it is measured against the smile master resized to 1440 (outside mask = WebP noise only).

**Not changed** (expression audit sheets in `qc/A-expression-audit/`):

- **m02**: CDN 上 17 款 after 里 11 款是露齿笑（和旧原图一致），只有 6 款闭嘴（afro-men / bro-flow / curly-top-fade / long-layers-men / man-bun / modern-mullet）。按「多数 after」规则原图已一致，没做 v2。清单说「after 大多平静」与 CDN 实际不符，需 GUCCI 确认。
- **f02**: 30 款 after 里 22 款露齿笑（和原图一致），8 款闭嘴（blunt-bangs / french-bob / high-ponytail / italian-bob / long-bob-lob / long-straight / low-ponytail / sleek-middle-part）。按多数规则没改。注意 topic-bob-or-lob 用的 3 款恰好都是闭嘴款。
- **f04**: 34 款 after 全部是闭嘴浅笑，和原图一致，不改。
- **f03**: 抽查 34 款：闭嘴浅笑，和原图一致，不改。
- **m03**: 抽查 12 款：一致，不改。
- **m04**: 抽查 10 款：一致，不改。

## B1 + B2. Videos

| file | duration | resolution | size | check image |
|---|---|---|---|---|
| `videos/hero-going-short/hero-going-short-4x5.mp4` | 10.0 s | 1080x1350 | 443.6 KB | `qc/B-hero-going-short-9x16-frames.jpg` |
| `videos/hero-going-short/hero-going-short-4x5.webm` | 10.0 s | 1080x1350 | 286.0 KB | `qc/B-hero-going-short-9x16-frames.jpg` |
| `videos/hero-going-short/hero-going-short-9x16.mp4` | 10.0 s | 1080x1920 | 1002.9 KB | `qc/B-hero-going-short-9x16-frames.jpg` |
| `videos/hero-going-short/hero-going-short-cover-9x16.{jpg,webp}`, `videos/hero-going-short/hero-going-short-cover-4x5.{jpg,webp}` | cover | original + first style side by side | | `qc/B-hero-going-short-9x16-frames.jpg` |
| ↳ hero-going-short substitutions | none (all slugs 200 on CDN) | | | |
| `videos/topic-bangs/topic-bangs-4x5.mp4` | 8.5 s | 1080x1350 | 367.6 KB | `qc/B-topic-bangs-9x16-frames.jpg` |
| `videos/topic-bangs/topic-bangs-4x5.webm` | 8.5 s | 1080x1350 | 229.0 KB | `qc/B-topic-bangs-9x16-frames.jpg` |
| `videos/topic-bangs/topic-bangs-9x16.mp4` | 8.5 s | 1080x1920 | 823.2 KB | `qc/B-topic-bangs-9x16-frames.jpg` |
| `videos/topic-bangs/topic-bangs-cover-9x16.{jpg,webp}`, `videos/topic-bangs/topic-bangs-cover-4x5.{jpg,webp}` | cover | original + first style side by side | | `qc/B-topic-bangs-9x16-frames.jpg` |
| ↳ topic-bangs substitutions | none (all slugs 200 on CDN) | | | |
| `videos/topic-bob-or-lob/topic-bob-or-lob-4x5.mp4` | 8.5 s | 1080x1350 | 480.1 KB | `qc/B-topic-bob-or-lob-9x16-frames.jpg` |
| `videos/topic-bob-or-lob/topic-bob-or-lob-4x5.webm` | 8.5 s | 1080x1350 | 318.3 KB | `qc/B-topic-bob-or-lob-9x16-frames.jpg` |
| `videos/topic-bob-or-lob/topic-bob-or-lob-9x16.mp4` | 8.5 s | 1080x1920 | 1042.0 KB | `qc/B-topic-bob-or-lob-9x16-frames.jpg` |
| `videos/topic-bob-or-lob/topic-bob-or-lob-cover-9x16.{jpg,webp}`, `videos/topic-bob-or-lob/topic-bob-or-lob-cover-4x5.{jpg,webp}` | cover | original + first style side by side | | `qc/B-topic-bob-or-lob-9x16-frames.jpg` |
| ↳ topic-bob-or-lob substitutions | none (all slugs 200 on CDN) | | | |
| `videos/topic-small-change/topic-small-change-4x5.mp4` | 8.5 s | 1080x1350 | 587.1 KB | `qc/B-topic-small-change-9x16-frames.jpg` |
| `videos/topic-small-change/topic-small-change-4x5.webm` | 8.5 s | 1080x1350 | 439.9 KB | `qc/B-topic-small-change-9x16-frames.jpg` |
| `videos/topic-small-change/topic-small-change-9x16.mp4` | 8.5 s | 1080x1920 | 1157.5 KB | `qc/B-topic-small-change-9x16-frames.jpg` |
| `videos/topic-small-change/topic-small-change-cover-9x16.{jpg,webp}`, `videos/topic-small-change/topic-small-change-cover-4x5.{jpg,webp}` | cover | original + first style side by side | | `qc/B-topic-small-change-9x16-frames.jpg` |
| ↳ topic-small-change substitutions | none (all slugs 200 on CDN) | | | |
| `videos/topic-waves/topic-waves-4x5.mp4` | 8.5 s | 1080x1350 | 388.3 KB | `qc/B-topic-waves-9x16-frames.jpg` |
| `videos/topic-waves/topic-waves-4x5.webm` | 8.5 s | 1080x1350 | 264.8 KB | `qc/B-topic-waves-9x16-frames.jpg` |
| `videos/topic-waves/topic-waves-9x16.mp4` | 8.5 s | 1080x1920 | 844.2 KB | `qc/B-topic-waves-9x16-frames.jpg` |
| `videos/topic-waves/topic-waves-cover-9x16.{jpg,webp}`, `videos/topic-waves/topic-waves-cover-4x5.{jpg,webp}` | cover | original + first style side by side | | `qc/B-topic-waves-9x16-frames.jpg` |
| ↳ topic-waves substitutions | none (all slugs 200 on CDN) | | | |
| `videos/topic-men-new-cut/topic-men-new-cut-4x5.mp4` | 8.5 s | 1080x1350 | 390.6 KB | `qc/B-topic-men-new-cut-9x16-frames.jpg` |
| `videos/topic-men-new-cut/topic-men-new-cut-4x5.webm` | 8.5 s | 1080x1350 | 230.0 KB | `qc/B-topic-men-new-cut-9x16-frames.jpg` |
| `videos/topic-men-new-cut/topic-men-new-cut-9x16.mp4` | 8.5 s | 1080x1920 | 775.9 KB | `qc/B-topic-men-new-cut-9x16-frames.jpg` |
| `videos/topic-men-new-cut/topic-men-new-cut-cover-9x16.{jpg,webp}`, `videos/topic-men-new-cut/topic-men-new-cut-cover-4x5.{jpg,webp}` | cover | original + first style side by side | | `qc/B-topic-men-new-cut-9x16-frames.jpg` |
| ↳ topic-men-new-cut substitutions | none (all slugs 200 on CDN) | | | |
| `videos/topic-fade-height/topic-fade-height-4x5.mp4` | 8.5 s | 1080x1350 | 369.1 KB | `qc/B-topic-fade-height-9x16-frames.jpg` |
| `videos/topic-fade-height/topic-fade-height-4x5.webm` | 8.5 s | 1080x1350 | 235.1 KB | `qc/B-topic-fade-height-9x16-frames.jpg` |
| `videos/topic-fade-height/topic-fade-height-9x16.mp4` | 8.5 s | 1080x1920 | 821.1 KB | `qc/B-topic-fade-height-9x16-frames.jpg` |
| `videos/topic-fade-height/topic-fade-height-cover-9x16.{jpg,webp}`, `videos/topic-fade-height/topic-fade-height-cover-4x5.{jpg,webp}` | cover | original + first style side by side | | `qc/B-topic-fade-height-9x16-frames.jpg` |
| ↳ topic-fade-height substitutions | none (all slugs 200 on CDN) | | | |

Rules applied: hard cuts + slider wipes only (no morph / no interpolation), change within the first second, ≥1.2 s per style (B1/B2), fixed label position, burned-in captions, corner "AI preview · model photo", final 1.5 s still with 4-up + "Try it on your photo · aiproshots.com", silent.
- hero-going-short: f01 before-v2.
- topic-bangs: f01 before-v2.
- topic-bob-or-lob: f02 has no v2 (before unchanged, see A): original is the open-mouth smile while these 3 afters are closed-mouth.
- topic-small-change: f04 before unchanged (already matches its afters).
- topic-waves: f01 before-v2.
- topic-men-new-cut: m01 before-v2. Label "Curtains" = curtains-middle-part.
- topic-fade-height: m01 before-v2.

## B3. Videos

| file | duration | resolution | size | check image |
|---|---|---|---|---|
| `videos/one-face-12-haircuts-f01/one-face-12-haircuts-f01-4x5.mp4` | 16.5 s | 1080x1350 | 875.0 KB | `qc/B-one-face-12-haircuts-f01-all-cuts.jpg` |
| `videos/one-face-12-haircuts-f01/one-face-12-haircuts-f01-4x5.webm` | 16.5 s | 1080x1350 | 483.4 KB | `qc/B-one-face-12-haircuts-f01-all-cuts.jpg` |
| `videos/one-face-12-haircuts-f01/one-face-12-haircuts-f01-9x16.mp4` | 16.5 s | 1080x1920 | 1893.4 KB | `qc/B-one-face-12-haircuts-f01-all-cuts.jpg` |
| `videos/one-face-12-haircuts-f01/one-face-12-haircuts-f01-cover-9x16.{jpg,webp}`, `videos/one-face-12-haircuts-f01/one-face-12-haircuts-f01-cover-4x5.{jpg,webp}` | cover | original + first style side by side | | `qc/B-one-face-12-haircuts-f01-all-cuts.jpg` |
| ↳ one-face-12-haircuts-f01 substitutions | none (all slugs 200 on CDN) | | | |
| `videos/one-face-12-haircuts-m01/one-face-12-haircuts-m01-4x5.mp4` | 16.5 s | 1080x1350 | 788.4 KB | `qc/B-one-face-12-haircuts-m01-all-cuts.jpg` |
| `videos/one-face-12-haircuts-m01/one-face-12-haircuts-m01-4x5.webm` | 16.5 s | 1080x1350 | 445.2 KB | `qc/B-one-face-12-haircuts-m01-all-cuts.jpg` |
| `videos/one-face-12-haircuts-m01/one-face-12-haircuts-m01-9x16.mp4` | 16.5 s | 1080x1920 | 1858.9 KB | `qc/B-one-face-12-haircuts-m01-all-cuts.jpg` |
| `videos/one-face-12-haircuts-m01/one-face-12-haircuts-m01-cover-9x16.{jpg,webp}`, `videos/one-face-12-haircuts-m01/one-face-12-haircuts-m01-cover-4x5.{jpg,webp}` | cover | original + first style side by side | | `qc/B-one-face-12-haircuts-m01-all-cuts.jpg` |
| ↳ one-face-12-haircuts-m01 substitutions | none (all slugs 200 on CDN) | | | |

Rules applied: hard cuts + slider wipes only (no morph / no interpolation), change within the first second, ≥1.2 s per style (B1/B2), fixed label position, burned-in captions, corner "AI preview · model photo", final 1.5 s still with 4-up + "Try it on your photo · aiproshots.com", silent.
- one-face-12-haircuts-f01: 16.5 s: original 0–0.8 s, 12 hard cuts × 1.0 s, 4-up 12.8–15.0 s, end still 15.0–16.5 s. Picked by hair-silhouette farthest-point from the 27 closed-mouth, non-color f01 afters (open-mouth afters excluded so they match before-v2).
- one-face-12-haircuts-m01: 16.5 s, same structure; picked from the 19 closed-mouth m01 afters.
