Add cinematic edit domain model
This commit is contained in:
parent
fdd8309311
commit
0d4752897d
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package org.example.videoclips.editing;
|
||||
|
||||
public record AudioCue(
|
||||
String type,
|
||||
String assetKey,
|
||||
double timelineStartSeconds,
|
||||
double timelineEndSeconds,
|
||||
double gainDb,
|
||||
String notes
|
||||
) {
|
||||
}
|
||||
|
|
@ -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
|
||||
) {
|
||||
}
|
||||
|
|
@ -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
|
||||
) {
|
||||
}
|
||||
|
|
@ -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
|
||||
) {
|
||||
}
|
||||
|
|
@ -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
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package org.example.videoclips.editing;
|
||||
|
||||
public enum EditProjectStatus {
|
||||
CREATED,
|
||||
ANALYZING,
|
||||
ANALYZED,
|
||||
WAITING_FOR_DIRECTOR,
|
||||
PLANNING,
|
||||
PLANNED,
|
||||
RENDERING,
|
||||
RENDERED,
|
||||
FAILED
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package org.example.videoclips.editing;
|
||||
|
||||
public record VoiceoverLine(
|
||||
String text,
|
||||
double timelineStartSeconds,
|
||||
double timelineEndSeconds,
|
||||
String delivery
|
||||
) {
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue