diff --git a/.gitignore b/.gitignore index fddc828..07621c0 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ yolov*.pt # Local model bundle for the cinematic highlight PoC (large, provisioned out-of-band) /models/ .claude/settings.local.json +yolov*.pt.license.txt diff --git a/docs/cinematic-highlight-poc-plan.md b/docs/cinematic-highlight-poc-plan.md index ce20619..01d2e2a 100644 --- a/docs/cinematic-highlight-poc-plan.md +++ b/docs/cinematic-highlight-poc-plan.md @@ -83,7 +83,15 @@ Ranked from the first render's evidence: h1, 0.2 dB over — tightened to 0.72 for inter-sample margin.) Test added. - [x] P3.3 Overlay styling: refined 48px caption, soft drop shadow, thin subtle border, smooth alpha fade in/out (0.4s ramps). Verified rendering; committed 24bd1d7. (Placement/safe-area unchanged.) -- [ ] P3.4 Real CV visual analysis (resident model, no bootstrap script) to improve candidate selection. +- [x] P3.4 **Real CV visual analysis via YOLOv8** (resident model, NO bootstrap script). Deps installed into + an isolated `.venv-local-cv`; uvicorn worker run directly against the existing `yolov8n.pt` offline + (`YOLO_OFFLINE=True`, telemetry sync off). Fixed a latent bug: the Java CV client negotiated HTTP/2 + (h2c) which the HTTP/1.1-only worker mishandled (422/empty body) — pinned HTTP/1.1. Result on the DJI + source: category GENERIC_VLOG@0.25 -> **CAR_VLOG@0.95** (real `car` detection), measured blur 0.191 / + exposure 0.95 (OpenCV), method local_cv_worker_opencv_yolo. Provider stays OPT-IN via runtime override + (committed localpoc profile keeps the safe heuristic default; worker started manually). + CAVEATS: yolov8n.pt is **AGPL-3.0** (production blocker on this license alone; see yolov8n.pt.license.txt); + loopback HTTP is flagged non-compliant for a certified path; Haar face detector gives false positives. - [ ] P3.5 Music/SFX creative fit + ducking review; consider longer inference / better prompts. - [ ] P3.6 Freeze acceptance thresholds + blinded human creative review vs baseline before declaring success. @@ -104,7 +112,11 @@ Ranked from the first render's evidence: 1080p/27.3s; mvn verify 246/0/0/0. - 2026-07-22: **Beat-specific grading done & verified** (a0b6023). Grade varies per story beat; final re-render TP -2.3/-2.5/-2.8 dBFS, I -16.3 LUFS, 1080p/27.3s; mvn verify 247/0/0/0. Phase 3 visual/audio - craft items complete. Next: P3.4 real CV selection, P3.5 music/SFX fit, P3.6 frozen thresholds + review. + craft items complete. +- 2026-07-22: **P3.4 real CV (YOLOv8) done & verified.** Offline worker vs existing yolov8n.pt (no bootstrap). + Fixed latent HTTP/2 client bug. DJI source now classifies CAR_VLOG@0.95 with measured blur/exposure and a + real `car` label (was GENERIC_VLOG@0.25/filename heuristic). mvn verify 247/0/0/0. AGPL + loopback caveats + recorded. Remaining: P3.5 music/SFX fit, P3.6 frozen thresholds + blinded review. ## Deferred (until output-quality gate passes) Production hardening: Spring Security/OIDC, PostgreSQL/Testcontainers, containers/K8s, CI/CD, distributed diff --git a/src/main/java/org/example/videoclips/editing/LocalCvVisualAnalysisProvider.java b/src/main/java/org/example/videoclips/editing/LocalCvVisualAnalysisProvider.java index 04e7b26..d003da6 100644 --- a/src/main/java/org/example/videoclips/editing/LocalCvVisualAnalysisProvider.java +++ b/src/main/java/org/example/videoclips/editing/LocalCvVisualAnalysisProvider.java @@ -143,7 +143,12 @@ public class LocalCvVisualAnalysisProvider implements VisualAnalysisProvider { } private static class JdkHttpExecutor implements HttpExecutor { - private final HttpClient httpClient = HttpClient.newHttpClient(); + // Force HTTP/1.1: the default client negotiates HTTP/2 (h2c cleartext upgrade) for http://, which + // the HTTP/1.1-only local CV worker (uvicorn/h11) mishandles by dropping the request body, + // producing a 422. Pinning HTTP/1.1 makes the loopback call reliable. + private final HttpClient httpClient = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_1_1) + .build(); @Override public HttpResult execute(HttpCall call) throws IOException, InterruptedException {