diff --git a/src/main/java/org/example/videoclips/processing/ClipProcessor.java b/src/main/java/org/example/videoclips/processing/ClipProcessor.java index 4ae7b96..3e4fb63 100644 --- a/src/main/java/org/example/videoclips/processing/ClipProcessor.java +++ b/src/main/java/org/example/videoclips/processing/ClipProcessor.java @@ -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 clips = videoClipperPort.generateClips( jobId, asset.id(), diff --git a/src/main/java/org/example/videoclips/storage/InMemoryObjectStorageAdapter.java b/src/main/java/org/example/videoclips/storage/InMemoryObjectStorageAdapter.java index 08195ba..666b2fd 100644 --- a/src/main/java/org/example/videoclips/storage/InMemoryObjectStorageAdapter.java +++ b/src/main/java/org/example/videoclips/storage/InMemoryObjectStorageAdapter.java @@ -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"; diff --git a/src/main/java/org/example/videoclips/storage/ObjectStoragePort.java b/src/main/java/org/example/videoclips/storage/ObjectStoragePort.java index d91cb91..6a69a21 100644 --- a/src/main/java/org/example/videoclips/storage/ObjectStoragePort.java +++ b/src/main/java/org/example/videoclips/storage/ObjectStoragePort.java @@ -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 parts); + void materializeSourceObject(String sourceObjectKey, Path targetPath); + String createClipDownloadUrl(String clipId, Instant expiresAt); record UploadPartDescriptor(int partNumber, String url) { diff --git a/src/main/java/org/example/videoclips/storage/S3ObjectStorageAdapter.java b/src/main/java/org/example/videoclips/storage/S3ObjectStorageAdapter.java index 400efcd..2a7d6af 100644 --- a/src/main/java/org/example/videoclips/storage/S3ObjectStorageAdapter.java +++ b/src/main/java/org/example/videoclips/storage/S3ObjectStorageAdapter.java @@ -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"; diff --git a/tmp/ffmpeg-input/uploads/upl_367df9ed2414/source b/tmp/ffmpeg-input/uploads/upl_367df9ed2414/source new file mode 100644 index 0000000..cb3d12a --- /dev/null +++ b/tmp/ffmpeg-input/uploads/upl_367df9ed2414/source @@ -0,0 +1 @@ +stub-video-content \ No newline at end of file