Commit Graph

133 Commits

Author SHA1 Message Date
JSLMPR 31efe33081 Add CI workflow and a production README
- .github/workflows/ci.yml: build + test on every push/PR (JDK 21 + ffmpeg,
  mvn -B verify, uploads surefire reports). Closes the "no CI" gap.
- README.md: honest overview of the local offline highlight pipeline, the R1-R9
  cinematic quality rules, how to build/test/run, the local models and their
  (non-commercial) licenses, constraints, and limitations. Explicitly states it is
  a PoC, not production-hardened. Closes the "no README" gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
2026-07-24 12:17:08 +02:00
JSLMPR 2552a7cf06 docs(memory): update handover HEAD to 9fb24e8; R1-R9 complete 2026-07-24 12:06:11 +02:00
JSLMPR 9fb24e8f19 R6 speed-ramp: ease into slow-motion
A slow-motion shot now decelerates smoothly instead of snapping to slow-mo:
speedRampSetpts builds a log-integrated setpts that ramps playback speed from
normal (1.0) down to below the target across the shot. The shot stays a SINGLE
segment (so the R5 per-shot push-in is preserved) and the existing -t pin keeps
the planned output duration. Normal-speed shots keep a plain constant setpts.
Unit-tested; verified in a real render (payoff carries the ramp, output valid).
Completes R6 (crossfades + speed-ramp).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
2026-07-24 12:04:32 +02:00
JSLMPR 4d2cea15a7 Fix: music/audio cut off early with crossfades (separate video+audio passes)
R6 ran the video xfade and audio acrossfade in one filtergraph on the same inputs,
which starved the audio path and truncated it (final audio 4.2s vs 9.7s video) --
so the music appeared to end early. Split into two passes (xfadeVideoCommand video
only, acrossfadeAudioCommand audio only) and mux them. Verified: final audio now
9.8s matching the 9.67s video, music present through the end. Isolation and unit
tests confirm the two chains each produce the full compressed length.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
2026-07-24 11:52:43 +02:00
JSLMPR 99ec273020 docs(memory): record current HEAD (a2a7d7d) + tmpdir/sandbox workaround
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
2026-07-24 09:42:57 +02:00
JSLMPR a2a7d7d736 R8: swell the music into the payoff
MusicGen's internal structure is uncontrolled, so the mix applies a deterministic
swell envelope to the score (volume='min(1,0.5+0.5*t/peak)':eval=frame): the music
amplitude rises from 0.5x to full over the run-up to the payoff, then holds. The
peak is the payoff (slow-motion) beat's timeline midpoint on the crossfade-
compressed timeline -- generic, driven only by the plan, no content assumptions.
No-ops when there is no slow-mo beat. Filter string unit-tested; verified in a real
render (swell peaks at 6.83s, output -16.1 LUFS).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
2026-07-24 09:37:43 +02:00
JSLMPR 407f6b5e04 Tier-2 caption-driven shot selection (semantic editor, not just decorator)
The vision director now captions several beat frames (two questions per frame in
one worker call: a discriminative description + a punchy label) and turns the
descriptions into a per-window "highlight-worthiness" curve via generic
emotion/action/idle keyword scoring (semanticScore/semanticCurve). The montage
director blends that curve with audio to place the payoff on the semantically
strongest moment; the payoff label becomes the bold overlay and the description
flavors the music. Everything is content-agnostic and fails soft to the measured
cut. Verified on bowling: the payoff moved onto moondream's detected celebration.

