ADD FFMPEG conversion

This commit is contained in:
2025-05-08 21:59:03 +02:00
parent 920bcc32b1
commit d27276b66a
8 changed files with 274 additions and 10 deletions

View File

@@ -35,7 +35,6 @@
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>

View File

@@ -0,0 +1,31 @@
package com.ddf.vodsystem.controllers;
import com.ddf.vodsystem.entities.EditDTO;
import com.ddf.vodsystem.services.EditService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
public class EditController {
private final EditService editService;
@Autowired
public EditController(EditService editService) {
this.editService = editService;
}
@PostMapping("edit/{uuid}")
public ResponseEntity<String> edit(@PathVariable("uuid") String uuid, @ModelAttribute EditDTO editDTO) {
editService.edit(uuid, editDTO);
return new ResponseEntity<>(uuid, HttpStatus.OK);
}
@GetMapping("/convert/{uuid}")
public ResponseEntity<String> convert(@PathVariable("uuid") String uuid) {
editService.jobReady(uuid);
return new ResponseEntity<>(uuid, HttpStatus.OK);
}
}

View File

@@ -0,0 +1,13 @@
package com.ddf.vodsystem.entities;
import lombok.Data;
@Data
public class EditDTO {
private Float startPoint;
private Float endPoint;
private Float fps;
private Integer width;
private Integer height;
private Float fileSize;
}

View File

