Commit Graph

40 Commits

Author SHA1 Message Date
JSLMPR 9e401870a1 feat(cinematic): R10–R14 grade/cadence/edit rules + 420p compat fix
Add five source-adaptive cinematic rules, all content-agnostic and
  fail-soft, derived from a web review of the film look and how consumer
  AI editors (DJI LightCut, Insta360) auto-edit:

  - R10: 24fps cinematic cadence (output frame rate 30 -> 24)
  - R11: filmic tone-curve grade (lifted toe + highlight roll-off);
         optional licensed film LUT via lut3d, gated on a .license.txt
         sidecar (fails closed, no unlicensed asset applied)
  - R12: shutter-angle motion blur (tmix), toggle
  - R13: beat-synced cuts — snap cut boundaries onto the score's beat
         grid (HighlightBeatSync + tools/beat_detect.py, librosa)
  - R14: subject-tracking reframe — follow the detected subject instead
         of a static crop (HighlightSubjectTracker + tools/subject_track.py,
         YOLO/AGPL, non-commercial); renderer subjectFollowFilter
  - Fix: pin -pix_fmt yuv420p on every encode pass (was yuv444p, which
         browsers/QuickTime reject)

  R13/R14 are opt-in flags, on in localpoc. mvn verify: 288 tests green.
  E2e bowling re-render verified: 24fps, yuv420p, -16.0 LUFS, 3 cut
  boundaries beat-snapped, all 5 shots subject-followed.
  Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 12:36:18 +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 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 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 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 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 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 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 bb23ac80e1 Add cinematic audio mastering filters 2026-07-11 01:16:14 +02:00
JSLMPR e3d69ad209 Add cinematic edit asset provider interfaces 2026-07-11 00:59:04 +02:00
JSLMPR 5006dfd50b Add category-aware cinematic highlight planning 2026-07-11 00:50:12 +02:00
JSLMPR 5d889b00dc • working version but not cinematic 2026-07-11 00:32:46 +02:00
JSLMPR e5a86257d1 Document local AI director workflow 2026-07-10 19:05:19 +02:00
JSLMPR c3904b75c2 Add voiceover generation hook 2026-07-10 18:53:34 +02:00
JSLMPR 754bf35cbe Add cinematic editing configuration 2026-07-10 14:30:18 +02:00
JSLMPR 7fd92a72df Configure folder scheduler in YAML 2026-07-10 12:24:16 +02:00
JSLMPR b61ab0604a Improve folder scheduler diagnostics 2026-07-10 10:14:24 +02:00
JSLMPR 8e67566f0d Document local folder scheduler profile 2026-07-10 08:58:25 +02:00
JSLMPR 97cc4ff4e4 • Implemented the next Input Folder Scheduler Plan milestone: startup directory initialization.
Added src/main/java/org/example/videoclips/folder/FolderSchedulerDirectoryInitializer.java, which creates the configured input, output, working, processed, and rejected directories when video-clipping.folder-
  scheduler.enabled=true. It stays inactive by default, so normal startup will not fail on machines where /input and /output cannot be created.

  Updated docs/input-folder-scheduler-plan.md to mark milestone 3 complete.
2026-07-10 00:52:40 +02:00
JSLMPR 632959104b • The next step is done and the plan is updated. Autoscaling policies is now marked complete in docs/video-clipping-service-implementation-plan.md:717.
I added autoscaling-ready queue metrics in src/main/java/org/example/videoclips/observability/DatabaseQueueMetricsBinder.java:13, backed by new queue repository counters, and enabled Prometheus scraping via src/main/
  resources/application.properties:3 plus the Prometheus registry dependency in pom.xml:43. The policy itself is documented in docs/autoscaling-policies.md:1, including worker/API scaling rules and a sample KEDA ScaledObject.
  Coverage for the new gauges is in src/test/java/org/example/videoclips/observability/DatabaseQueueMetricsBinderTest.java:14.
