Add cinematic edit domain model

This commit is contained in:
JSLMPR 2026-07-10 14:48:42 +02:00
parent fdd8309311
commit 0d4752897d
9 changed files with 193 additions and 1 deletions

View File

@ -783,7 +783,7 @@ It writes the script only and requires an imported audio file.
1. [x] Add editing configuration under `video-clipping.editing`.
2. [x] Add filesystem project store for `project.json`, `analysis.json`, `edit-plan.json`, and render output.
3. [ ] Add edit project domain records and status enum.
3. [x] Add edit project domain records and status enum.
4. [ ] Add `POST /v1/edit-projects` and `GET /v1/edit-projects/{projectId}`.
5. [ ] Add clip discovery for project input directories.
6. [ ] Add `ffprobe` analysis and validation for all input clips.

View File

@ -0,0 +1,11 @@
package org.example.videoclips.editing;
public record AudioCue(
String type,
String assetKey,
double timelineStartSeconds,
double timelineEndSeconds,
double gainDb,
String notes
) {
}

View File

@ -0,0 +1,21 @@
package org.example.videoclips.editing;
import java.util.List;
public record ClipAnalysis(
String clipId,
String sourcePath,
double durationSeconds,
String videoCodec,
String audioCodec,
int width,
int height,
double frameRate,
List<String> thumbnails,
String contactSheet,
String proxyPath,
double motionScore,
double brightnessScore,
double sharpnessScore
) {
}

View File

@ -0,0 +1,15 @@
package org.example.videoclips.editing;
public record EditDecision(
String clipId,
double sourceStartSeconds,
double sourceEndSeconds,
double timelineStartSeconds,
double timelineEndSeconds,
String transitionIn,
String transitionOut,
double playbackSpeed,
String visualTreatment,
String reason
) {
}

View File

@ -0,0 +1,15 @@
package org.example.videoclips.editing;
import java.util.List;
public record EditPlan(
String projectId,
String style,
double targetDurationSeconds,
List<EditDecision> decisions,
List<AudioCue> audioCues,
List<VoiceoverLine> voiceover,
String renderProfile,
String summary
) {
}

View File

@ -0,0 +1,15 @@
package org.example.videoclips.editing;
import java.time.Instant;
public record EditProject(
String id,
String name,
EditProjectStatus status,
String inputDirectory,
String outputDirectory,
Instant createdAt,
Instant updatedAt,
String failureReason
) {
}

View File

@ -0,0 +1,13 @@
package org.example.videoclips.editing;
public enum EditProjectStatus {
CREATED,
ANALYZING,
ANALYZED,
WAITING_FOR_DIRECTOR,
PLANNING,
PLANNED,
RENDERING,
RENDERED,
FAILED
}

View File

@ -0,0 +1,9 @@
package org.example.videoclips.editing;
public record VoiceoverLine(
String text,
double timelineStartSeconds,
double timelineEndSeconds,
String delivery
) {
}

View File

@ -0,0 +1,93 @@
package org.example.videoclips.editing;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class EditDomainJsonTest {
private final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
@Test
void serializesAndDeserializesEditProject() throws Exception {
EditProject project = new EditProject(
"porsche-session-001",
"Porsche Session",
EditProjectStatus.WAITING_FOR_DIRECTOR,
"./input/editing/processed/porsche-session-001",
"./output/edit-projects/porsche-session-001",
Instant.parse("2026-07-10T10:00:00Z"),
Instant.parse("2026-07-10T10:01:00Z"),
null
);
String json = objectMapper.writeValueAsString(project);
assertThat(json).contains("\"inputDirectory\"");
assertThat(json).contains("\"WAITING_FOR_DIRECTOR\"");
assertThat(objectMapper.readValue(json, EditProject.class)).isEqualTo(project);
}
@Test
void serializesAndDeserializesClipAnalysis() throws Exception {
ClipAnalysis analysis = new ClipAnalysis(
"clip_00001",
"./clips/clip_00001.mp4",
8.0,
"h264",
"aac",
1920,
1080,
30.0,
List.of("./thumbnails/clip_00001_0001.jpg"),
"./contact-sheets/clip_00001.jpg",
"./proxies/clip_00001.mp4",
0.8,
0.6,
0.9
);
String json = objectMapper.writeValueAsString(analysis);
assertThat(json).contains("\"clipId\"");
assertThat(json).contains("\"durationSeconds\"");
assertThat(objectMapper.readValue(json, ClipAnalysis.class)).isEqualTo(analysis);
}
@Test
void serializesAndDeserializesEditPlan() throws Exception {
EditPlan plan = new EditPlan(
"porsche-session-001",
"cinematic-porsche-promo",
60.0,
List.of(new EditDecision(
"clip_00001",
0.0,
2.5,
0.0,
2.5,
"cut",
"crossfade",
1.0,
"high contrast cinematic grade",
"hero front angle"
)),
List.of(new AudioCue("music", "cinematic-driving-bed", 0.0, 60.0, -12.0, "build energy")),
List.of(new VoiceoverLine("This Porsche turns precision into emotion.", 3.0, 6.0,
"confident cinematic narrator")),
"mp4-h264-aac-1080p",
"Cinematic Porsche promo edit."
);
String json = objectMapper.writeValueAsString(plan);
assertThat(json).contains("\"targetDurationSeconds\"");
assertThat(json).contains("\"visualTreatment\"");
assertThat(json).contains("\"voiceover\"");
assertThat(objectMapper.readValue(json, EditPlan.class)).isEqualTo(plan);
}
}