3
\$\begingroup\$

In this java application, I am trying to convert an video into small clips.

Here is the implementation class for the same

package ffmpeg.clip.process;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import ffmpeg.clip.utils.VideoConstant;
import ffmpeg.clip.utils.VideoUtils;

/*
 * @author Nitishkumar Singh
 * @Description: class will use ffmpeg to break an source video into clips
 */
public class VideoToClip {

    /*
     * Prevent from creating instance
     */
    private VideoToClip() {
    }

    /**
     * Get Video Duration is milliseconds
     * 
     * @Exception IOException - File does not exist VideoException- Video File have data issues
     */
    static LocalTime getDuration(String sourceVideoFile) throws Exception {
        if (!Paths.get(sourceVideoFile).toFile().exists())
            throw new Exception("File does not exist!!");

        Process proc = new ProcessBuilder(VideoConstant.SHELL, VideoConstant.SHELL_COMMAND_STRING_ARGUMENT,
                String.format(VideoConstant.DURATION_COMMAND, sourceVideoFile)).start();
        boolean errorOccured = (new BufferedReader(new InputStreamReader(proc.getErrorStream())).lines()
                .count() > VideoConstant.ZERO);
        String durationInSeconds = new BufferedReader(new InputStreamReader(proc.getInputStream())).lines()
                .collect(Collectors.joining(System.lineSeparator()));
        proc.destroy();
        if (errorOccured || (durationInSeconds.length() == VideoConstant.ZERO))
            throw new Exception("Video File have some issues!");
        else
            return VideoUtils.parseHourMinuteSecondMillisecondFormat(durationInSeconds);
    }

    /**
     * Create Clips for Video Using Start and End Second
     * 
     * @Exception IOException - Clip Creation Process Failed InterruptedException - Clip Creation task get's failed
     */
    static String toClipProcess(String sourceVideo, String outputDirectory, LocalTime start, LocalTime end,
            String fileExtension) throws IOException, InterruptedException, ExecutionException {

        String clipName = String.format(VideoConstant.CLIP_FILE_NAME,
                VideoUtils.getHourMinuteSecondMillisecondFormat(start),
                VideoUtils.getHourMinuteSecondMillisecondFormat(end), fileExtension);

        String command = String.format(VideoConstant.FFMPEG_OUTPUT_COMMAND, sourceVideo,
                VideoUtils.getHourMinuteSecondMillisecondFormat(start),
                VideoUtils.getHourMinuteSecondMillisecondFormat(end.minus(start.toNanoOfDay(), ChronoUnit.NANOS)),
                outputDirectory, clipName);
        LocalTime startTime = LocalTime.now();
        System.out.println("Clip Name: " + clipName);
        System.out.println("FFMPEG Process Execution Started");
        CompletableFuture<Process> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                return executeProcess(command);
            } catch (InterruptedException | IOException ex) {
                throw new RuntimeException(ex);
            }
        });
        completableFuture.get();
        // remove
        LocalTime endTime = LocalTime.now();
        System.out.println("Clip Name: " + clipName);
        System.out.println("FFMPEG Process Execution Finished");
        System.out.println("Duration: " + Duration.between(startTime, endTime).toMillis() / 1000);

        return clipName;
    }

    /**
     * Create and Execute Process for each command
     */
    static Process executeProcess(String command) throws InterruptedException, IOException {
        Process clipProcess = Runtime.getRuntime().exec(command);
        clipProcess.waitFor();
        return clipProcess;
    }
}

The Entire Solution is availble at Github. I am actually using CompletableFuture and running FFMPEG command by creating Java Process. The time it takes is too much. For a 40 minutes video, it takes more than 49 minutes, on a 64 CPU machine. I am trying to reduce the core size to 8 or something, as well improve its performance, as this kind of performance won't be acceptable for any kind of application.

22-jan-2017 update
One Update, I have changed the FFMPEG command to create clips and updated to FFMPEG 3, but there is no improvement.

ffmpeg -y -i INPUT_FILE_PATH -ss TIME_STAMP -t DURATION_TO_CLIP OUTPUT_FILE_PATH