@@ -1,7 +1,9 @@
package com.ddf.vodsystem.entities;
import com.ddf.vodsystem.services.FfmpegService;
import lombok.Data;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -14,15 +16,16 @@ public class Job implements Runnable {
private File file;
// configs
private float startPoint;
private float endPoint;
private float fps;
private int width;
private int height;
private float fileSize;
private Float startPoint;
private Float endPoint;
private Float fps;
private Integer width;
private Integer height;
private Float fileSize;
// job status
private JobStatus status = JobStatus.PENDING;
private Float progress = 0.0f;
public Job(String uuid, File file) {
this.uuid = uuid;
@@ -31,10 +34,26 @@ public class Job implements Runnable {
@Override
public void run() {
logger.info("Job started");
logger.info("Job {} started", uuid);
this.status = JobStatus.RUNNING;
FfmpegService f = new FfmpegService(file, new File("output.mp4"));
f.setStartPoint(startPoint);
f.setEndPoint(endPoint);
f.setFps(fps);
f.setWidth(width);
f.setHeight(height);
f.setFileSize(fileSize);
try {
f.run();
} catch (IOException | InterruptedException e) {
logger.error(e.getMessage());
}
this.status = JobStatus.FINISHED;
logger.info("Job {} finished", uuid);
}
}

View File

@@ -0,0 +1,54 @@
package com.ddf.vodsystem.services;
import com.ddf.vodsystem.entities.EditDTO;
import com.ddf.vodsystem.entities.Job;
import org.springframework.stereotype.Service;
@Service
public class EditService {
private final JobService jobService;
public EditService(JobService jobService) {
this.jobService = jobService;
}
public void edit(String uuid, EditDTO editDTO) {
Job job = jobService.get(uuid);
if (editDTO.getStartPoint() != null) {
if (editDTO.getStartPoint() < 0) {
throw new IllegalArgumentException("Start point cannot be negative");
}
job.setStartPoint(editDTO.getStartPoint());
}
if (editDTO.getEndPoint() != null) {
job.setEndPoint(editDTO.getEndPoint());
}
if (editDTO.getFps() != null) {
job.setFps(editDTO.getFps());
}
if (editDTO.getWidth() != null) {
job.setWidth(editDTO.getWidth());
}
if (editDTO.getHeight() != null) {
job.setHeight(editDTO.getHeight());
}
if (editDTO.getFileSize() != null) {
if (editDTO.getFileSize() < 0) {
throw new IllegalArgumentException("File size cannot be negative");
}
job.setFileSize(editDTO.getFileSize());
}
}
public void jobReady(String uuid) {
jobService.jobReady(uuid);
}
}

View File

@@ -0,0 +1,140 @@
package com.ddf.vodsystem.services;
import lombok.Data;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.lang.Long.parseLong;
@Data
public class FfmpegService {
private static final Logger logger = LoggerFactory.getLogger(FfmpegService.class);
private List<String> command;
private File inputFile;
private File outputFile;
private Float startPoint;
private Float endPoint;
private Integer width;
private Integer height;
private Float fps;
private Float fileSize;
private static final float AUDIO_RATIO = 0.2f;
private static final float BITRATE_MULTIPLIER = 0.9f;
private Pattern timePattern = Pattern.compile("time=([\\d:.]+)");
private long out_time_ms;
public FfmpegService(File file, File output) {
command = new ArrayList<>();
command.add("ffmpeg");
command.add("-progress");
command.add("pipe:1");
command.add("-y");
this.inputFile = file;
this.outputFile = output;
}
public void setResolution(Integer width, Integer height) {
this.width = width;
this.height = height;
}
private void buildFilters() {
List<String> filters = new ArrayList<>();
System.out.println(fps);
if (fps != null) {
filters.add("fps=" + fps);
}
if ((width != null && height == null) || (height != null && width == null)) {
String w = (width != null) ? width.toString() : "-1";
String h = (height != null) ? height.toString() : "-1";
filters.add("scale=" + w + ":" + h);
}
if (!filters.isEmpty()) {
command.add("-vf");
command.add(String.join(",", filters));
}
}
private void buildBitrate() {
float length = endPoint - startPoint;
float bitrate = (fileSize / length) * BITRATE_MULTIPLIER;
float video_bitrate = bitrate * (1 - AUDIO_RATIO);
float audio_bitrate = bitrate * AUDIO_RATIO;
command.add("-b:v");
command.add(video_bitrate + "k");
command.add("-b:a");
command.add(audio_bitrate + "k");
}
private void buildInputs(){
if (startPoint != null) {
command.add("-ss");
command.add(startPoint.toString());
}
command.add("-i");
command.add(inputFile.getAbsolutePath());
if (endPoint != null) {
command.add("-t");
Float duration = endPoint - startPoint;
command.add(duration.toString());
}
}
private ProcessBuilder buildCommand() {
buildInputs();
buildFilters();
if (fileSize != null) {
buildBitrate();
}
// Output file
command.add(outputFile.getAbsolutePath());
logger.info("Running command: {}", String.join(" ", command));
return new ProcessBuilder(command);
}
public void run() throws IOException, InterruptedException {
logger.info("FFMPEG starting...");
ProcessBuilder pb = buildCommand();
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("out_time_ms=")) {
out_time_ms = parseLong(line.substring("out_time_ms=".length()));
}
}
logger.info("FFMPEG finished");
}
}

View File

@@ -43,7 +43,9 @@ public class JobService {
Thread thread = new Thread(() -> {
while (true) {
if (!jobQueue.isEmpty()) {
Runnable task = jobQueue.poll();
Job task = jobQueue.poll();
logger.info("Starting job {}", task.getUuid());
task.run(); // Execute the task
}

View File

@@ -1,6 +1,7 @@
package com.ddf.vodsystem.services;
import com.ddf.vodsystem.entities.Job;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@@ -14,11 +15,16 @@ import java.nio.file.StandardCopyOption;
import java.util.Base64;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class UploadService {
private static final Logger logger = LoggerFactory.getLogger(UploadService.class);
private static final String UPLOAD_DIR = "videos/";
private final JobService jobService;
@Autowired
public UploadService(JobService jobService) {
this.jobService = jobService;
}
@@ -41,7 +47,7 @@ public class UploadService {
Path filePath = Paths.get(outputFile.getAbsolutePath());
Files.copy(inputFile.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
logger.error(e.getMessage());
}
}