2026-07-09 19:00:31 +02:00
JSLMPR 5565298351 • I completed the next plan item and updated the checklist. Queue visibility-timeout tuning is now marked done in docs/video-clipping-service-implementation-plan.md:716.
The main change is in src/main/java/org/example/videoclips/queue/DatabaseBackedClipJobQueueAdapter.java:52: the DB queue now reclaims stale PROCESSING messages after a configurable visibility timeout instead of leaving them
  stuck forever after a worker crash. I added video-clipping.database-queue.visibility-timeout-ms with a default of 900000 ms in src/main/java/org/example/videoclips/config/VideoClippingProperties.java:171 and src/main/
  resources/application.properties:19, plus coverage for stale-claim recovery in src/test/java/org/example/videoclips/queue/DatabaseBackedClipJobQueueAdapterTest.java:91. I also added the tuning note and sizing rule in docs/
  queue-visibility-timeout-tuning.md:1.
2026-07-09 18:37:57 +02:00
JSLMPR 1bd879f1e2 I completed the next plan step, Tenant quotas, and updated the plan to mark it done in docs/video-clipping-service-implementation-plan.md:701.
The implementation adds tenant-aware assets/jobs plus a configurable per-tenant active-job quota. The main enforcement is in src/main/java/org/example/videoclips/application/VideoAssetService.java:165, backed by tenant fields
  added to the domain/persistence model and migration src/main/resources/db/migration/V6__tenant_quota_support.sql:1. Requests can set X-Tenant-Id on asset creation, quota config lives in src/main/java/org/example/videoclips/
  config/VideoClippingProperties.java:39, and quota violations now return 429 via src/main/java/org/example/videoclips/api/ApiExceptionHandler.java:44.
2026-07-09 01:07:07 +02:00
JSLMPR 8cb232d70e Assets and clips now carry expiration timestamps in the domain model:
- src/main/java/org/example/videoclips/domain/VideoAsset.java
  - src/main/java/org/example/videoclips/domain/Clip.java

  That metadata is mapped through the in-memory and JPA repository paths, including:

  - src/main/java/org/example/videoclips/infrastructure/InMemoryVideoAssetRepository.java
  - src/main/java/org/example/videoclips/persistence/JpaVideoClippingRepository.java
  - src/main/java/org/example/videoclips/persistence/entity/VideoAssetEntity.java
  - src/main/java/org/example/videoclips/persistence/entity/ClipEntity.java

  I also added scheduled retention cleanup in src/main/java/org/example/videoclips/operations/RetentionCleanupJob.java, with configuration in src/main/java/org/example/videoclips/config/
  VideoClippingProperties.java and src/main/resources/application.properties. Right now it soft-expires assets by marking them deleted when their retention time passes. Clip retention timestamps
  are now assigned by both clipper adapters so the service has real expiration data to work with.

  What this does not do yet is physically delete expired clip objects or original source objects from object storage. The retention job is metadata-aware now, but object-store deletion still needs
  to be added through the storage port as the next step.
2026-07-09 00:28:05 +02:00
JSLMPR 3d961f2adf The new cleanup job is in src/main/java/org/example/videoclips/operations/LocalArtifactCleanupJob.java. It runs on a schedule and removes expired files from:
- the FFmpeg input directory
  - the FFmpeg output directory
  - tmp/in-memory-storage
  - tmp/stub-output

  The schedule and retention window are now configurable through src/main/java/org/example/videoclips/config/VideoClippingProperties.java and src/main/resources/application.properties via:

  - video-clipping.cleanup.enabled
  - video-clipping.cleanup.local-artifact-poll-interval-ms
  - video-clipping.cleanup.local-artifact-retention-hours

  This closes the first retention slice from the plan on the worker side. It does not yet delete expired original videos or clips from real object storage, and it does not do database-driven
  retention policies. That would be the next cleanup step.
2026-07-09 00:23:41 +02:00
JSLMPR 8f4804c2ab src/main/java/org/example/videoclips/processing/ClipProcessor.java now cleans up staged local input/output files in a finally block, so worker temp files are removed on success and failure
instead of accumulating. Cleanup is controlled by the new video-clipping.ffmpeg.cleanup-local-files property wired through src/main/java/org/example/videoclips/config/VideoClippingProperties.java
  and src/main/resources/application.properties.

  I also improved FFmpeg failure diagnostics in src/main/java/org/example/videoclips/processing/FfmpegVideoClipperAdapter.java. It now captures the merged process output and includes a trimmed
  version in the exception instead of only reporting the exit code, which makes job failures much easier to debug.
