forked from jsl/video_editing_poc
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`.
|
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.
|
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}`.
|
4. [ ] Add `POST /v1/edit-projects` and `GET /v1/edit-projects/{projectId}`.
|
||||||
5. [ ] Add clip discovery for project input directories.
|
5. [ ] Add clip discovery for project input directories.
|
||||||
6. [ ] Add `ffprobe` analysis and validation for all input clips.
|
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