From ab3cf591c66697feec93d9d9c1ead2c75bdbfa97 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Wed, 5 Apr 2023 08:20:41 +0200 Subject: [PATCH] Clean up VideoProcessingService --- .../nl/andrewlalis/gymboardcdn/Config.java | 8 ++- .../gymboardcdn/files/FileStorageService.java | 13 +++- .../uploads/model/VideoProcessingTask.java | 15 +---- .../uploads/service/UploadService.java | 3 +- .../service/VideoProcessingService.java | 64 ++++++++++++------- 5 files changed, 61 insertions(+), 42 deletions(-) diff --git a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/Config.java b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/Config.java index defdbc3..1258112 100644 --- a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/Config.java +++ b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/Config.java @@ -1,5 +1,6 @@ package nl.andrewlalis.gymboardcdn; +import com.fasterxml.jackson.databind.ObjectMapper; import nl.andrewlalis.gymboardcdn.files.FileStorageService; import nl.andrewlalis.gymboardcdn.files.util.ULID; import nl.andrewlalis.gymboardcdn.uploads.service.process.FfmpegThumbnailGenerator; @@ -44,9 +45,14 @@ public class Config { return new ULID(); } + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + @Bean public FileStorageService fileStorageService() { - return new FileStorageService(ulid(), "cdn-files"); + return new FileStorageService(ulid(), objectMapper(), "cdn-files"); } @Bean diff --git a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/files/FileStorageService.java b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/files/FileStorageService.java index 1fac261..a1a4d46 100644 --- a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/files/FileStorageService.java +++ b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/files/FileStorageService.java @@ -36,11 +36,12 @@ public class FileStorageService { private static final int HEADER_SIZE = 1024; private final ULID ulid; - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper; private final String baseStorageDir; - public FileStorageService(ULID ulid, String baseStorageDir) { + public FileStorageService(ULID ulid, ObjectMapper objectMapper, String baseStorageDir) { this.ulid = ulid; + this.objectMapper = objectMapper; this.baseStorageDir = baseStorageDir; } @@ -48,6 +49,12 @@ public class FileStorageService { return ulid.nextULID(); } + public String save(Path inputFile, FileMetadata metadata) throws IOException { + try (var in = Files.newInputStream(inputFile)) { + return save(in, metadata, -1); + } + } + /** * Saves a new file to the storage. * @param in The input stream to the file contents. @@ -182,7 +189,7 @@ public class FileStorageService { private FileMetadata readMetadata(InputStream in) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE); int readCount = in.read(buffer.array(), 0, HEADER_SIZE); - if (readCount != HEADER_SIZE) throw new IOException("Invalid header."); + if (readCount != HEADER_SIZE) throw new IOException("Invalid header. Read " + readCount + " bytes instead of " + HEADER_SIZE); short metadataBytesLength = buffer.getShort(); byte[] metadataBytes = new byte[metadataBytesLength]; buffer.get(metadataBytes); diff --git a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/model/VideoProcessingTask.java b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/model/VideoProcessingTask.java index e4dba71..c0ca5d4 100644 --- a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/model/VideoProcessingTask.java +++ b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/model/VideoProcessingTask.java @@ -45,20 +45,11 @@ public class VideoProcessingTask { @Column(nullable = false, updatable = false, length = 26) private String uploadFileId; - /** - * The file id for the final processed video file. This doesn't exist yet, - * but we generate the video id right away, just in case there's a need to - * preemptively link to it. - */ - @Column(nullable = false, updatable = false, length = 26) - private String videoFileId; - public VideoProcessingTask() {} - public VideoProcessingTask(Status status, String uploadFileId, String videoFileId) { + public VideoProcessingTask(Status status, String uploadFileId) { this.status = status; this.uploadFileId = uploadFileId; - this.videoFileId = videoFileId; } public Long getId() { @@ -80,8 +71,4 @@ public class VideoProcessingTask { public String getUploadFileId() { return uploadFileId; } - - public String getVideoFileId() { - return videoFileId; - } } diff --git a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/service/UploadService.java b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/service/UploadService.java index 312ba41..324fe43 100644 --- a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/service/UploadService.java +++ b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/service/UploadService.java @@ -64,8 +64,7 @@ public class UploadService { } var task = videoTaskRepository.save(new VideoProcessingTask( VideoProcessingTask.Status.NOT_STARTED, - uploadFileId, - fileStorageService.generateFileId() + uploadFileId )); return new VideoUploadResponse(task.getId()); } diff --git a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/service/VideoProcessingService.java b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/service/VideoProcessingService.java index 4a2b700..d2961e8 100644 --- a/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/service/VideoProcessingService.java +++ b/gymboard-cdn/src/main/java/nl/andrewlalis/gymboardcdn/uploads/service/VideoProcessingService.java @@ -1,8 +1,9 @@ package nl.andrewlalis.gymboardcdn.uploads.service; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import nl.andrewlalis.gymboardcdn.files.FileMetadata; import nl.andrewlalis.gymboardcdn.files.FileStorageService; -import nl.andrewlalis.gymboardcdn.files.util.ULID; import nl.andrewlalis.gymboardcdn.uploads.model.VideoProcessingTask; import nl.andrewlalis.gymboardcdn.uploads.model.VideoProcessingTaskRepository; import nl.andrewlalis.gymboardcdn.uploads.service.process.ThumbnailGenerator; @@ -13,6 +14,10 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; @@ -29,17 +34,24 @@ public class VideoProcessingService { private final FileStorageService fileStorageService; private final VideoProcessor videoProcessor; private final ThumbnailGenerator thumbnailGenerator; + private final ObjectMapper objectMapper; public VideoProcessingService(Executor videoProcessingExecutor, VideoProcessingTaskRepository taskRepo, FileStorageService fileStorageService, VideoProcessor videoProcessor, - ThumbnailGenerator thumbnailGenerator) { + ThumbnailGenerator thumbnailGenerator, ObjectMapper objectMapper) { this.videoProcessingExecutor = videoProcessingExecutor; this.taskRepo = taskRepo; this.fileStorageService = fileStorageService; this.videoProcessor = videoProcessor; this.thumbnailGenerator = thumbnailGenerator; + this.objectMapper = objectMapper; + } + + private void updateTask(VideoProcessingTask task, VideoProcessingTask.Status status) { + task.setStatus(status); + taskRepo.saveAndFlush(task); } @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) @@ -83,6 +95,7 @@ public class VideoProcessingService { return; } + // Run the actual processing here. Path videoFile = uploadFile.resolveSibling(task.getUploadFileId() + "-vid-out"); Path thumbnailFile = uploadFile.resolveSibling(task.getUploadFileId() + "-thm-out"); try { @@ -107,29 +120,20 @@ public class VideoProcessingService { } // And finally, copy the output to the final location. - try ( - var videoIn = Files.newInputStream(videoFile); - var thumbnailIn = Files.newInputStream(thumbnailFile) - ) { + try { // Save the video to a final file location. var originalMetadata = fileStorageService.getMetadata(task.getUploadFileId()); - FileMetadata metadata = new FileMetadata( - originalMetadata.filename(), - originalMetadata.mimeType(), - true - ); - fileStorageService.save(ULID.parseULID(task.getVideoFileId()), videoIn, metadata, Files.size(videoFile)); + FileMetadata metadata = new FileMetadata(originalMetadata.filename(), originalMetadata.mimeType(), true); + String videoFileId = fileStorageService.save(videoFile, metadata); + // Save the thumbnail too. - FileMetadata thumbnailMetadata = new FileMetadata( - "thumbnail.jpeg", - "image/jpeg", - true - ); - fileStorageService.save(thumbnailIn, thumbnailMetadata, Files.size(thumbnailFile)); + FileMetadata thumbnailMetadata = new FileMetadata("thumbnail.jpeg", "image/jpeg", true); + String thumbnailFileId = fileStorageService.save(thumbnailFile, thumbnailMetadata); updateTask(task, VideoProcessingTask.Status.COMPLETED); log.info("Finished processing task {}.", task.getId()); - // TODO: Send HTTP POST to API, with video id and thumbnail id. + // Send HTTP POST to API, with video id and thumbnail id. + sendProcessedDataToApi(videoFileId, thumbnailFileId); } catch (IOException e) { log.error("Failed to copy processed video to final storage location.", e); updateTask(task, VideoProcessingTask.Status.FAILED); @@ -145,8 +149,24 @@ public class VideoProcessingService { } } - private void updateTask(VideoProcessingTask task, VideoProcessingTask.Status status) { - task.setStatus(status); - taskRepo.saveAndFlush(task); + private void sendProcessedDataToApi(String videoId, String thumbnailId) throws IOException { + ObjectNode obj = objectMapper.createObjectNode(); + obj.put("videoFileId", videoId); + obj.put("thumbnailFileId", thumbnailId); + String json = objectMapper.writeValueAsString(obj); + HttpClient httpClient = HttpClient.newBuilder().build(); + HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost:8080/submissions/123/processed-data")) + .header("Authorization", "Bearer bullshit") + .POST(HttpRequest.BodyPublishers.ofString(json)) + .build(); + try { + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.discarding()); + if (response.statusCode() == 200) { + // We can now delete the task. + } + } catch (InterruptedException e) { + // TODO: Retry! + throw new RuntimeException(e); + } } }