Honest limit: a small VLM on distant subjects is only weakly discriminative;
descriptive questions beat terse ones (which collapse to a constant answer).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
2026-07-24 01:31:48 +02:00
JSLMPR 67bdf683ca 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
2026-07-24 00:05:39 +02:00
JSLMPR f03d5472c5 R6: cross-dissolve transitions between montage beats
Beats now blend instead of hard-cutting (incl. the cut into the slow-mo payoff)
via an xfade + acrossfade chain (xfadeTimelineCommand). The timeline compresses
by (n-1)*xf, so shiftOverlayForCrossfade re-times overlays and the reported
duration is reduced to keep overlays, loudness mastering, and QA aligned. Opt-in
via editing.crossfade-seconds (0 = hard cuts default; localpoc 0.25), clamped to
half the shortest beat. Offset math + overlay shift unit-tested; verified on the
bowling cut (visible dissolve, STRIKE overlay stayed on the payoff).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
2026-07-23 23:41:12 +02:00
JSLMPR 30c832e9d5 Accurate loudness mastering: measure the finished file and correct to target
Single-pass loudnorm in the mix is only ~+/-2 LUFS accurate, so the auto-rendered
output could land quiet (e.g. -18.7 LUFS vs the -16 target). After the mix,
probeIntegratedLoudness measures the file, loudnessGainDb computes the corrective
gain, and masterLoudness applies it with a brickwall limiter for true peak. No-ops
when already on target or when the measurement is implausible; handles MusicGen
loudness variance. loudnessGainDb unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
2026-07-23 23:09:06 +02:00
JSLMPR 1da0661eea Automatic two-tier highlight director + source-adaptive rendering
Rendering (whole service, driven by source measurements, not constants):
- Orientation-aware geometry: FfmpegClipInspector reads display rotation and
  stores effective dims; HighlightFfmpegRenderer.outputGeometry renders portrait
  sources portrait and skips the 2.39 letterbox on portrait (landscape unchanged).
- Dynamic exposure: probeSourceLuma measures the frames; exposureNormalizationFilter
  maps the mean toward a target; the grade is now exposure-preserving (no crushed
  subjects: bowling final went ~63 -> ~100 mean luma).
- Motion-adaptive in-shot push-in (zoompan), amount from per-shot YDIF.
- Audio mix ducks source audio under the generated score so it leads.
- Highlight duration is no longer capped (validator + config).

Automatic director (plans were hand-authored before):
- Tier 1 HighlightMontageDirector: composes the montage from measured motion (YDIF)
  and audio-energy (RMS) curves -- setup, continuous action/tension, slow-mo payoff
  on the audio climax, resolution button, camera-whip tail trimmed.
- Tier 2 HighlightVisionDirector + tools/vision_caption.py: a local, offline
  vision-language model (moondream2) captions the payoff frame and augments the
  montage with a semantic overlay ("STRIKE") and scene-informed music; fails soft.
- Wired into the scheduler behind auto-director-enabled / vision-director-enabled
  (on in the localpoc profile).

Docs: cinematic-quality-rules.md (R1-R5, R9 both tiers), poc-plan milestones.
Tests: mvn -o verify -> 262 passing, 0 failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPuJXQyAeWpFcTtcnxo1UN
2026-07-23 22:49:05 +02:00
JSLMPR 8b68116dae docs: record Phase 5 next-steps roadmap
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 22:51:18 +02:00
JSLMPR b3718dd0b8 Add cinematic montage mode (break free of the 8-35s highlight windows)
A montage edits many short shots pulled from ANYWHERE in the source (not a few
fixed contiguous highlight windows), sequenced establishing -> quick detail cuts ->
slow-motion hero, over one continuous music bed -- much closer to how a car film is
actually cut.

- MontagePlan model + director/montage.json shot list (per-shot source time,
  duration, punch-in zoom, speed).
- HighlightDirectorFlowService.processMontage: builds one EditPlan from the shot
  list (bypassing the highlight-window validator), one continuous music cue,
  distributed voiceover, and titles; reuses asset prep + worker + renderer.
- Renderer: explicit per-shot framing via a "zoom=" token (falls back to the
  progressive punch-in); pin each segment to its exact target duration with -t so
  frame-quantization drift cannot accumulate across many short shots.

