SkillAgentSearch skills...

Jaffree

Java ffmpeg and ffprobe command-line wrapper

Install / Use

/learn @kokorin/Jaffree
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Jaffree Sparkline

Jaffree stands for JAva FFmpeg and FFprobe FREE command line wrapper. Jaffree supports programmatic video production and consumption (with transparency)

It integrates with ffmpeg via java.lang.Process.

Inspired by ffmpeg-cli-wrapper

Tested with the help of GitHub Actions

Tests Coverage

OS: Ubuntu, MacOS, Windows

JDK: 8, 11, 17

Usage

Maven Central

<dependency>
    <groupId>com.github.kokorin.jaffree</groupId>
    <artifactId>jaffree</artifactId>
    <version>${jaffree.version}</version>
</dependency>

<!--
    You should also include slf4j into dependencies.
    This is done intentionally to allow changing of slf4j version.
  -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

Examples

Checking media streams with ffprobe

See whole example here.

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");
}

Detecting exact media file duration

Sometimes ffprobe can't show exact duration, use ffmpeg trancoding to NULL output to get it.

See whole example here.

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");

Re-encode and track progress

See whole example here.

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();

Cut and scale media file

Pay attention that arguments related to Input must be set at Input, not at FFmpeg.

See whole example here.

FFmpeg.atPath()
    .addInput(
        UrlInput.fromUrl(pathToSrc)
                .setPosition(10, TimeUnit.SECONDS)
                .setDuration(42, TimeUnit.SECONDS)
    )
    .setFilter(StreamType.VIDEO, "scale=160:-2")
    .setOverwriteOutput(true)
    .addArguments("-movflags", "faststart")
    .addOutput(
        UrlOutput.toUrl(pathToDst)
                 .setPosition(10, TimeUnit.SECONDS)
    )
    .execute();

Custom parsing of ffmpeg output

See whole example here.

// StringBuffer - because it's thread safe
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);

Supplying and consuming data with SeekableByteChannel

Ability to interact with SeekableByteChannel is one of the features, which distinct Jaffree from similar libraries. Under the hood Jaffree uses tiny FTP server to interact with SeekableByteChannel.

See whole example here.

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();
}

Supplying and consuming data with InputStream and OutputStream

Notice It's recommended to use ChannelInput & ChannelOutput since ffmpeg leverage seeking in input and requires seekable output for many formats.

Under the hood pipes are not OS pipes, but TCP Sockets. This allows much higher bandwidth.

See whole example here.

try (InputStream inputStream =
         Files.newInputStream(pathToSrc);
     OutputStream outputStream =
         Files.newOutputStream(pathToDst, StandardOpenOption.CREATE,
                 StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)
) {
    FFmpeg.atPath()
        .addInput(PipeInput.pumpFrom(inputStream))
        .addOutput(
                PipeOutput.pumpTo(outputStream)
                        .setFormat("flv")
        )
        .execute();
}

Live Stream Re-Streaming (HLS)

See whole example here.

FFmpeg.atPath()
    .addInput(
        UrlInput.fromUrl(liveStream)
    )
    .addOutput(
        UrlOutput.toPath(dir.resolve("index.m3u8"))
            .setFrameRate(30)
            // check all available options: ffmpeg -help muxer=hls
            .setFormat("hls")
            // enforce keyframe every 2s - see setFrameRate
            .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();

Screen Capture

See whole example here.

FFmpeg.atPath()
    .addInput(CaptureInput
            .captureDesktop()
            .setCaptureFrameRate(30)
            .setCaptureCursor(true)
    )
    .addOutput(UrlOutput
            .toPath(pathToVideo)
            // Record with ultrafast to lower CPU usage
            .addArguments("-preset", "ultrafast")
            .setDuration(30, TimeUnit.SECONDS)
    )
    .setOverwriteOutput(true)
    .execute();

//Re-encode when record is completed to optimize file size 
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);

Produce Video in Pure Java Code

See whole example here. Check also more advanced example which produce both audio and video

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; // return null when End of Stream is reached
        }

        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 PTS in Stream Timebase
        Frame videoFrame = Frame.createVideoFrame(0, pts, image);
        frameCounter++;

        return videoFrame;
    }
};

FFmpeg.atPath()
    .addInput(FrameInput.withProducer(producer))
    .addOutput(UrlOutput.toUrl(pathToVideo))
    .execute();

Here is an output of the above example:

example output

Consume Video in Pure Java Code

See whole example here.

FFmpeg.atPath()
        .addInput(UrlInput
                .fromPath(pathToSrc)
        )
        .addOutput(FrameOutput
                .withConsumer(
                        new
View on GitHub
GitHub Stars536
CategoryContent
Updated15d ago
Forks90

Languages

Java

Security Score

100/100

Audited on Mar 14, 2026

No findings