\$\endgroup\$
1
  • \$\begingroup\$ I posted some examples using JavaCV I didn't tried with a long video video but for short videos (i.e. 5 mins) the processing time is near 1 minute approximately. \$\endgroup\$ – Teocci Dec 10 '18 at 7:08
3
\$\begingroup\$

That is a natural restriction on video encoding. On modern machines 1 minute of 720p video is encoded approximately in 1 minute.

You can save a lot of time if you do not need re-encoding (i.e. changing codec or video size) by using -codec copy ffmpeg option.

Also you said you have 64 cores, but your code use only 1 thread for encoding. Use -threads 0 to allow ffmpeg to choose by itself.

Also, if you need to perform this in Java - give Jaffree a chance (I'm an author).

\$\endgroup\$
3
  • \$\begingroup\$ If you wrote Jaffree, please, state so explicitly. \$\endgroup\$ – Mast Feb 11 '18 at 23:42
  • \$\begingroup\$ github.com/bramp/ffmpeg-cli-wrapper might be a better solution for you. \$\endgroup\$ – thekevshow Nov 20 '18 at 3:45
  • \$\begingroup\$ I started Jaffree exactly because of several issues with ffmpeg-cli-wrapper. \$\endgroup\$ – Denis Kokorin Nov 20 '18 at 5:46
0
\$\begingroup\$

I know this is an old question but I think this might be useful for Java Developers.

There is a nice library called JavaCV this live is a wrapper for multiple C and C++ libraries like FFmpeg.

This is a simple example of how to implement a Converter:

import org.bytedeco.javacpp.avcodec;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class PacketRecorderTest {

    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd__hhmmSSS");

    private static final int RECORD_LENGTH = 5000;

    private static final boolean AUDIO_ENABLED = false;

    public static void main(String[] args) throws FrameRecorder.Exception, FrameGrabber.Exception {

        String inputFile = "/home/usr/videos/VIDEO_FILE_NAME.mp4";

        // Decodes-encodes
        String outputFile = "/tmp/" + DATE_FORMAT.format(new Date()) + "_frameRecord.mp4";
        PacketRecorderTest.frameRecord(inputFile, outputFile);

        // copies codec (no need to re-encode)
        outputFile = "/tmp/" + DATE_FORMAT.format(new Date()) + "_packetRecord.mp4";
        PacketRecorderTest.packetRecord(inputFile, outputFile);

    }

    public static void frameRecord(String inputFile, String outputFile) throws FrameGrabber.Exception, FrameRecorder.Exception {

        int audioChannel = AUDIO_ENABLED ? 1 : 0;

        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputFile, 1280, 720, audioChannel);

        grabber.start();
        recorder.start();

        Frame frame;
        long t1 = System.currentTimeMillis();
        while ((frame = grabber.grabFrame(AUDIO_ENABLED, true, true, false)) != null) {
            recorder.record(frame);
            if ((System.currentTimeMillis() - t1) > RECORD_LENGTH) {
                break;
            }
        }
        recorder.stop();
        grabber.stop();
    }

    public static void packetRecord(String inputFile, String outputFile) throws FrameGrabber.Exception, FrameRecorder.Exception {

        int audioChannel = AUDIO_ENABLED ? 1 : 0;

        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputFile, 1280, 720, audioChannel);

        grabber.start();
        recorder.start(grabber.getFormatContext());

        avcodec.AVPacket packet;
        long t1 = System.currentTimeMillis();
        while ((packet = grabber.grabPacket()) != null) {
            recorder.recordPacket(packet);
            if ((System.currentTimeMillis() - t1) > RECORD_LENGTH) {
                break;
            }
        }

        recorder.stop();
        grabber.stop();
    }
}

This basic implementation shows how to convert a video using a FFmpeg packet or a JavaCV frame.

\$\endgroup\$
1
  • \$\begingroup\$ Welcome to Code Review. You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process. \$\endgroup\$ – Zeta Dec 10 '18 at 11:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.