forked from jsl/video_editing_poc
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.
This commit is contained in:
parent
7e8a214c3e
commit
9df97362ba
|
|
@ -60,6 +60,7 @@ public class VideoAssetService {
|
|||
|
||||
String assetId = nextId("vid");
|
||||
String uploadId = nextId("upl");
|
||||
String sourceObjectKey = "uploads/" + uploadId + "/source";
|
||||
Instant now = Instant.now();
|
||||
|
||||
VideoAsset asset = new VideoAsset(
|
||||
|
|
@ -69,6 +70,7 @@ public class VideoAssetService {
|
|||
request.contentLengthBytes(),
|
||||
request.checksumSha256(),
|
||||
request.clipProfile() == null || request.clipProfile().isBlank() ? "mp4-h264-aac" : request.clipProfile(),
|
||||
sourceObjectKey,
|
||||
VideoAsset.Status.PENDING_UPLOAD,
|
||||
uploadId,
|
||||
null,
|
||||
|
|
@ -402,6 +404,7 @@ public class VideoAssetService {
|
|||
response.put("contentType", asset.contentType());
|
||||
response.put("contentLengthBytes", asset.contentLengthBytes());
|
||||
response.put("clipProfile", asset.clipProfile());
|
||||
response.put("sourceObjectKey", asset.sourceObjectKey());
|
||||
response.put("status", asset.status().name());
|
||||
response.put("uploadId", asset.uploadId());
|
||||
response.put("sourceDurationSeconds", asset.sourceDurationSeconds());
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public record VideoAsset(
|
|||
long contentLengthBytes,
|
||||
String checksumSha256,
|
||||
String clipProfile,
|
||||
String sourceObjectKey,
|
||||
Status status,
|
||||
String uploadId,
|
||||
Long sourceDurationSeconds,
|
||||
|
|
@ -24,12 +25,12 @@ public record VideoAsset(
|
|||
}
|
||||
|
||||
public VideoAsset withUploadCompleted(long durationSeconds, Instant uploadedAt) {
|
||||
return new VideoAsset(id, fileName, contentType, contentLengthBytes, checksumSha256, clipProfile,
|
||||
return new VideoAsset(id, fileName, contentType, contentLengthBytes, checksumSha256, clipProfile, sourceObjectKey,
|
||||
Status.UPLOADED, uploadId, durationSeconds, createdAt, uploadedAt);
|
||||
}
|
||||
|
||||
public VideoAsset withStatus(Status status) {
|
||||
return new VideoAsset(id, fileName, contentType, contentLengthBytes, checksumSha256, clipProfile,
|
||||
return new VideoAsset(id, fileName, contentType, contentLengthBytes, checksumSha256, clipProfile, sourceObjectKey,
|
||||
status, uploadId, sourceDurationSeconds, createdAt, uploadedAt);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ public class JpaVideoClippingMapper {
|
|||
entity.setContentLengthBytes(asset.contentLengthBytes());
|
||||
entity.setChecksumSha256(asset.checksumSha256());
|
||||
entity.setClipProfile(asset.clipProfile());
|
||||
entity.setSourceObjectKey(asset.sourceObjectKey());
|
||||
entity.setStatus(asset.status().name());
|
||||
entity.setUploadId(asset.uploadId());
|
||||
entity.setSourceDurationSeconds(asset.sourceDurationSeconds());
|
||||
|
|
@ -41,6 +42,7 @@ public class JpaVideoClippingMapper {
|
|||
entity.getContentLengthBytes(),
|
||||
entity.getChecksumSha256(),
|
||||
entity.getClipProfile(),
|
||||
entity.getSourceObjectKey(),
|
||||
VideoAsset.Status.valueOf(entity.getStatus()),
|
||||
entity.getUploadId(),
|
||||
entity.getSourceDurationSeconds(),
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ public class VideoAssetEntity {
|
|||
@Column(nullable = false, length = 100)
|
||||
private String clipProfile;
|
||||
|
||||
@Column(nullable = false, length = 255)
|
||||
private String sourceObjectKey;
|
||||
|
||||
@Column(nullable = false, length = 32)
|
||||
private String status;
|
||||
|
||||
|
|
@ -90,6 +93,14 @@ public class VideoAssetEntity {
|
|||
this.clipProfile = clipProfile;
|
||||
}
|
||||
|
||||
public String getSourceObjectKey() {
|
||||
return sourceObjectKey;
|
||||
}
|
||||
|
||||
public void setSourceObjectKey(String sourceObjectKey) {
|
||||
this.sourceObjectKey = sourceObjectKey;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ public class ClipProcessor {
|
|||
List<Clip> clips = videoClipperPort.generateClips(
|
||||
jobId,
|
||||
asset.id(),
|
||||
asset.sourceObjectKey(),
|
||||
totalDuration,
|
||||
segmentDuration,
|
||||
job.accuracyMode()
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@ public class FfmpegVideoClipperAdapter implements VideoClipperPort {
|
|||
public List<Clip> generateClips(
|
||||
String jobId,
|
||||
String assetId,
|
||||
String sourceObjectKey,
|
||||
long sourceDurationSeconds,
|
||||
int segmentDurationSeconds,
|
||||
AccuracyMode accuracyMode
|
||||
) {
|
||||
Path inputFile = Path.of(properties.getFfmpeg().getInputDirectory(), assetId + ".mp4");
|
||||
Path inputFile = resolveInputFile(sourceObjectKey);
|
||||
Path outputDir = Path.of(properties.getFfmpeg().getOutputDirectory(), jobId);
|
||||
if (!Files.exists(inputFile)) {
|
||||
throw new IllegalStateException("FFmpeg input file not found: " + inputFile);
|
||||
|
|
@ -61,6 +62,11 @@ public class FfmpegVideoClipperAdapter implements VideoClipperPort {
|
|||
return buildClipManifest(jobId, assetId, sourceDurationSeconds, segmentDurationSeconds, accuracyMode);
|
||||
}
|
||||
|
||||
private Path resolveInputFile(String sourceObjectKey) {
|
||||
String normalized = sourceObjectKey.startsWith("/") ? sourceObjectKey.substring(1) : sourceObjectKey;
|
||||
return Path.of(properties.getFfmpeg().getInputDirectory()).resolve(normalized);
|
||||
}
|
||||
|
||||
private List<String> buildCommand(Path inputFile, Path outputDir, int segmentDurationSeconds, AccuracyMode accuracyMode) {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(properties.getFfmpeg().getFfmpegBinary());
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class StubVideoClipperAdapter implements VideoClipperPort {
|
|||
public List<Clip> generateClips(
|
||||
String jobId,
|
||||
String assetId,
|
||||
String sourceObjectKey,
|
||||
long sourceDurationSeconds,
|
||||
int segmentDurationSeconds,
|
||||
AccuracyMode accuracyMode
|
||||
|
|
|
|||
|
|
@ -7,5 +7,12 @@ import java.util.List;
|
|||
|
||||
public interface VideoClipperPort {
|
||||
|
||||
List<Clip> generateClips(String jobId, String assetId, long sourceDurationSeconds, int segmentDurationSeconds, AccuracyMode accuracyMode);
|
||||
List<Clip> generateClips(
|
||||
String jobId,
|
||||
String assetId,
|
||||
String sourceObjectKey,
|
||||
long sourceDurationSeconds,
|
||||
int segmentDurationSeconds,
|
||||
AccuracyMode accuracyMode
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ create table if not exists video_assets (
|
|||
content_length_bytes bigint not null,
|
||||
checksum_sha256 varchar(64),
|
||||
clip_profile varchar(100) not null,
|
||||
source_object_key varchar(255) not null,
|
||||
status varchar(32) not null,
|
||||
upload_id varchar(64) not null,
|
||||
source_duration_seconds bigint,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
alter table video_assets
|
||||
add column if not exists source_object_key varchar(255);
|
||||
|
||||
update video_assets
|
||||
set source_object_key = 'uploads/' || upload_id || '/source'
|
||||
where source_object_key is null;
|
||||
|
||||
alter table video_assets
|
||||
alter column source_object_key set not null;
|
||||
|
|
@ -65,6 +65,7 @@ class VideoAssetControllerTest {
|
|||
mockMvc.perform(get("/v1/video-assets/{assetId}", assetId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.assetId").value(assetId))
|
||||
.andExpect(jsonPath("$.sourceObjectKey").value("uploads/" + uploadId + "/source"))
|
||||
.andExpect(jsonPath("$.status").value("UPLOADED"));
|
||||
|
||||
mockMvc.perform(get("/v1/video-assets/{assetId}/upload-session", assetId))
|
||||
|
|
|
|||
Loading…
Reference in New Issue