diff --git a/src/main/java/com/ddf/vodsystem/entities/Job.java b/src/main/java/com/ddf/vodsystem/entities/Job.java index 4f1c6ad..5e59c31 100644 --- a/src/main/java/com/ddf/vodsystem/entities/Job.java +++ b/src/main/java/com/ddf/vodsystem/entities/Job.java @@ -1,6 +1,8 @@ package com.ddf.vodsystem.entities; import java.io.File; +import java.util.concurrent.atomic.AtomicReference; + import org.springframework.security.core.context.SecurityContext; import lombok.Data; @@ -20,7 +22,7 @@ public class Job { // job status private JobStatus status = JobStatus.NOT_READY; - private Float progress = 0.0f; + private AtomicReference progress = new AtomicReference<>(0f); public Job(String uuid, File inputFile, File outputFile, VideoMetadata inputVideoMetadata) { this.uuid = uuid; diff --git a/src/main/java/com/ddf/vodsystem/services/ClipService.java b/src/main/java/com/ddf/vodsystem/services/ClipService.java index e7e209d..78cba6f 100644 --- a/src/main/java/com/ddf/vodsystem/services/ClipService.java +++ b/src/main/java/com/ddf/vodsystem/services/ClipService.java @@ -2,15 +2,10 @@ package com.ddf.vodsystem.services; import com.ddf.vodsystem.entities.*; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; import java.time.LocalDateTime; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import com.ddf.vodsystem.exceptions.FFMPEGException; import com.ddf.vodsystem.repositories.ClipRepository; import com.ddf.vodsystem.security.CustomOAuth2User; import org.slf4j.Logger; @@ -27,7 +22,6 @@ public class ClipService { private final MetadataService metadataService; private final DirectoryService directoryService; private final FfmpegService ffmpegService; - private final Pattern timePattern = Pattern.compile("out_time_ms=(\\d+)"); public ClipService(ClipRepository clipRepository, MetadataService metadataService, @@ -51,19 +45,8 @@ public class ClipService { * */ public void run(Job job) throws IOException, InterruptedException { - logger.info("FFMPEG starting..."); metadataService.normalizeVideoMetadata(job.getInputVideoMetadata(), job.getOutputVideoMetadata()); - - ProcessBuilder pb = ffmpegService.buildCommand(job.getInputFile(), job.getOutputFile(), job.getOutputVideoMetadata()); - Process process = pb.start(); - job.setStatus(JobStatus.RUNNING); - - updateJobProgress(process, job); - - if (process.waitFor() != 0) { - job.setStatus(JobStatus.FAILED); - throw new FFMPEGException("FFMPEG process failed"); - } + ffmpegService.runWithProgress(job.getInputFile(), job.getOutputFile(), job.getOutputVideoMetadata(), job.getProgress()); Float fileSize = metadataService.getFileSize(job.getOutputFile()); job.getOutputVideoMetadata().setFileSize(fileSize); @@ -74,24 +57,9 @@ public class ClipService { } job.setStatus(JobStatus.FINISHED); - logger.info("FFMPEG finished"); + logger.info("FFMPEG finished successfully for job: {}", job.getUuid()); } - private void updateJobProgress(Process process, Job job) throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - float length = job.getOutputVideoMetadata().getEndPoint() - job.getOutputVideoMetadata().getStartPoint(); - - String line; - while ((line = reader.readLine()) != null) { - logger.debug(line); - Matcher matcher = timePattern.matcher(line); - - if (matcher.find()) { - Float progress = Long.parseLong(matcher.group(1))/(length*1000000); - job.setProgress(progress); - } - } - } private User getUser() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); diff --git a/src/main/java/com/ddf/vodsystem/services/EditService.java b/src/main/java/com/ddf/vodsystem/services/EditService.java index 5ccf654..e69e2f9 100644 --- a/src/main/java/com/ddf/vodsystem/services/EditService.java +++ b/src/main/java/com/ddf/vodsystem/services/EditService.java @@ -29,7 +29,7 @@ public class EditService { return 1f; } - return job.getProgress(); + return job.getProgress().get(); } private void validateClipConfig(VideoMetadata videoMetadata) { diff --git a/src/main/java/com/ddf/vodsystem/services/FfmpegService.java b/src/main/java/com/ddf/vodsystem/services/FfmpegService.java index dd8d495..741ca6e 100644 --- a/src/main/java/com/ddf/vodsystem/services/FfmpegService.java +++ b/src/main/java/com/ddf/vodsystem/services/FfmpegService.java @@ -5,9 +5,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +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.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Service public class FfmpegService { @@ -16,8 +22,51 @@ public class FfmpegService { private static final float AUDIO_RATIO = 0.15f; private static final float MAX_AUDIO_BITRATE = 128f; private static final float BITRATE_MULTIPLIER = 0.9f; + private final Pattern timePattern = Pattern.compile("out_time_ms=(\\d+)"); + + public void runWithProgress(File inputFile, File outputFile, VideoMetadata videoMetadata, AtomicReference progress) throws IOException, InterruptedException { + logger.info("Starting FFMPEG process"); + + List command = buildCommand(inputFile, outputFile, videoMetadata); + + String strCommand = String.join(" ", command); + logger.info("FFMPEG command: {}", strCommand); + + ProcessBuilder processBuilder = new ProcessBuilder(command); + processBuilder.redirectErrorStream(true); + + Process process = processBuilder.start(); + logger.info("FFMPEG process started with PID: {}", process.pid()); + + updateJobProgress(process, progress, videoMetadata.getEndPoint() - videoMetadata.getStartPoint()); + process.waitFor(); + + logger.info("FFMPEG process completed successfully"); + } + + public void run(File inputFile, File outputFile, VideoMetadata videoMetadata) throws IOException, InterruptedException { + runWithProgress(inputFile, outputFile, videoMetadata, new AtomicReference<>(0f)); + } + + private void updateJobProgress(Process process, AtomicReference progress, Float length) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + + String line; + while ((line = reader.readLine()) != null) { + logger.debug(line); + Matcher matcher = timePattern.matcher(line); + + if (matcher.find()) { + Float timeInMs = Float.parseFloat(matcher.group(1)) / 1000000f; + progress.set(timeInMs/length); + } + } + } + + private List buildFilters(Float fps, Integer width, Integer height) { + List command = new ArrayList<>(); + command.add("-vf"); - private void buildFilters(ArrayList command, Float fps, Integer width, Integer height) { List filters = new ArrayList<>(); if (fps != null) { @@ -32,16 +81,15 @@ public class FfmpegService { filters.add("scale=" + w + ":" + h); } - if (!filters.isEmpty()) { - logger.info("Adding video filters"); - command.add("-vf"); - command.add(String.join(",", filters)); - } + logger.info("Adding video filters"); + command.add(String.join(",", filters)); + return command; } - private void buildBitrate(ArrayList command, Float length, Float fileSize) { - float bitrate = ((fileSize * 8) / length) * BITRATE_MULTIPLIER; + private List buildBitrate(Float length, Float fileSize) { + List command = new ArrayList<>(); + float bitrate = ((fileSize * 8) / length) * BITRATE_MULTIPLIER; float audioBitrate = bitrate * AUDIO_RATIO; float videoBitrate; @@ -56,9 +104,13 @@ public class FfmpegService { command.add(videoBitrate + "k"); command.add("-b:a"); command.add(audioBitrate + "k"); + + return command; } - private void buildInputs(ArrayList command, File inputFile, Float startPoint, Float length) { + private List buildInputs(File inputFile, Float startPoint, Float length) { + List command = new ArrayList<>(); + command.add("-ss"); command.add(startPoint.toString()); @@ -67,25 +119,30 @@ public class FfmpegService { command.add("-t"); command.add(Float.toString(length)); + + return command; } - public ProcessBuilder buildCommand(File inputFile, File outputFile, VideoMetadata videoMetadata) { - ArrayList command = new ArrayList<>(); + private List buildCommand(File inputFile, File outputFile, VideoMetadata videoMetadata) { + List command = new ArrayList<>(); command.add("ffmpeg"); command.add("-progress"); command.add("pipe:1"); command.add("-y"); Float length = videoMetadata.getEndPoint() - videoMetadata.getStartPoint(); - buildInputs(command, inputFile, videoMetadata.getStartPoint(), length); - buildFilters(command, videoMetadata.getFps(), videoMetadata.getWidth(), videoMetadata.getHeight()); + command.addAll(buildInputs(inputFile, length, length)); + + if (videoMetadata.getFps() != null || videoMetadata.getWidth() != null || videoMetadata.getHeight() != null) { + command.addAll(buildFilters(videoMetadata.getFps(), videoMetadata.getWidth(), videoMetadata.getHeight())); + } if (videoMetadata.getFileSize() != null) { - buildBitrate(command, length, videoMetadata.getFileSize()); + command.addAll(buildBitrate(length, videoMetadata.getFileSize())); } // Output file command.add(outputFile.getAbsolutePath()); - return new ProcessBuilder(command); + return command; } } diff --git a/src/main/java/com/ddf/vodsystem/services/JobService.java b/src/main/java/com/ddf/vodsystem/services/JobService.java index ec15b0d..242a736 100644 --- a/src/main/java/com/ddf/vodsystem/services/JobService.java +++ b/src/main/java/com/ddf/vodsystem/services/JobService.java @@ -67,7 +67,6 @@ public class JobService { */ public void jobReady(String uuid) { Job job = getJob(uuid); - job.setProgress(0f); SecurityContext context = SecurityContextHolder.getContext(); job.setSecurityContext(context); @@ -113,6 +112,7 @@ public class JobService { Job job = jobQueue.take(); // Blocks until a job is available logger.info("Starting job {}", job.getUuid()); + job.setStatus(JobStatus.RUNNING); processJob(job); } catch (InterruptedException e) { Thread.currentThread().interrupt();