2026-07-09 00:21:36 +02:00
JSLMPR 66e998ee2a Generated clips now have a persisted object location instead of being metadata-only. I added objectKey to src/main/java/org/example/videoclips/domain/Clip.java, mapped it through JPA in src/main/
java/org/example/videoclips/persistence/entity/ClipEntity.java and src/main/java/org/example/videoclips/persistence/JpaVideoClippingMapper.java, and added the schema migration at src/main/
  resources/db/migration/V5__clip_object_key.sql.

  The clipper contract now returns both metadata and the local file path for each generated clip through src/main/java/org/example/videoclips/processing/GeneratedClip.java and the updated src/main/
  java/org/example/videoclips/processing/VideoClipperPort.java. Both adapters were updated:

  - src/main/java/org/example/videoclips/processing/StubVideoClipperAdapter.java now creates stub local output files
  - src/main/java/org/example/videoclips/processing/FfmpegVideoClipperAdapter.java now returns deterministic local output paths for generated segments

  On the storage side, ObjectStoragePort gained uploadGeneratedClip(...), with implementations in:

  - src/main/java/org/example/videoclips/storage/InMemoryObjectStorageAdapter.java
  - src/main/java/org/example/videoclips/storage/S3ObjectStorageAdapter.java

  And src/main/java/org/example/videoclips/processing/ClipProcessor.java now uploads each generated local file through storage before saving the clip record, so the job path is finally staged input
  -> process -> uploaded output -> persisted clip metadata.

  I also exposed objectKey in the clip response via src/main/java/org/example/videoclips/application/VideoAssetService.java and extended the MockMvc test to assert it exists in src/test/java/org/
  example/videoclips/api/VideoAssetControllerTest.java.
2026-07-09 00:16:49 +02:00
JSLMPR 9df97362ba The core change is that VideoAsset now carries sourceObjectKey, and that value is persisted through the JPA entity and migrations:
- src/main/java/org/example/videoclips/domain/VideoAsset.java
  - src/main/java/org/example/videoclips/persistence/entity/VideoAssetEntity.java
  - src/main/java/org/example/videoclips/persistence/JpaVideoClippingMapper.java
  - src/main/resources/db/migration/V4__video_asset_source_object_key.sql

  createAsset(...) now assigns a stable source object key of the form uploads/{uploadId}/source, and that value is exposed back through the asset read response in src/main/java/org/example/
  videoclips/application/VideoAssetService.java. I also threaded that source-object reference through the clipper contract in src/main/java/org/example/videoclips/processing/VideoClipperPort.java,
  so src/main/java/org/example/videoclips/processing/ClipProcessor.java passes a real persisted object reference into the processing adapter. The FFmpeg adapter now resolves its input path from
  that source object key instead of assuming {assetId}.mp4.

  I also completed the multipart upload seam:

  - UploadSession now stores providerUploadId
  - the storage port gained completeMultipartUpload(...)
  - the service now calls storage completion during uploads:complete
  - the new metadata is migrated in src/main/resources/db/migration/V3__upload_session_provider_metadata.sql

  Finally, I extended the MockMvc test to assert that the asset read response now includes the expected sourceObjectKey in src/test/java/org/example/videoclips/api/VideoAssetControllerTest.java.
2026-07-08 23:57:09 +02:00
JSLMPR 7e8a214c3e uploads:complete now goes through the storage port instead of being a pure metadata flip. I added provider upload metadata to UploadSession, wired it through src/main/java/org/example/videoclips/
application/VideoAssetService.java, src/main/java/org/example/videoclips/storage/ObjectStoragePort.java, and the JPA upload-session mapping, and added a migration at src/main/resources/db/
  migration/V3__upload_session_provider_metadata.sql. The storage adapters now expose completeMultipartUpload(...), with the in-memory adapter as a no-op and the S3 adapter implementing the AWS
  multipart completion call.

  I also added an FFmpeg-backed clipper adapter in src/main/java/org/example/videoclips/processing/FfmpegVideoClipperAdapter.java, controlled by video-clipping.processing=ffmpeg. The stub clipper
  remains the default via video-clipping.processing=stub, so the app still has a safe fallback. The new FFmpeg configuration lives under src/main/java/org/example/videoclips/config/
  VideoClippingProperties.java and src/main/resources/application.properties.

  I also extended the test expectations so the upload-session response now verifies providerUploadId in src/test/java/org/example/videoclips/api/VideoAssetControllerTest.java.
