Jaffree Documentation

repository·master·Indexed 20 days ago

https://github.com/kokorin/jaffree

A free Java wrapper for FFmpeg and FFprobe command-line tools. Jaffree enables programmatic video production, consumption, and stream analysis, featuring support for SeekableByteChannel, InputStream/OutputStream, desktop screen capture, HLS re-streaming, and custom FrameProducer/FrameConsumer implementations for generating and extracting video frames.

Tokens
7.5K
Snippets
17
Records
17
Agent score
19%

What's inside Jaffree

  1. Handle FFmpeg errors using -xerror

    master

    By default, Jaffree raises exceptions only when FFmpeg exits with a non-zero exit code. If FFmpeg encounters an error but exits with a zero status, Jaffree may not raise an exception. To force FFmpeg to exit with an error status when an error occurs, add the -xerror argument using FFmpeg.atPath().

    FFmpeg.atPath()
          .addArgument("-xerror")
          // ...
  2. Forcefully stop FFmpeg execution

    master

    If a process must be stopped immediately, you can use one of three force-stop methods. Warning: Force-stopping may result in corrupted output media as FFmpeg might not finalize the file correctly.

    1. Via ProgressListener: Throw a RuntimeException inside the onProgress method of your ProgressListener.
    2. Via FFmpegResultFuture: If using executeAsync(), call future.forceStop().
    3. Via Thread Interruption: If using the synchronous execute() method, run it in a separate thread and call thread.interrupt() on that thread.
    // Method 1: Throw exception in ProgressListener
    final AtomicBoolean stopped = new AtomicBoolean();
    ffmpeg.setProgressListener(
            new ProgressListener() {
                @Override
                public void onProgress(FFmpegProgress progress) {
                    if (stopped.get()) {
                        throw new RuntimeException("Stopped with exception!");
                    }
                }
            }
    );
    
    // Method 2: Use forceStop on the future
    FFmpegResultFuture future = ffmpeg.executeAsync();
    Thread.sleep(5_000);
    future.forceStop();
    
    // Method 3: Interrupt the execution thread
    Thread thread = new Thread() {
        @Override
        public void run() {
            ffmpeg.execute();
        }
    };
    thread.start();
    Thread.sleep(5_000);
    thread.interrupt();
  3. Gracefully stop FFmpeg execution

    master

    To stop an asynchronous FFmpeg process without corrupting the output, use FFmpegResultFuture#graceStop(). This method sends the q symbol to FFmpeg's stdin, allowing it to finalize the output media. Note that finalization may take several seconds.

    Requirement: You must use FFmpeg#executeAsync() to obtain a FFmpegResultFuture.

    FFmpegResultFuture future = ffmpeg.executeAsync();
    
    Thread.sleep(5_000);
    future.graceStop();
  4. Install Jaffree via Maven

    master

    Add the following dependencies to your pom.xml to use Jaffree. Note that you should also include slf4j-api as a dependency to allow for custom SLF4J version management.

    <dependency>
        <groupId>com.github.kokorin.jaffree</groupId>
        <artifactId>jaffree</artifactId>
        <version>${jaffree.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
    </dependency>
  5. Re-encode media and track progress

    master

    To track progress during re-encoding, first determine the total duration (e.g., by transcoding to NullOutput), then run the actual encoding process using a ProgressListener. You can calculate percentage completion by comparing progress.getTimeMillis() against the total duration.

    final AtomicLong duration = new AtomicLong();
    FFmpeg.atPath()
        .addInput(UrlInput.fromUrl(pathToSrc))
        .setOverwriteOutput(true)
        .addOutput(new NullOutput())
        .setProgressListener(new ProgressListener() {
            @Override
            public void onProgress(FFmpegProgress progress) {
                duration.set(progress.getTimeMillis());
            }
        })
        .execute();
    
    FFmpeg.atPath()
        .addInput(UrlInput.fromUrl(pathToSrc))
        .setOverwriteOutput(true)
        .addArguments("-movflags", "faststart")
        .addOutput(UrlOutput.toUrl(pathToDst))
        .setProgressListener(new ProgressListener() {
            @Override
            public void onProgress(FFmpegProgress progress) {
                double percents = 100. * progress.getTimeMillis() / duration.get();
                System.out.println("Progress: " + percents + "% ");
            }
        })
        .execute();
  6. Supply and consume data with SeekableByteChannel

    master

    Jaffree supports SeekableByteChannel for both input and output. Under the hood, it uses a tiny FTP server to facilitate this interaction. This is a distinct feature compared to other libraries.

    try (SeekableByteChannel inputChannel =
             Files.newByteChannel(pathToSrc, StandardOpenOption.READ);
         SeekableByteChannel outputChannel =
             Files.newByteChannel(pathToDst, StandardOpenOption.CREATE,
                     StandardOpenOption.WRITE, StandardOpenOption.READ,
                     StandardOpenOption.TRUNCATE_EXISTING)
    ) {
        FFmpeg.atPath()
            .addInput(ChannelInput.fromChannel(inputChannel))
            .addOutput(ChannelOutput.toChannel(filename, outputChannel))
            .execute();
    }
  7. Capture the desktop screen

    master

    Use CaptureInput.captureDesktop() to record the screen. You can configure the frame rate and whether to capture the cursor. It is often best practice to record with a fast preset (like ultrafast) to reduce CPU load, and then perform a second pass to re-encode the file for optimization.

    FFmpeg.atPath()
        .addInput(CaptureInput
                .captureDesktop()
                .setCaptureFrameRate(30)
                .setCaptureCursor(true)
        )
        .addOutput(
            UrlOutput
                .toPath(pathToVideo)
                .addArguments("-preset", "ultrafast")
                .setDuration(30, TimeUnit.SECONDS)
        )
        .setOverwriteOutput(true)
        .execute();
    
    // Re-encode for optimization
    Path pathToOptimized = pathToVideo.resolveSibling("optimized-" + pathToVideo.getFileName());
    FFmpeg.atPath()
        .addInput(UrlInput.fromPath(pathToVideo))
        .addOutput(UrlOutput.toPath(pathToOptimized))
        .execute();
    
    Files.move(pathToOptimized, pathToVideo, StandardCopyOption.REPLACE_EXISTING);
  8. Detect exact media file duration

    master

    If ffprobe cannot provide an exact duration, you can use FFmpeg to transcode to a NullOutput and use a ProgressListener to capture the exact time in milliseconds via progress.getTimeMillis().

    final AtomicLong durationMillis = new AtomicLong();
    
    FFmpegResult ffmpegResult = FFmpeg.atPath()
        .addInput(
            UrlInput.fromUrl(pathToVideo)
        )
        .addOutput(new NullOutput())
        .setProgressListener(new ProgressListener() {
            @Override
            public void onProgress(FFmpegProgress progress) {
                durationMillis.set(progress.getTimeMillis());
            }
        })
        .execute();
    
    System.out.println("Exact duration: " + durationMillis.get() + " milliseconds");
  9. Live Stream Re-Streaming (HLS)

    master

    To re-stream a live source to HLS, use UrlInput.fromUrl(liveStream) and configure UrlOutput with .setFormat("hls"). You can use .addArguments() to pass specific HLS muxer parameters like hls_time, hls_list_size, and hls_flags.

    FFmpeg.atPath()
        .addInput(
            UrlInput.fromUrl(liveStream)
        )
        .addOutput(
            UrlOutput.toPath(dir.resolve("index.m3u8"))
                .setFrameRate(30)
                .setFormat("hls")
                .addArguments("-x264-params", "keyint=60")
                .addArguments("-hls_list_size", "5")
                .addArguments("-hls_delete_threshold", "5")
                .addArguments("-hls_time", "2")
                .addArguments("-hls_flags", "delete_segments")
        )
        .setOverwriteOutput(true)
        .execute();
  10. Check media streams with FFprobe

    master

    Use FFprobe.atPath() to inspect media files. You can enable stream information using .setShowStreams(true) and then iterate through the resulting FFprobeResult to access stream details like index, codec type, and duration.

    FFprobeResult result = FFprobe.atPath()
        .setShowStreams(true)
        .setInput(pathToVideo)
        .execute();
    
    for (Stream stream : result.getStreams()) {
        System.out.println("Stream #" + stream.getIndex()
            + " type: " + stream.getCodecType()
            + " duration: " + stream.getDuration() + " seconds");
    }
  11. Capture custom FFmpeg output via OutputListener

    master

    You can capture the raw text output from FFmpeg by using .setOutputListener(). This is useful for parsing specific reports (like loudnorm JSON output) generated by FFmpeg filters.

    final StringBuffer loudnormReport = new StringBuffer();
    
    FFmpeg.atPath()
        .addInput(UrlInput.fromUrl(pathToVideo))
        .addArguments("-af", "loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json")
        .addOutput(new NullOutput(false))
        .setOutputListener(new OutputListener() {
            @Override
            public void onOutput(String line) {
                loudnormReport.append(line);
            }
        })
        .execute();
    
    System.out.println("Loudnorm report:\n" + loudnormReport);
  12. Produce video programmatically in Java

    master

    You can generate video content entirely in Java by implementing a FrameProducer. The producer must implement produceStreams() to define the video/audio properties and produce() to return Frame objects. Returning null from produce() signals the end of the stream.

    FrameProducer producer = new FrameProducer() {
        private long frameCounter = 0;
    
        @Override
        public List<Stream> produceStreams() {
            return Collections.singletonList(new Stream()
                    .setType(Stream.Type.VIDEO)
                    .setTimebase(1000L)
                    .setWidth(320)
                    .setHeight(240)
            );
        }
    
        @Override
        public Frame produce() {
            if (frameCounter > 30) {
                return null; // End of Stream
            }
    
            BufferedImage image = new BufferedImage(320, 240, BufferedImage.TYPE_3BYTE_BGR);
            Graphics2D graphics = image.createGraphics();
            graphics.setPaint(new Color(frameCounter * 1.0f / 30, 0, 0));
            graphics.fillRect(0, 0, 320, 240);
            long pts = frameCounter * 1000 / 10; 
            Frame videoFrame = Frame.createVideoFrame(0, pts, image);
            frameCounter++;
    
            return videoFrame;
        }
    };
    
    FFmpeg.atPath()
        .addInput(FrameInput.withProducer(producer))
        .addOutput(UrlOutput.toUrl(pathToVideo))
        .execute();