Backend MP4 conversion (#23)

* ADD conversion queue

* ADD RemuxService for MP4 conversion

* REMOVE unused conversion queue

* REORGANISE Job-related classes

* ADD Job stages

* REVERT to old commit, using Spring Async instead

* ADD asynchronous processing for video tasks

* PATCH and streamline progress tracking

* ADD asynchronous video processing and job restructuring

* REFACTOR job service method

* ADD job remux functionality

* ADD remuxing endpoint

* PATCH complete flag not updating in API response

* ADD progress type in frontend

* ADD reset functionality for job status

* PATCH missing progress bar for subsequent exports

* REDESIGN settings box

* ADD tracking video file conversion in frontend

* PATCH extension bug

* REMOVE autowired decorator
This commit is contained in:
Dylan De Faoite
2025-07-31 20:48:34 +02:00
committed by GitHub
parent 338eb605fd
commit 20f7ec8db4
24 changed files with 324 additions and 185 deletions

View File

@@ -1,15 +1,18 @@
package com.ddf.vodsystem.services.media;
import com.ddf.vodsystem.dto.CommandOutput;
import com.ddf.vodsystem.dto.ProgressTracker;
import com.ddf.vodsystem.dto.VideoMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -22,12 +25,21 @@ public class CompressionService {
private static final float BITRATE_MULTIPLIER = 0.9f;
private final Pattern timePattern = Pattern.compile("out_time_ms=(\\d+)");
public void compress(File inputFile, File outputFile, VideoMetadata videoMetadata, ProgressTracker progress) throws IOException, InterruptedException {
@Async("ffmpegTaskExecutor")
public CompletableFuture<CommandOutput> compress(File inputFile,
File outputFile,
VideoMetadata videoMetadata,
ProgressTracker progress
) throws IOException, InterruptedException {
logger.info("Compressing video from {} to {}", inputFile.getAbsolutePath(), outputFile.getAbsolutePath());
float length = videoMetadata.getEndPoint() - videoMetadata.getStartPoint();
List<String> command = buildCommand(inputFile, outputFile, videoMetadata);
CommandRunner.run(command, line -> setProgress(line, progress, length));
CommandOutput result = CommandRunner.run(command, line -> setProgress(line, progress, length));
progress.markComplete();
return CompletableFuture.completedFuture(result);
}
private void setProgress(String line, ProgressTracker progress, float length) {

View File

@@ -7,17 +7,21 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@Service
public class MetadataService {
private static final Logger logger = LoggerFactory.getLogger(MetadataService.class);
public VideoMetadata getVideoMetadata(File file) {
@Async("ffmpegTaskExecutor")
public Future<VideoMetadata> getVideoMetadata(File file) {
logger.info("Getting metadata for file {}", file.getAbsolutePath());
List<String> command = List.of(
@@ -40,7 +44,7 @@ public class MetadataService {
}
JsonNode node = mapper.readTree(outputBuilder.toString());
return parseVideoMetadata(node);
return CompletableFuture.completedFuture(parseVideoMetadata(node));
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
throw new FFMPEGException("Error while getting video metadata: " + e);

View File

@@ -0,0 +1,46 @@
package com.ddf.vodsystem.services.media;
import com.ddf.vodsystem.dto.CommandOutput;
import com.ddf.vodsystem.dto.ProgressTracker;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
public class RemuxService {
private final Pattern timePattern = Pattern.compile("out_time_ms=(\\d+)");
@Async("ffmpegTaskExecutor")
public CompletableFuture<CommandOutput> remux(File inputFile,
File outputFile,
ProgressTracker remuxProgress,
float length
) throws IOException, InterruptedException {
List<String> command = List.of(
"ffmpeg",
"-progress", "pipe:1",
"-y",
"-i", inputFile.getAbsolutePath(),
"-c:v", "h264",
"-c:a", "aac",
"-f", "mp4",
outputFile.getAbsolutePath()
);
return CompletableFuture.completedFuture(CommandRunner.run(command, line -> setProgress(line, remuxProgress, length)));
}
private void setProgress(String line, ProgressTracker progress, float length) {
Matcher matcher = timePattern.matcher(line);
if (matcher.find()) {
float timeInMs = Float.parseFloat(matcher.group(1)) / 1000000f;
progress.setProgress(timeInMs / length);
}
}
}

View File

@@ -1,8 +1,8 @@
package com.ddf.vodsystem.services.media;
import com.ddf.vodsystem.dto.CommandOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.File;
@@ -13,7 +13,8 @@ import java.util.List;
public class ThumbnailService {
private static final Logger logger = LoggerFactory.getLogger(ThumbnailService.class);
public CommandOutput createThumbnail(File inputFile, File outputFile, Float timeInVideo) throws IOException, InterruptedException {
@Async("ffmpegTaskExecutor")
public void createThumbnail(File inputFile, File outputFile, Float timeInVideo) throws IOException, InterruptedException {
logger.info("Creating thumbnail at {} seconds", timeInVideo);
List<String> command = List.of(
@@ -24,6 +25,6 @@ public class ThumbnailService {
outputFile.getAbsolutePath()
);
return CommandRunner.run(command);
CommandRunner.run(command);
}
}