R7: bold, animated overlay captions

Overlays are now large (fontsize 84) with a thick outline + strong drop shadow so
they read on any background, and animate in: a snappy 0.18s alpha punch plus a
34px rise-up over 0.22s, with a soft ease-out. Placed on the payoff beat so the
entrance lands on the musical/edit accent. This presents the Tier-2 vision
director semantic caption ("STRIKE") boldly. Overlay style/animation asserted in
the existing overlay test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
This commit is contained in:
JSLMPR 2026-07-24 00:05:39 +02:00
parent f03d5472c5
commit 67bdf683ca
3 changed files with 19 additions and 14 deletions

View File

@ -51,9 +51,11 @@ Legend: ✅ implemented · ⏳ planned (see `cinematic-highlight-poc-plan.md` P5
per-shot push-in (sub-segment splitting would reset the zoom); needs a time-varying `setpts` or a push-in per-shot push-in (sub-segment splitting would reset the zoom); needs a time-varying `setpts` or a push-in
that spans sub-segments. The crossfade already softens the *cut* into the payoff. that spans sub-segments. The crossfade already softens the *cut* into the payoff.
## R7 — Overlays: bold, animated, synced ⏳ (P5.12) ## R7 — Overlays: bold, animated, synced ✅
- Title/label text scaled to the frame, with an entrance (scale/fade) timed to the musical/edit accent — not - **Rule:** captions are large (fontsize 84), thick-outlined + drop-shadowed for legibility on any background,
small static text dropped on screen. with a snappy entrance (0.18 s alpha punch + 34 px rise-up over 0.22 s) and a soft ease-out. `drawTextFilter`.
- **Ties to Tier 2 (R9):** the vision director produces the caption *text* ("STRIKE"); R7 makes it *land*. The
overlay is placed on the payoff beat, so it's timed to the musical/edit accent. Verified on bowling.
## R8 — Music sync: climax on the payoff ⏳ (P5.13) ## R8 — Music sync: climax on the payoff ⏳ (P5.13)
- **Measure:** the payoff beat's timeline position. - **Measure:** the payoff beat's timeline position.

View File

@ -953,15 +953,16 @@ public class HighlightFfmpegRenderer {
private String drawTextFilter(TextOverlay overlay) { private String drawTextFilter(TextOverlay overlay) {
String start = Double.toString(overlay.timelineStartSeconds()); String start = Double.toString(overlay.timelineStartSeconds());
String end = Double.toString(overlay.timelineEndSeconds()); String end = Double.toString(overlay.timelineEndSeconds());
// Smooth alpha fade: ramp up over the first 0.4 s, hold, ramp down over the last 0.4 s. // Snappy entrance timed to the beat: alpha punches in over 0.18 s, holds, eases out over 0.35 s.
String alpha = "if(lt(t\\," + start + "+0.4)\\,(t-" + start + ")/0.4\\," String alpha = "if(lt(t\\," + start + "+0.18)\\,(t-" + start + ")/0.18\\,"
+ "if(lt(t\\," + end + "-0.4)\\,1\\,(" + end + "-t)/0.4))"; + "if(lt(t\\," + end + "-0.35)\\,1\\,max(0\\,(" + end + "-t)/0.35)))";
// Premium caption: refined size, soft drop shadow, thin subtle border for legibility. // Rise-up entrance: start 34 px below the resting position and ease up over 0.22 s.
return ("drawtext=text='%s':x=%s:y=%s:fontsize=48:fontcolor=white" String y = "(" + overlayY(overlay.placement()) + ")+34*(1-min(1\\,(t-" + start + ")/0.22))";
+ ":shadowcolor=black@0.55:shadowx=2:shadowy=2:borderw=1:bordercolor=black@0.35" // Bold cinematic caption: large, thick outline + strong drop shadow so it reads on any background.
return ("drawtext=text='%s':x=%s:y=%s:fontsize=84:fontcolor=white"
+ ":shadowcolor=black@0.7:shadowx=3:shadowy=3:borderw=4:bordercolor=black@0.6"
+ ":alpha='%s':enable='between(t\\,%s\\,%s)'") + ":alpha='%s':enable='between(t\\,%s\\,%s)'")
.formatted(escapeDrawText(overlay.text()), overlayX(overlay.placement()), .formatted(escapeDrawText(overlay.text()), overlayX(overlay.placement()), y, alpha, start, end);
overlayY(overlay.placement()), alpha, start, end);
} }
private String overlayX(String placement) { private String overlayX(String placement) {

View File

@ -105,9 +105,11 @@ class HighlightFfmpegRendererTest {
assertThat(filter) assertThat(filter)
.contains("drawtext=text='First light.'") .contains("drawtext=text='First light.'")
.contains("fontsize=48") .contains("fontsize=84") // bold
.contains("shadowcolor=black@0.55:shadowx=2:shadowy=2") .contains("borderw=4:bordercolor=black@0.6") // thick outline
.contains("alpha='if(lt(t\\,0.5+0.4)") .contains("shadowcolor=black@0.7:shadowx=3:shadowy=3") // strong shadow
.contains("alpha='if(lt(t\\,0.5+0.18)") // snappy 0.18s punch-in
.contains("34*(1-min(1\\,(t-0.5)/0.22))") // rise-up entrance animation
.contains("enable='between(t\\,0.5\\,3.0)'"); .contains("enable='between(t\\,0.5\\,3.0)'");
} }