2026-07-08 23:54:16 +02:00
JSLMPR 67c7b65639 The storage port now has an opt-in S3-compatible implementation in src/main/java/org/example/videoclips/storage/S3ObjectStorageAdapter.java. It uses the AWS SDK presigner to generate multipart
upload part URLs and signed clip download URLs when video-clipping.storage=s3. The current in-memory adapter remains the default. I also added the supporting configuration fields in src/main/
  java/org/example/videoclips/config/VideoClippingProperties.java and default values in src/main/resources/application.properties.

  The queue port now has a durable database-backed implementation in src/main/java/org/example/videoclips/queue/DatabaseBackedClipJobQueueAdapter.java. It persists queue messages in a new
  queue_messages table from src/main/resources/db/migration/V2__queue_messages.sql, polls them on a schedule, and dispatches them through the existing processor. I updated src/main/java/org/
  example/videoclips/processing/ClipProcessor.java so processing failures propagate back to the queue adapter instead of being swallowed.

  I also enabled scheduling in src/main/java/org/example/videoclips/VideoClippingApplication.java, added the AWS SDK dependency in pom.xml, and updated src/main/resources/application-jpa.properties
  so the JPA profile uses the database queue by default.
2026-07-08 23:47:05 +02:00
JSLMPR 517a4e1146 The service now has an opt-in JPA/Flyway adapter instead of only the in-memory repository. I introduced VideoClippingRepository as the application-facing persistence contract and wired both src/
main/java/org/example/videoclips/infrastructure/InMemoryVideoAssetRepository.java and the new src/main/java/org/example/videoclips/persistence/JpaVideoClippingRepository.java behind it. The JPA
  path includes entity models, Spring Data repositories, a mapper, and a Flyway baseline migration at src/main/resources/db/migration/V1__init_video_clipping.sql. I also added the required
  dependencies in pom.xml.

  The default runtime still uses the in-memory repository via video-clipping.repository=memory, so existing behavior stays intact. There’s now a ready-to-use JPA profile in src/main/resources/
  application-jpa.properties that uses H2 in PostgreSQL mode for local bring-up. That gives the codebase a real persistence lane without forcing the switch yet.
2026-07-08 22:44:18 +02:00
JSLMPR bcf2fa40bb This pass added explicit upload-session state and stricter media validation. GET /v1/video-assets/{assetId}/upload-session is now implemented in src/main/java/org/example/videoclips/api/
VideoAssetController.java, backed by richer UploadSession metadata in src/main/java/org/example/videoclips/domain/UploadSession.java. The service now stores part size, part count, expiry, and
  completion time, and returns them through src/main/java/org/example/videoclips/application/VideoAssetService.java. I also added a configurable media-type allowlist in src/main/java/org/example/
  videoclips/config/VideoClippingProperties.java and reject unsupported uploads with 415 via src/main/java/org/example/videoclips/application/UnsupportedMediaTypeException.java and src/main/java/
  org/example/videoclips/api/ApiExceptionHandler.java.

  I also added adapter-level health visibility for operations. src/main/java/org/example/videoclips/observability/ObjectStorageHealthIndicator.java and src/main/java/org/example/videoclips/
  observability/QueueHealthIndicator.java now expose basic readiness details through Actuator. Tests were extended in src/test/java/org/example/videoclips/api/VideoAssetControllerTest.java to cover
  the upload-session read path and the unsupported-media-type case.
2026-07-08 22:28:20 +02:00
JSLMPR 372d2d6fa6 wip 2026-07-08 22:25:12 +02:00