The storage port now has a real staging operation:
- materializeSourceObject(String sourceObjectKey, Path targetPath) in src/main/java/org/example/videoclips/storage/ObjectStoragePort.java Both storage adapters implement it: - src/main/java/org/example/videoclips/storage/InMemoryObjectStorageAdapter.java creates a placeholder local file for the default stub flow - src/main/java/org/example/videoclips/storage/S3ObjectStorageAdapter.java downloads the object from S3-compatible storage to the worker filesystem The processor now uses that seam before clip generation starts. In src/main/java/org/example/videoclips/processing/ClipProcessor.java it resolves the local input path under the configured FFmpeg input directory, materializes the source object there, and records a SOURCE_MATERIALIZED job event before invoking the clipper. That means the FFmpeg path is no longer relying on a file appearing magically in the input directory. The next concrete step is to finish the output side the same way: persist generated clip object keys and push FFmpeg outputs back through the storage port instead of only creating clip metadata in memory.
This commit is contained in:
parent
9df97362ba
commit
bd620fc773
|
|
@ -1,14 +1,17 @@
|
|||
package org.example.videoclips.processing;
|
||||
|
||||
import org.example.videoclips.config.VideoClippingProperties;
|
||||
import org.example.videoclips.application.VideoClippingRepository;
|
||||
import org.example.videoclips.domain.Clip;
|
||||
import org.example.videoclips.domain.ClipJob;
|
||||
import org.example.videoclips.domain.ClipJobStatus;
|
||||
import org.example.videoclips.domain.VideoAsset;
|
||||
import org.example.videoclips.domain.JobEvent;
|
||||
import org.example.videoclips.storage.ObjectStoragePort;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -17,10 +20,19 @@ public class ClipProcessor {
|
|||
|
||||
private final VideoClippingRepository repository;
|
||||
private final VideoClipperPort videoClipperPort;
|
||||
private final ObjectStoragePort objectStoragePort;
|
||||
private final VideoClippingProperties properties;
|
||||
|
||||
public ClipProcessor(VideoClippingRepository repository, VideoClipperPort videoClipperPort) {
|
||||
public ClipProcessor(
|
||||
VideoClippingRepository repository,
|
||||
VideoClipperPort videoClipperPort,
|
||||
ObjectStoragePort objectStoragePort,
|
||||
VideoClippingProperties properties
|
||||
) {
|
||||
this.repository = repository;
|
||||
this.videoClipperPort = videoClipperPort;
|
||||
this.objectStoragePort = objectStoragePort;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Async
|
||||
|
|
@ -43,6 +55,9 @@ public class ClipProcessor {
|
|||
recordEvent(jobId, "CANCELLED", "Job was cancelled before clip generation");
|
||||
return;
|
||||
}
|
||||
Path localInputPath = Path.of(properties.getFfmpeg().getInputDirectory()).resolve(asset.sourceObjectKey());
|
||||
objectStoragePort.materializeSourceObject(asset.sourceObjectKey(), localInputPath);
|
||||
recordEvent(jobId, "SOURCE_MATERIALIZED", "Source object staged to " + localInputPath);
|
||||
List<Clip> clips = videoClipperPort.generateClips(
|
||||
jobId,
|
||||
asset.id(),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package org.example.videoclips.storage;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -26,6 +29,18 @@ public class InMemoryObjectStorageAdapter implements ObjectStoragePort {
|
|||
// No-op for the in-memory adapter.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void materializeSourceObject(String sourceObjectKey, Path targetPath) {
|
||||
try {
|
||||
Files.createDirectories(targetPath.getParent());
|
||||
if (!Files.exists(targetPath)) {
|
||||
Files.writeString(targetPath, "stub-video-content");
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("Unable to materialize in-memory source object", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createClipDownloadUrl(String clipId, Instant expiresAt) {
|
||||
return "https://storage.example/clips/" + clipId + "?expiresAt=" + expiresAt.toString() + "&signature=demo";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package org.example.videoclips.storage;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -9,6 +10,8 @@ public interface ObjectStoragePort {
|
|||
|
||||
void completeMultipartUpload(String providerUploadId, String uploadId, List<CompletedUploadPart> parts);
|
||||
|
||||
void materializeSourceObject(String sourceObjectKey, Path targetPath);
|
||||
|
||||
String createClipDownloadUrl(String clipId, Instant expiresAt);
|
||||
|
||||
record UploadPartDescriptor(int partNumber, String url) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import org.example.videoclips.config.VideoClippingProperties;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
|
||||
import software.amazon.awssdk.core.sync.ResponseTransformer;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.s3.S3Configuration;
|
||||
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest;
|
||||
|
|
@ -18,6 +19,8 @@ import software.amazon.awssdk.services.s3.presigner.model.PresignedUploadPartReq
|
|||
import software.amazon.awssdk.services.s3.presigner.model.UploadPartPresignRequest;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
|
@ -87,6 +90,20 @@ public class S3ObjectStorageAdapter implements ObjectStoragePort {
|
|||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void materializeSourceObject(String sourceObjectKey, Path targetPath) {
|
||||
try {
|
||||
Files.createDirectories(targetPath.getParent());
|
||||
} catch (java.io.IOException ex) {
|
||||
throw new IllegalStateException("Unable to create local source-object directory", ex);
|
||||
}
|
||||
s3Client.getObject(GetObjectRequest.builder()
|
||||
.bucket(properties.getS3().getBucket())
|
||||
.key(sourceObjectKey)
|
||||
.build(),
|
||||
ResponseTransformer.toFile(targetPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createClipDownloadUrl(String clipId, Instant expiresAt) {
|
||||
String objectKey = "clips/" + clipId + ".mp4";
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
stub-video-content
|
||||
Loading…
Reference in New Issue