Clean up VideoProcessingService
This commit is contained in:
		
							parent
							
								
									63550c880d
								
							
						
					
					
						commit
						ab3cf591c6
					
				| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
package nl.andrewlalis.gymboardcdn;
 | 
					package nl.andrewlalis.gymboardcdn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.ObjectMapper;
 | 
				
			||||||
import nl.andrewlalis.gymboardcdn.files.FileStorageService;
 | 
					import nl.andrewlalis.gymboardcdn.files.FileStorageService;
 | 
				
			||||||
import nl.andrewlalis.gymboardcdn.files.util.ULID;
 | 
					import nl.andrewlalis.gymboardcdn.files.util.ULID;
 | 
				
			||||||
import nl.andrewlalis.gymboardcdn.uploads.service.process.FfmpegThumbnailGenerator;
 | 
					import nl.andrewlalis.gymboardcdn.uploads.service.process.FfmpegThumbnailGenerator;
 | 
				
			||||||
| 
						 | 
					@ -44,9 +45,14 @@ public class Config {
 | 
				
			||||||
		return new ULID();
 | 
							return new ULID();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Bean
 | 
				
			||||||
 | 
						public ObjectMapper objectMapper() {
 | 
				
			||||||
 | 
							return new ObjectMapper();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Bean
 | 
						@Bean
 | 
				
			||||||
	public FileStorageService fileStorageService() {
 | 
						public FileStorageService fileStorageService() {
 | 
				
			||||||
		return new FileStorageService(ulid(), "cdn-files");
 | 
							return new FileStorageService(ulid(), objectMapper(), "cdn-files");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Bean
 | 
						@Bean
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,11 +36,12 @@ public class FileStorageService {
 | 
				
			||||||
	private static final int HEADER_SIZE = 1024;
 | 
						private static final int HEADER_SIZE = 1024;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final ULID ulid;
 | 
						private final ULID ulid;
 | 
				
			||||||
	private final ObjectMapper objectMapper = new ObjectMapper();
 | 
						private final ObjectMapper objectMapper;
 | 
				
			||||||
	private final String baseStorageDir;
 | 
						private final String baseStorageDir;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public FileStorageService(ULID ulid, String baseStorageDir) {
 | 
						public FileStorageService(ULID ulid, ObjectMapper objectMapper, String baseStorageDir) {
 | 
				
			||||||
		this.ulid = ulid;
 | 
							this.ulid = ulid;
 | 
				
			||||||
 | 
							this.objectMapper = objectMapper;
 | 
				
			||||||
		this.baseStorageDir = baseStorageDir;
 | 
							this.baseStorageDir = baseStorageDir;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,6 +49,12 @@ public class FileStorageService {
 | 
				
			||||||
		return ulid.nextULID();
 | 
							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.
 | 
						 * Saves a new file to the storage.
 | 
				
			||||||
	 * @param in The input stream to the file contents.
 | 
						 * @param in The input stream to the file contents.
 | 
				
			||||||
| 
						 | 
					@ -182,7 +189,7 @@ public class FileStorageService {
 | 
				
			||||||
	private FileMetadata readMetadata(InputStream in) throws IOException {
 | 
						private FileMetadata readMetadata(InputStream in) throws IOException {
 | 
				
			||||||
		ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE);
 | 
							ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE);
 | 
				
			||||||
		int readCount = in.read(buffer.array(), 0, 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();
 | 
							short metadataBytesLength = buffer.getShort();
 | 
				
			||||||
		byte[] metadataBytes = new byte[metadataBytesLength];
 | 
							byte[] metadataBytes = new byte[metadataBytesLength];
 | 
				
			||||||
		buffer.get(metadataBytes);
 | 
							buffer.get(metadataBytes);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,20 +45,11 @@ public class VideoProcessingTask {
 | 
				
			||||||
	@Column(nullable = false, updatable = false, length = 26)
 | 
						@Column(nullable = false, updatable = false, length = 26)
 | 
				
			||||||
	private String uploadFileId;
 | 
						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() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public VideoProcessingTask(Status status, String uploadFileId, String videoFileId) {
 | 
						public VideoProcessingTask(Status status, String uploadFileId) {
 | 
				
			||||||
		this.status = status;
 | 
							this.status = status;
 | 
				
			||||||
		this.uploadFileId = uploadFileId;
 | 
							this.uploadFileId = uploadFileId;
 | 
				
			||||||
		this.videoFileId = videoFileId;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Long getId() {
 | 
						public Long getId() {
 | 
				
			||||||
| 
						 | 
					@ -80,8 +71,4 @@ public class VideoProcessingTask {
 | 
				
			||||||
	public String getUploadFileId() {
 | 
						public String getUploadFileId() {
 | 
				
			||||||
		return uploadFileId;
 | 
							return uploadFileId;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	public String getVideoFileId() {
 | 
					 | 
				
			||||||
		return videoFileId;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,8 +64,7 @@ public class UploadService {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		var task = videoTaskRepository.save(new VideoProcessingTask(
 | 
							var task = videoTaskRepository.save(new VideoProcessingTask(
 | 
				
			||||||
				VideoProcessingTask.Status.NOT_STARTED,
 | 
									VideoProcessingTask.Status.NOT_STARTED,
 | 
				
			||||||
				uploadFileId,
 | 
									uploadFileId
 | 
				
			||||||
				fileStorageService.generateFileId()
 | 
					 | 
				
			||||||
		));
 | 
							));
 | 
				
			||||||
		return new VideoUploadResponse(task.getId());
 | 
							return new VideoUploadResponse(task.getId());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,9 @@
 | 
				
			||||||
package nl.andrewlalis.gymboardcdn.uploads.service;
 | 
					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.FileMetadata;
 | 
				
			||||||
import nl.andrewlalis.gymboardcdn.files.FileStorageService;
 | 
					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.VideoProcessingTask;
 | 
				
			||||||
import nl.andrewlalis.gymboardcdn.uploads.model.VideoProcessingTaskRepository;
 | 
					import nl.andrewlalis.gymboardcdn.uploads.model.VideoProcessingTaskRepository;
 | 
				
			||||||
import nl.andrewlalis.gymboardcdn.uploads.service.process.ThumbnailGenerator;
 | 
					import nl.andrewlalis.gymboardcdn.uploads.service.process.ThumbnailGenerator;
 | 
				
			||||||
| 
						 | 
					@ -13,6 +14,10 @@ import org.springframework.scheduling.annotation.Scheduled;
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					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.Files;
 | 
				
			||||||
import java.nio.file.Path;
 | 
					import java.nio.file.Path;
 | 
				
			||||||
import java.time.LocalDateTime;
 | 
					import java.time.LocalDateTime;
 | 
				
			||||||
| 
						 | 
					@ -29,17 +34,24 @@ public class VideoProcessingService {
 | 
				
			||||||
	private final FileStorageService fileStorageService;
 | 
						private final FileStorageService fileStorageService;
 | 
				
			||||||
	private final VideoProcessor videoProcessor;
 | 
						private final VideoProcessor videoProcessor;
 | 
				
			||||||
	private final ThumbnailGenerator thumbnailGenerator;
 | 
						private final ThumbnailGenerator thumbnailGenerator;
 | 
				
			||||||
 | 
						private final ObjectMapper objectMapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public VideoProcessingService(Executor videoProcessingExecutor,
 | 
						public VideoProcessingService(Executor videoProcessingExecutor,
 | 
				
			||||||
								  VideoProcessingTaskRepository taskRepo,
 | 
													  VideoProcessingTaskRepository taskRepo,
 | 
				
			||||||
								  FileStorageService fileStorageService,
 | 
													  FileStorageService fileStorageService,
 | 
				
			||||||
								  VideoProcessor videoProcessor,
 | 
													  VideoProcessor videoProcessor,
 | 
				
			||||||
								  ThumbnailGenerator thumbnailGenerator) {
 | 
													  ThumbnailGenerator thumbnailGenerator, ObjectMapper objectMapper) {
 | 
				
			||||||
		this.videoProcessingExecutor = videoProcessingExecutor;
 | 
							this.videoProcessingExecutor = videoProcessingExecutor;
 | 
				
			||||||
		this.taskRepo = taskRepo;
 | 
							this.taskRepo = taskRepo;
 | 
				
			||||||
		this.fileStorageService = fileStorageService;
 | 
							this.fileStorageService = fileStorageService;
 | 
				
			||||||
		this.videoProcessor = videoProcessor;
 | 
							this.videoProcessor = videoProcessor;
 | 
				
			||||||
		this.thumbnailGenerator = thumbnailGenerator;
 | 
							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)
 | 
						@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
 | 
				
			||||||
| 
						 | 
					@ -83,6 +95,7 @@ public class VideoProcessingService {
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Run the actual processing here.
 | 
				
			||||||
		Path videoFile = uploadFile.resolveSibling(task.getUploadFileId() + "-vid-out");
 | 
							Path videoFile = uploadFile.resolveSibling(task.getUploadFileId() + "-vid-out");
 | 
				
			||||||
		Path thumbnailFile = uploadFile.resolveSibling(task.getUploadFileId() + "-thm-out");
 | 
							Path thumbnailFile = uploadFile.resolveSibling(task.getUploadFileId() + "-thm-out");
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
| 
						 | 
					@ -107,29 +120,20 @@ public class VideoProcessingService {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// And finally, copy the output to the final location.
 | 
							// And finally, copy the output to the final location.
 | 
				
			||||||
		try (
 | 
							try {
 | 
				
			||||||
				var videoIn = Files.newInputStream(videoFile);
 | 
					 | 
				
			||||||
				var thumbnailIn = Files.newInputStream(thumbnailFile)
 | 
					 | 
				
			||||||
		) {
 | 
					 | 
				
			||||||
			// Save the video to a final file location.
 | 
								// Save the video to a final file location.
 | 
				
			||||||
			var originalMetadata = fileStorageService.getMetadata(task.getUploadFileId());
 | 
								var originalMetadata = fileStorageService.getMetadata(task.getUploadFileId());
 | 
				
			||||||
			FileMetadata metadata = new FileMetadata(
 | 
								FileMetadata metadata = new FileMetadata(originalMetadata.filename(), originalMetadata.mimeType(), true);
 | 
				
			||||||
					originalMetadata.filename(),
 | 
								String videoFileId = fileStorageService.save(videoFile, metadata);
 | 
				
			||||||
					originalMetadata.mimeType(),
 | 
					
 | 
				
			||||||
					true
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			fileStorageService.save(ULID.parseULID(task.getVideoFileId()), videoIn, metadata, Files.size(videoFile));
 | 
					 | 
				
			||||||
			// Save the thumbnail too.
 | 
								// Save the thumbnail too.
 | 
				
			||||||
			FileMetadata thumbnailMetadata = new FileMetadata(
 | 
								FileMetadata thumbnailMetadata = new FileMetadata("thumbnail.jpeg", "image/jpeg", true);
 | 
				
			||||||
					"thumbnail.jpeg",
 | 
								String thumbnailFileId = fileStorageService.save(thumbnailFile, thumbnailMetadata);
 | 
				
			||||||
					"image/jpeg",
 | 
					 | 
				
			||||||
					true
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			fileStorageService.save(thumbnailIn, thumbnailMetadata, Files.size(thumbnailFile));
 | 
					 | 
				
			||||||
			updateTask(task, VideoProcessingTask.Status.COMPLETED);
 | 
								updateTask(task, VideoProcessingTask.Status.COMPLETED);
 | 
				
			||||||
			log.info("Finished processing task {}.", task.getId());
 | 
								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) {
 | 
							} catch (IOException e) {
 | 
				
			||||||
			log.error("Failed to copy processed video to final storage location.", e);
 | 
								log.error("Failed to copy processed video to final storage location.", e);
 | 
				
			||||||
			updateTask(task, VideoProcessingTask.Status.FAILED);
 | 
								updateTask(task, VideoProcessingTask.Status.FAILED);
 | 
				
			||||||
| 
						 | 
					@ -145,8 +149,24 @@ public class VideoProcessingService {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void updateTask(VideoProcessingTask task, VideoProcessingTask.Status status) {
 | 
						private void sendProcessedDataToApi(String videoId, String thumbnailId) throws IOException {
 | 
				
			||||||
		task.setStatus(status);
 | 
							ObjectNode obj = objectMapper.createObjectNode();
 | 
				
			||||||
		taskRepo.saveAndFlush(task);
 | 
							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<Void> 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);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue