diff --git a/pom.xml b/pom.xml index f1e30c5..df253f0 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,6 @@ com.vaadin vaadin-spring-boot-starter - org.springframework.boot spring-boot-devtools diff --git a/src/main/java/com/ddf/vodsystem/controllers/EditController.java b/src/main/java/com/ddf/vodsystem/controllers/EditController.java new file mode 100644 index 0000000..4497be5 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/controllers/EditController.java @@ -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 edit(@PathVariable("uuid") String uuid, @ModelAttribute EditDTO editDTO) { + editService.edit(uuid, editDTO); + return new ResponseEntity<>(uuid, HttpStatus.OK); + } + + @GetMapping("/convert/{uuid}") + public ResponseEntity convert(@PathVariable("uuid") String uuid) { + editService.jobReady(uuid); + return new ResponseEntity<>(uuid, HttpStatus.OK); + } + +} diff --git a/src/main/java/com/ddf/vodsystem/entities/EditDTO.java b/src/main/java/com/ddf/vodsystem/entities/EditDTO.java new file mode 100644 index 0000000..7b644e8 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/entities/EditDTO.java @@ -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; +} diff --git a/src/main/java/com/ddf/vodsystem/entities/Job.java b/src/main/java/com/ddf/vodsystem/entities/Job.java index b8476e0..c21bd4d 100644 --- a/src/main/java/com/ddf/vodsystem/entities/Job.java +++ b/src/main/java/com/ddf/vodsystem/entities/Job.java @@ -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); } } diff --git a/src/main/java/com/ddf/vodsystem/services/EditService.java b/src/main/java/com/ddf/vodsystem/services/EditService.java new file mode 100644 index 0000000..5ec23c5 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/services/EditService.java @@ -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); + } +} diff --git a/src/main/java/com/ddf/vodsystem/services/FfmpegService.java b/src/main/java/com/ddf/vodsystem/services/FfmpegService.java new file mode 100644 index 0000000..0f412e3 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/services/FfmpegService.java @@ -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 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 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"); + } + +} diff --git a/src/main/java/com/ddf/vodsystem/services/JobService.java b/src/main/java/com/ddf/vodsystem/services/JobService.java index 3ae9235..443e308 100644 --- a/src/main/java/com/ddf/vodsystem/services/JobService.java +++ b/src/main/java/com/ddf/vodsystem/services/JobService.java @@ -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 } diff --git a/src/main/java/com/ddf/vodsystem/services/UploadService.java b/src/main/java/com/ddf/vodsystem/services/UploadService.java index 390647b..8110699 100644 --- a/src/main/java/com/ddf/vodsystem/services/UploadService.java +++ b/src/main/java/com/ddf/vodsystem/services/UploadService.java @@ -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()); } }