Verified on the DJI source: 18 shots, 16.7 s, -16.5 LUFS, TP -2.8 dBTP, QA green.
The edit now reads as a real montage (varied framing + detail cuts + hero) rather
than slow pans. mvn -o verify green (248). Remaining ceiling is the source footage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 22:35:08 +02:00
JSLMPR daa58a8c8d Dynamic multi-cut edit: tighter framing + progressive punch-in per beat
Turn each beat from one long pan into an edit:
- Flow splits every beat into contiguous cuts (opening 2, rising 3, hero 2) with
  hard cuts between and sequential timeline positions; playback speed is preserved
  so the hero stays slow-motion across its cuts.
- Renderer frames each cut with a tighter center crop that fills the frame and
  hides the mundane location, plus a progressive punch-in (each successive cut of a
  beat steps tighter), so the sequence reads as deliberate.
- Widen the duration QA tolerance to the frozen acceptance value (0.25 s): multi-cut
  and slow-motion accumulate small per-segment frame-quantization drift.

Verified: 7 cuts (2/3/2), final 27.4 s, TP -2.7 dBTP. mvn -o verify green (248).
Known follow-up: integrated loudness runs low (~-18.7 LUFS) on the sparse music-bed
mix; single-pass loudnorm undershoots -- needs two-pass or a louder bed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 19:05:16 +02:00
JSLMPR d3d2a1b14e Soften voiceover entry/exit with short fades
Add afade in (0.12s) / out (0.25s) to each narration line so it eases in and out
instead of hard-cutting -- the hard cut read as un-cinematic. Voice remains
present and leading (measured onset ramp -18 -> -17 dB).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 18:19:27 +02:00
JSLMPR aae92ed7a5 Fix: voiceover was silently dropped from every mix; remove SFX gating
Root cause of "no voiceover": in audioMixCommand the [voice] label was used both
as the sidechaincompress key AND as an amix input. FFmpeg does NOT auto-split a
reused label, so the ducking sidechain consumed the voice entirely and the amix
reference got no audio -- the narration never made it into any final mix (every
render played ducked music with a silent hole where the voice should be).

Fix: explicitly `asplit=2[voice_key][voice_mix]` so one copy keys the duck and one
copy stays in the mix. Measured: voice window went from ~-30..-53 dB (silent) to
~-16 dB (present, leading the bed). Revert the compensating +8 dB over-boost back
to unity; music bed lowered to -14 dB so narration leads cleanly.

Also make SFX direction OPTIONAL in the validator (matching the earlier
voiceover-optional change) so a clean music+narration edit with no sound effects
validates. Regression test added for the asplit. mvn -o verify green (248 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 18:03:38 +02:00
JSLMPR 235810e253 docs: log Phase 4 cinematic pass progress
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 16:15:02 +02:00
JSLMPR 842f2a7404 Cinematic pass: 2.39 letterbox, film grain, optional voiceover
Push the highlight output toward a genuinely cinematic look after a human review
found it functional-but-not-cinematic:

- HighlightFfmpegRenderer: add a 2.39:1 letterbox and subtle film grain to each
  graded segment; raise overlay placement above the letterbox bar. (A time-based
  crop-zoom push-in was prototyped and removed: FFmpeg crop cannot use the `t`
  variable for width/height; a zoompan push-in is a possible follow-up.)
- HighlightDirectorPlanValidator: make voiceover OPTIONAL so a music-driven edit
  can carry no narration (lines still validated when present).

Combined with a no-voiceover, driving-orchestral, slow-motion-hero director plan,
this yields a letterboxed, richly graded, music-led cinematic cut. mvn -o verify
green (247 tests, 0 failures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 16:13:19 +02:00
JSLMPR 3221b9827b docs: add P3.6 acceptance review (frozen thresholds + rubric); Gate A measured PASS
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 11:59:06 +02:00
JSLMPR 6600ded929 docs: P3.5 mix/ducking review findings (calibration deferred to P3.6)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 11:43:10 +02:00
JSLMPR 56205d91f2 Fix local CV visual-analysis client (HTTP/1.1) to enable YOLO selection
The local-cv provider was never exercised end to end (its bootstrap was
prohibited), and hid a latent bug: the JDK HttpClient defaulted to HTTP/2 and
negotiated an h2c cleartext upgrade that the HTTP/1.1-only worker (uvicorn/h11)
mishandled by dropping the request body, so every call returned HTTP 422. Pin the
client to HTTP/1.1.

With this fix the resident YOLOv8 worker (run offline against the existing
yolov8n.pt, no bootstrap script) classifies the sample source as CAR_VLOG at 0.95
with measured OpenCV blur/exposure and a real car label, replacing the previous
filename-keyword GENERIC_VLOG fallback. Provider remains opt-in via runtime
override; the committed localpoc profile keeps the heuristic default.

Also ignore yolov*.pt.license.txt (provenance for the git-ignored weights).
mvn -o verify green (247 tests, 0 failures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 11:14:48 +02:00
JSLMPR 9d217c249e docs: log beat-specific grading milestone
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 10:31:41 +02:00
JSLMPR a0b6023b9c Beat-specific cinematic grading for highlights
The highlight renderer now selects a filmic grade per story beat, read from the
EditPlan style key (safeKey: style_<category>_<storyPurpose>), so no change to the
shared EditDecision or its serialization is needed:

- opening_hook: calmer, cooler, softer contrast (linear_contrast, gentle vignette)
- rising_energy / default: balanced base grade (medium_contrast)
- hero_payoff: richest, warmer, stronger S-curve and deeper vignette (strong_contrast)

Threads plan.style() into segmentCommand -> cinematicVisualFilter. Multi-clip
FfmpegEditRenderer untouched. Test added; mvn -o verify green (247 tests, 0 failures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 10:19:52 +02:00
JSLMPR 1a1566adda docs: log Phase 3.3 overlay styling milestone
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 10:01:01 +02:00
JSLMPR 24bd1d78f2 Premium highlight overlay styling (P3.3)
Replace the plain hard-cut white caption with a refined 48px caption, a soft drop
shadow, a thin subtle border, and a smooth alpha fade in and out (0.4s ramps)
within each overlay timeline window. Highlight renderer only; the multi-clip
renderer and overlay placement/safe-area logic are unchanged. Test added;
mvn -o verify green (246 tests, 0 failures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 09:52:12 +02:00
JSLMPR 0dd64d96cd docs: log Phase 2 render + Phase 3.1/3.2 milestones
Record the operator-approved first render and the verified grade + true-peak
limiter results (TP -1.7/-2.7/-2.8 dBFS, I -16.3 LUFS) with commit references.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 09:41:51 +02:00
JSLMPR 0fcfca9283 Improve highlight render: filmic grade + true-peak limiter
Phase 3 quality fixes for the highlight renderer (multi-clip FfmpegEditRenderer
left untouched):

- cinematicVisualFilter: replace the weak fixed eq with a deliberate filmic grade
  (curves medium_contrast S-curve + teal-orange colorbalance + eq + unsharp +
  vignette). Richer blue, warm highlights, tonal contrast. Still one uniform look;
  beat/category-specific grading is future work (needs a validated grade enum).
- audio mix: add a brickwall limiter (alimiter limit=0.72) after loudnorm. The
  first render clipped at 0.0 dBFS true peak; measured re-render now lands
  -1.7/-2.7/-2.8 dBFS per highlight (all within the -1.5 dBTP gate), integrated
  loudness -16.3 LUFS.

Test updated; mvn -o verify green (245 tests, 0 failures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 09:38:47 +02:00
JSLMPR 588c652a2f Add opt-in localpoc profile for the local highlight PoC
Activated with --spring.profiles.active=localpoc. Points only at pre-provisioned
local model paths (Piper voice, MusicGen, AudioLDM2), disables bootstrap
auto-start, uses heuristic visual analysis, isolates PoC input/output dirs, and
keeps render disabled + director approval required. Base/production defaults are
untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-22 09:38:47 +02:00
JSLMPR a95d1fa0ac Wire local generative audio models into the highlight asset worker
Replace the unusable audiocraft path (requires xformers, which has no Intel-Mac
build) with runtimes proven to work offline on this machine:
- music: transformers MusicGen (facebook/musicgen-small)
- sfx:   diffusers AudioLDM2 (cvssp/audioldm2), resampled 16k -> 48k
- voiceover: Piper (unchanged), normalized to 48 kHz mono

The worker CLI contract and exit codes are preserved, so the Java
LocalAssetSynthesizer license gate and fail-closed behavior are unchanged.
Add tools/provision_local_models.py to materialize models into models/ from the
local HF cache with no network. Models and their license sidecars live under the
git-ignored models/ dir; both audio models are non-commercial (CC-BY-NC-4.0 /
CC-BY-NC-SA-4.0), recorded for later production review.

Add docs/cinematic-highlight-poc-plan.md tracking the PoC plan and milestones.
mvn -o verify: 245 tests, 0 failures/errors/skips (unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bg76sLc43Wc3j5ZcLkboYR
2026-07-21 23:00:57 +02:00
JSLMPR f6dc6b8a83 Harden cinematic highlight planning and rendering
- require explicit approval and skip ineligible highlight projects
  - validate timing and deduplicate candidate ranges
  - enforce licensed local assets and fail-closed generation
  - preserve video duration when mixing generated audio
  - refresh skill runbooks and regression coverage
2026-07-21 18:32:35 +02:00
JSLMPR 97ba827d50 add asset generation 2026-07-21 10:03:27 +02:00
JSLMPR 618ba9af49 - Implement end-to-end cinematic highlight rendering pipeline
- Wire visual effects and local asset generation into highlight flow
  - Add director plan rendering with local asset worker
2026-07-12 01:20:18 +02:00
JSLMPR adc979eea8 Add local CV worker readiness checks 2026-07-11 17:46:40 +02:00
JSLMPR 80eca92d56 Add highlight rendering gap closure plan 2026-07-11 17:26:36 +02:00
JSLMPR 8195b58552 Add rich video editing diagnostics 2026-07-11 17:11:35 +02:00
JSLMPR e2f49e2e5a Add managed local CV worker startup 2026-07-11 16:50:54 +02:00
JSLMPR 9dc720b0cc Add runnable local CV model worker 2026-07-11 16:40:59 +02:00
JSLMPR 82e557905e Add local CV visual analysis provider 2026-07-11 10:49:11 +02:00
JSLMPR eea619a2d3 Add highlight visual analysis 2026-07-11 10:35:07 +02:00
JSLMPR fc17578549 Add highlight audio analysis 2026-07-11 10:23:07 +02:00
JSLMPR 5d5f8022f2 Add highlight scene segmentation 2026-07-11 10:16:07 +02:00
JSLMPR ed337c1268 Add highlight source analysis 2026-07-11 09:59:55 +02:00
JSLMPR 0b6e5afddf Add highlight source scheduler 2026-07-11 09:50:11 +02:00
JSLMPR 5ba108f740 Add highlight project folder contract 2026-07-11 09:42:55 +02:00
JSLMPR 9536928054 Add cinematic edit approval gate 2026-07-11 09:36:56 +02:00
JSLMPR 2dc3ec97f4 Add cinematic benchmark metrics 2026-07-11 09:28:13 +02:00
JSLMPR 72a0626ab7 Add cinematic category fixture integration tests 2026-07-11 01:29:23 +02:00
JSLMPR 2ec2b9c37f Add cinematic editing operator runbook 2026-07-11 01:26:46 +02:00
JSLMPR 9b56e89b4c Add cinematic render QA report 2026-07-11 01:25:38 +02:00
JSLMPR 2d0c331710 Add cinematic dynamic crop rendering 2026-07-11 01:20:39 +02:00