FFmpeg Wrapper in Java: Complete Multimedia Processing Library

Introduction

FFmpeg is a powerful multimedia framework for processing audio, video, and other multimedia files and streams. This article provides a comprehensive Java wrapper for FFmpeg that simplifies multimedia operations while maintaining full access to FFmpeg's capabilities.

Project Setup

Maven Dependencies

<!-- pom.xml -->
<properties>
<javafx.version>21</javafx.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- JavaFX for Media Playback (Optional) -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
</dependencies>

Core FFmpeg Wrapper

FFmpeg Command Builder

package com.ffmpeg.core;
import java.util.*;
public class FFmpegCommandBuilder {
private final List<String> command;
private boolean overwriteOutput;
public FFmpegCommandBuilder() {
this.command = new ArrayList<>();
this.command.add("ffmpeg");
this.overwriteOutput = false;
}
public FFmpegCommandBuilder overwriteOutput() {
this.overwriteOutput = true;
return this;
}
public FFmpegCommandBuilder hideBanner() {
command.add("-hide_banner");
return this;
}
public FFmpegCommandBuilder logLevel(LogLevel level) {
command.add("-loglevel");
command.add(level.getValue());
return this;
}
public FFmpegCommandBuilder input(String inputPath) {
command.add("-i");
command.add(inputPath);
return this;
}
public FFmpegCommandBuilder input(Map<String, String> options, String inputPath) {
for (Map.Entry<String, String> entry : options.entrySet()) {
command.add(entry.getKey());
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
command.add(entry.getValue());
}
}
command.add("-i");
command.add(inputPath);
return this;
}
public FFmpegCommandBuilder codec(Codec codec) {
command.add("-c");
command.add(codec.getValue());
return this;
}
public FFmpegCommandBuilder videoCodec(String codec) {
command.add("-c:v");
command.add(codec);
return this;
}
public FFmpegCommandBuilder audioCodec(String codec) {
command.add("-c:a");
command.add(codec);
return this;
}
public FFmpegCommandBuilder subtitleCodec(String codec) {
command.add("-c:s");
command.add(codec);
return this;
}
public FFmpegCommandBuilder videoBitrate(String bitrate) {
command.add("-b:v");
command.add(bitrate);
return this;
}
public FFmpegCommandBuilder audioBitrate(String bitrate) {
command.add("-b:a");
command.add(bitrate);
return this;
}
public FFmpegCommandBuilder resolution(int width, int height) {
command.add("-s");
command.add(width + "x" + height);
return this;
}
public FFmpegCommandBuilder frameRate(double fps) {
command.add("-r");
command.add(String.valueOf(fps));
return this;
}
public FFmpegCommandBuilder audioChannels(int channels) {
command.add("-ac");
command.add(String.valueOf(channels));
return this;
}
public FFmpegCommandBuilder audioSampleRate(int sampleRate) {
command.add("-ar");
command.add(String.valueOf(sampleRate));
return this;
}
public FFmpegCommandBuilder startTime(String time) {
command.add("-ss");
command.add(time);
return this;
}
public FFmpegCommandBuilder duration(String duration) {
command.add("-t");
command.add(duration);
return this;
}
public FFmpegCommandBuilder seek(String time) {
command.add("-ss");
command.add(time);
return this;
}
public FFmpegCommandBuilder toTime(String time) {
command.add("-to");
command.add(time);
return this;
}
public FFmpegCommandBuilder filter(String filter) {
command.add("-filter");
command.add(filter);
return this;
}
public FFmpegCommandBuilder videoFilter(String filter) {
command.add("-vf");
command.add(filter);
return this;
}
public FFmpegCommandBuilder audioFilter(String filter) {
command.add("-af");
command.add(filter);
return this;
}
public FFmpegCommandBuilder complexFilter(String filter) {
command.add("-filter_complex");
command.add(filter);
return this;
}
public FFmpegCommandBuilder map(String streamSpecifier) {
command.add("-map");
command.add(streamSpecifier);
return this;
}
public FFmpegCommandBuilder disableVideo() {
command.add("-vn");
return this;
}
public FFmpegCommandBuilder disableAudio() {
command.add("-an");
return this;
}
public FFmpegCommandBuilder disableSubtitle() {
command.add("-sn");
return this;
}
public FFmpegCommandBuilder preset(String preset) {
command.add("-preset");
command.add(preset);
return this;
}
public FFmpegCommandBuilder crf(int crf) {
command.add("-crf");
command.add(String.valueOf(crf));
return this;
}
public FFmpegCommandBuilder customOption(String option, String value) {
command.add(option);
if (value != null && !value.isEmpty()) {
command.add(value);
}
return this;
}
public FFmpegCommandBuilder customOption(String option) {
command.add(option);
return this;
}
public FFmpegCommandBuilder output(String outputPath) {
command.add(outputPath);
return this;
}
public List<String> build() {
List<String> finalCommand = new ArrayList<>(command);
if (overwriteOutput) {
finalCommand.add("-y");
}
return finalCommand;
}
public String buildAsString() {
List<String> cmd = build();
return String.join(" ", cmd);
}
public enum LogLevel {
QUIET("quiet"),
PANIC("panic"),
FATAL("fatal"),
ERROR("error"),
WARNING("warning"),
INFO("info"),
VERBOSE("verbose"),
DEBUG("debug"),
TRACE("trace");
private final String value;
LogLevel(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public enum Codec {
COPY("copy"),
H264("libx264"),
H265("libx265"),
VP9("libvpx-vp9"),
AAC("aac"),
MP3("libmp3lame"),
OPUS("libopus");
private final String value;
Codec(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}

FFmpeg Executor

package com.ffmpeg.core;
import java.io.*;
import java.util.List;
import java.util.concurrent.*;
public class FFmpegExecutor {
private final String ffmpegPath;
private final ExecutorService executorService;
private final long timeout;
private final TimeUnit timeoutUnit;
public FFmpegExecutor() {
this("ffmpeg");
}
public FFmpegExecutor(String ffmpegPath) {
this(ffmpegPath, 30, TimeUnit.MINUTES);
}
public FFmpegExecutor(String ffmpegPath, long timeout, TimeUnit timeoutUnit) {
this.ffmpegPath = ffmpegPath;
this.timeout = timeout;
this.timeoutUnit = timeoutUnit;
this.executorService = Executors.newCachedThreadPool();
}
public FFmpegResult execute(List<String> command) throws FFmpegException {
return execute(command, null);
}
public FFmpegResult execute(List<String> command, File workingDirectory) throws FFmpegException {
List<String> fullCommand = new ArrayList<>();
fullCommand.add(ffmpegPath);
fullCommand.addAll(command);
ProcessBuilder processBuilder = new ProcessBuilder(fullCommand);
if (workingDirectory != null) {
processBuilder.directory(workingDirectory);
}
processBuilder.redirectErrorStream(true);
try {
Process process = processBuilder.start();
FFmpegProcessHandler handler = new FFmpegProcessHandler(process);
Future<FFmpegResult> future = executorService.submit(handler);
try {
FFmpegResult result = future.get(timeout, timeoutUnit);
if (result.getExitCode() != 0) {
throw new FFmpegException("FFmpeg process failed with exit code: " + 
result.getExitCode(), result);
}
return result;
} catch (TimeoutException e) {
process.destroyForcibly();
throw new FFmpegException("FFmpeg process timed out", e);
} catch (ExecutionException e) {
throw new FFmpegException("FFmpeg process execution failed", e.getCause());
}
} catch (IOException e) {
throw new FFmpegException("Failed to start FFmpeg process", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new FFmpegException("FFmpeg process was interrupted", e);
}
}
public FFmpegResult execute(FFmpegCommandBuilder builder) throws FFmpegException {
return execute(builder.build());
}
public FFmpegResult execute(FFmpegCommandBuilder builder, File workingDirectory) throws FFmpegException {
return execute(builder.build(), workingDirectory);
}
public CompletableFuture<FFmpegResult> executeAsync(List<String> command) {
return executeAsync(command, null);
}
public CompletableFuture<FFmpegResult> executeAsync(List<String> command, File workingDirectory) {
return CompletableFuture.supplyAsync(() -> {
try {
return execute(command, workingDirectory);
} catch (FFmpegException e) {
throw new CompletionException(e);
}
}, executorService);
}
public void shutdown() {
executorService.shutdown();
try {
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
private static class FFmpegProcessHandler implements Callable<FFmpegResult> {
private final Process process;
public FFmpegProcessHandler(Process process) {
this.process = process;
}
@Override
public FFmpegResult call() throws Exception {
StringBuilder output = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
// You can add progress parsing here
}
}
int exitCode = process.waitFor();
return new FFmpegResult(exitCode, output.toString());
}
}
public static class FFmpegResult {
private final int exitCode;
private final String output;
private final long executionTime;
public FFmpegResult(int exitCode, String output) {
this(exitCode, output, 0);
}
public FFmpegResult(int exitCode, String output, long executionTime) {
this.exitCode = exitCode;
this.output = output;
this.executionTime = executionTime;
}
public int getExitCode() { return exitCode; }
public String getOutput() { return output; }
public long getExecutionTime() { return executionTime; }
public boolean isSuccess() { return exitCode == 0; }
}
public static class FFmpegException extends Exception {
private final FFmpegResult result;
public FFmpegException(String message) {
super(message);
this.result = null;
}
public FFmpegException(String message, Throwable cause) {
super(message, cause);
this.result = null;
}
public FFmpegException(String message, FFmpegResult result) {
super(message);
this.result = result;
}
public FFmpegResult getResult() {
return result;
}
}
}

Media Information Extraction

Media Analyzer

package com.ffmpeg.analysis;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ffmpeg.core.FFmpegExecutor;
import java.io.File;
import java.util.*;
public class MediaAnalyzer {
private final FFmpegExecutor executor;
private final ObjectMapper objectMapper;
public MediaAnalyzer() {
this.executor = new FFmpegExecutor();
this.objectMapper = new ObjectMapper();
}
public MediaAnalyzer(FFmpegExecutor executor) {
this.executor = executor;
this.objectMapper = new ObjectMapper();
}
public MediaInfo getMediaInfo(String filePath) throws Exception {
List<String> command = Arrays.asList(
"-v", "quiet",
"-print_format", "json",
"-show_format",
"-show_streams",
filePath
);
FFmpegExecutor.FFmpegResult result = executor.execute(command);
if (!result.isSuccess()) {
throw new Exception("Failed to get media info: " + result.getOutput());
}
JsonNode root = objectMapper.readTree(result.getOutput());
return parseMediaInfo(root, filePath);
}
private MediaInfo parseMediaInfo(JsonNode root, String filePath) {
MediaInfo info = new MediaInfo();
info.setFilePath(filePath);
// Parse format information
JsonNode format = root.get("format");
if (format != null) {
info.setFormatName(format.get("format_name").asText());
info.setFormatLongName(format.get("format_long_name").asText());
info.setDuration(format.get("duration").asDouble());
info.setSize(format.get("size").asLong());
info.setBitrate(format.get("bit_rate").asLong());
// Parse tags
JsonNode tags = format.get("tags");
if (tags != null) {
Map<String, String> tagMap = new HashMap<>();
tags.fields().forEachRemaining(entry -> 
tagMap.put(entry.getKey(), entry.getValue().asText()));
info.setFormatTags(tagMap);
}
}
// Parse streams
JsonNode streams = root.get("streams");
if (streams != null && streams.isArray()) {
List<StreamInfo> streamList = new ArrayList<>();
for (JsonNode stream : streams) {
streamList.add(parseStreamInfo(stream));
}
info.setStreams(streamList);
}
return info;
}
private StreamInfo parseStreamInfo(JsonNode stream) {
StreamInfo streamInfo = new StreamInfo();
streamInfo.setIndex(stream.get("index").asInt());
streamInfo.setCodecName(stream.get("codec_name").asText());
streamInfo.setCodecLongName(stream.get("codec_long_name").asText());
streamInfo.setCodecType(stream.get("codec_type").asText());
streamInfo.setCodecTagString(stream.get("codec_tag_string").asText());
// Common fields
if (stream.has("width")) streamInfo.setWidth(stream.get("width").asInt());
if (stream.has("height")) streamInfo.setHeight(stream.get("height").asInt());
if (stream.has("sample_rate")) streamInfo.setSampleRate(stream.get("sample_rate").asInt());
if (stream.has("channels")) streamInfo.setChannels(stream.get("channels").asInt());
if (stream.has("sample_fmt")) streamInfo.setSampleFormat(stream.get("sample_fmt").asText());
if (stream.has("pix_fmt")) streamInfo.setPixelFormat(stream.get("pix_fmt").asText());
if (stream.has("bit_rate")) streamInfo.setBitrate(stream.get("bit_rate").asLong());
if (stream.has("duration")) streamInfo.setDuration(stream.get("duration").asDouble());
// Parse tags
JsonNode tags = stream.get("tags");
if (tags != null) {
Map<String, String> tagMap = new HashMap<>();
tags.fields().forEachRemaining(entry -> 
tagMap.put(entry.getKey(), entry.getValue().asText()));
streamInfo.setTags(tagMap);
}
return streamInfo;
}
public boolean isValidMediaFile(String filePath) {
try {
MediaInfo info = getMediaInfo(filePath);
return info != null && !info.getStreams().isEmpty();
} catch (Exception e) {
return false;
}
}
public static class MediaInfo {
private String filePath;
private String formatName;
private String formatLongName;
private double duration;
private long size;
private long bitrate;
private Map<String, String> formatTags;
private List<StreamInfo> streams;
// Getters and setters
public String getFilePath() { return filePath; }
public void setFilePath(String filePath) { this.filePath = filePath; }
public String getFormatName() { return formatName; }
public void setFormatName(String formatName) { this.formatName = formatName; }
public String getFormatLongName() { return formatLongName; }
public void setFormatLongName(String formatLongName) { this.formatLongName = formatLongName; }
public double getDuration() { return duration; }
public void setDuration(double duration) { this.duration = duration; }
public long getSize() { return size; }
public void setSize(long size) { this.size = size; }
public long getBitrate() { return bitrate; }
public void setBitrate(long bitrate) { this.bitrate = bitrate; }
public Map<String, String> getFormatTags() { return formatTags; }
public void setFormatTags(Map<String, String> formatTags) { this.formatTags = formatTags; }
public List<StreamInfo> getStreams() { return streams; }
public void setStreams(List<StreamInfo> streams) { this.streams = streams; }
public Optional<StreamInfo> getFirstVideoStream() {
return streams.stream()
.filter(s -> "video".equals(s.getCodecType()))
.findFirst();
}
public Optional<StreamInfo> getFirstAudioStream() {
return streams.stream()
.filter(s -> "audio".equals(s.getCodecType()))
.findFirst();
}
public List<StreamInfo> getVideoStreams() {
return streams.stream()
.filter(s -> "video".equals(s.getCodecType()))
.toList();
}
public List<StreamInfo> getAudioStreams() {
return streams.stream()
.filter(s -> "audio".equals(s.getCodecType()))
.toList();
}
}
public static class StreamInfo {
private int index;
private String codecName;
private String codecLongName;
private String codecType;
private String codecTagString;
private Integer width;
private Integer height;
private Integer sampleRate;
private Integer channels;
private String sampleFormat;
private String pixelFormat;
private Long bitrate;
private Double duration;
private Map<String, String> tags;
// Getters and setters
public int getIndex() { return index; }
public void setIndex(int index) { this.index = index; }
public String getCodecName() { return codecName; }
public void setCodecName(String codecName) { this.codecName = codecName; }
public String getCodecLongName() { return codecLongName; }
public void setCodecLongName(String codecLongName) { this.codecLongName = codecLongName; }
public String getCodecType() { return codecType; }
public void setCodecType(String codecType) { this.codecType = codecType; }
public String getCodecTagString() { return codecTagString; }
public void setCodecTagString(String codecTagString) { this.codecTagString = codecTagString; }
public Integer getWidth() { return width; }
public void setWidth(Integer width) { this.width = width; }
public Integer getHeight() { return height; }
public void setHeight(Integer height) { this.height = height; }
public Integer getSampleRate() { return sampleRate; }
public void setSampleRate(Integer sampleRate) { this.sampleRate = sampleRate; }
public Integer getChannels() { return channels; }
public void setChannels(Integer channels) { this.channels = channels; }
public String getSampleFormat() { return sampleFormat; }
public void setSampleFormat(String sampleFormat) { this.sampleFormat = sampleFormat; }
public String getPixelFormat() { return pixelFormat; }
public void setPixelFormat(String pixelFormat) { this.pixelFormat = pixelFormat; }
public Long getBitrate() { return bitrate; }
public void setBitrate(Long bitrate) { this.bitrate = bitrate; }
public Double getDuration() { return duration; }
public void setDuration(Double duration) { this.duration = duration; }
public Map<String, String> getTags() { return tags; }
public void setTags(Map<String, String> tags) { this.tags = tags; }
public String getResolution() {
if (width != null && height != null) {
return width + "x" + height;
}
return null;
}
public boolean isVideo() {
return "video".equals(codecType);
}
public boolean isAudio() {
return "audio".equals(codecType);
}
}
}

Common Media Operations

Video Operations

package com.ffmpeg.operations;
import com.ffmpeg.core.FFmpegCommandBuilder;
import com.ffmpeg.core.FFmpegExecutor;
import java.io.File;
import java.util.concurrent.CompletableFuture;
public class VideoOperations {
private final FFmpegExecutor executor;
public VideoOperations() {
this.executor = new FFmpegExecutor();
}
public VideoOperations(FFmpegExecutor executor) {
this.executor = executor;
}
public void convertVideo(String inputPath, String outputPath, String videoCodec, String audioCodec) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.videoCodec(videoCodec)
.audioCodec(audioCodec)
.output(outputPath);
executor.execute(builder);
}
public void extractFrame(String videoPath, String outputImagePath, String time) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(videoPath)
.seek(time)
.frames(1)
.output(outputImagePath);
executor.execute(builder);
}
public void createThumbnail(String videoPath, String outputImagePath, int width, int height) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(videoPath)
.seek("00:00:01")
.frames(1)
.videoFilter("scale=" + width + ":" + height)
.output(outputImagePath);
executor.execute(builder);
}
public void resizeVideo(String inputPath, String outputPath, int width, int height) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.videoFilter("scale=" + width + ":" + height)
.videoCodec("libx264")
.preset("medium")
.crf(23)
.output(outputPath);
executor.execute(builder);
}
public void changeFrameRate(String inputPath, String outputPath, double fps) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.frameRate(fps)
.videoCodec("libx264")
.output(outputPath);
executor.execute(builder);
}
public void trimVideo(String inputPath, String outputPath, String startTime, String duration) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.seek(startTime)
.duration(duration)
.videoCodec("copy")
.audioCodec("copy")
.output(outputPath);
executor.execute(builder);
}
public void mergeVideos(List<String> inputPaths, String outputPath) throws Exception {
// First create a file list
File listFile = File.createTempFile("ffmpeg_list", ".txt");
try (var writer = new java.io.FileWriter(listFile)) {
for (String path : inputPaths) {
writer.write("file '" + path + "'\n");
}
}
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.customOption("-f", "concat")
.customOption("-safe", "0")
.input(listFile.getAbsolutePath())
.videoCodec("copy")
.audioCodec("copy")
.output(outputPath);
executor.execute(builder);
// Clean up
listFile.delete();
}
public void addWatermark(String inputPath, String outputPath, String watermarkImagePath, 
String position) throws Exception {
String filter = "overlay=" + position; // e.g., "10:10" or "main_w-overlay_w-10:10"
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.input(watermarkImagePath)
.complexFilter("[0:v][1:v] " + filter + " [v]")
.map("[v]")
.map("0:a")
.output(outputPath);
executor.execute(builder);
}
public void extractAudio(String videoPath, String outputAudioPath) throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(videoPath)
.disableVideo()
.audioCodec("copy")
.output(outputAudioPath);
executor.execute(builder);
}
public CompletableFuture<Void> convertVideoAsync(String inputPath, String outputPath, 
String videoCodec, String audioCodec) {
return executor.executeAsync(new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.videoCodec(videoCodec)
.audioCodec(audioCodec)
.output(outputPath)
.build())
.thenAccept(result -> {
if (!result.isSuccess()) {
throw new RuntimeException("Conversion failed: " + result.getOutput());
}
});
}
public void compressVideo(String inputPath, String outputPath, String preset, int crf) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.videoCodec("libx264")
.preset(preset)
.crf(crf)
.audioCodec("aac")
.output(outputPath);
executor.execute(builder);
}
}

Audio Operations

package com.ffmpeg.operations;
import com.ffmpeg.core.FFmpegCommandBuilder;
import com.ffmpeg.core.FFmpegExecutor;
import java.io.File;
import java.util.concurrent.CompletableFuture;
public class AudioOperations {
private final FFmpegExecutor executor;
public AudioOperations() {
this.executor = new FFmpegExecutor();
}
public AudioOperations(FFmpegExecutor executor) {
this.executor = executor;
}
public void convertAudio(String inputPath, String outputPath, String audioCodec, 
Integer bitrate, Integer sampleRate, Integer channels) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.audioCodec(audioCodec)
.disableVideo();
if (bitrate != null) {
builder.audioBitrate(bitrate + "k");
}
if (sampleRate != null) {
builder.audioSampleRate(sampleRate);
}
if (channels != null) {
builder.audioChannels(channels);
}
builder.output(outputPath);
executor.execute(builder);
}
public void extractAudioSegment(String inputPath, String outputPath, 
String startTime, String duration) throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.seek(startTime)
.duration(duration)
.disableVideo()
.audioCodec("copy")
.output(outputPath);
executor.execute(builder);
}
public void normalizeAudio(String inputPath, String outputPath) throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.disableVideo()
.audioFilter("loudnorm")
.audioCodec("aac")
.output(outputPath);
executor.execute(builder);
}
public void changeAudioVolume(String inputPath, String outputPath, double volumeMultiplier) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.disableVideo()
.audioFilter("volume=" + volumeMultiplier)
.audioCodec("aac")
.output(outputPath);
executor.execute(builder);
}
public void mergeAudioFiles(List<String> inputPaths, String outputPath) throws Exception {
if (inputPaths.size() < 2) {
throw new IllegalArgumentException("At least two input files required");
}
// Create filter complex for concatenation
StringBuilder filter = new StringBuilder();
for (int i = 0; i < inputPaths.size(); i++) {
filter.append("[").append(i).append(":a]");
}
filter.append("concat=n=").append(inputPaths.size())
.append(":v=0:a=1[aout]");
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner();
for (String path : inputPaths) {
builder.input(path);
}
builder.complexFilter(filter.toString())
.map("[aout]")
.audioCodec("aac")
.output(outputPath);
executor.execute(builder);
}
public void convertToMp3(String inputPath, String outputPath, Integer bitrate) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.disableVideo()
.audioCodec("libmp3lame");
if (bitrate != null) {
builder.audioBitrate(bitrate + "k");
}
builder.output(outputPath);
executor.execute(builder);
}
public CompletableFuture<Void> convertAudioAsync(String inputPath, String outputPath, 
String audioCodec, Integer bitrate) {
return executor.executeAsync(new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.disableVideo()
.audioCodec(audioCodec)
.audioBitrate(bitrate != null ? bitrate + "k" : "128k")
.output(outputPath)
.build())
.thenAccept(result -> {
if (!result.isSuccess()) {
throw new RuntimeException("Audio conversion failed: " + result.getOutput());
}
});
}
public void extractAudioMetadata(String inputPath, String outputPath) throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.disableVideo()
.audioCodec("copy")
.customOption("-map_metadata", "0")
.output(outputPath);
executor.execute(builder);
}
}

Advanced Features

Progress Monitoring

package com.ffmpeg.monitoring;
import com.ffmpeg.core.FFmpegExecutor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ProgressMonitor {
private static final Pattern PROGRESS_PATTERN = 
Pattern.compile("time=(\\d{2}:\\d{2}:\\d{2}\\.\\d{2})");
private static final Pattern DURATION_PATTERN = 
Pattern.compile("Duration: (\\d{2}:\\d{2}:\\d{2}\\.\\d{2})");
public static void executeWithProgress(FFmpegExecutor executor, 
java.util.List<String> command,
Consumer<ProgressUpdate> progressCallback) 
throws Exception {
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();
AtomicReference<String> durationStr = new AtomicReference<>();
// Read stderr for progress information
Thread progressThread = new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
// Extract duration
if (durationStr.get() == null) {
Matcher durationMatcher = DURATION_PATTERN.matcher(line);
if (durationMatcher.find()) {
durationStr.set(durationMatcher.group(1));
}
}
// Extract current time
Matcher progressMatcher = PROGRESS_PATTERN.matcher(line);
if (progressMatcher.find()) {
String currentTime = progressMatcher.group(1);
ProgressUpdate update = new ProgressUpdate(
currentTime, durationStr.get(), line);
progressCallback.accept(update);
}
}
} catch (IOException e) {
// Stream closed, process finished
}
});
progressThread.start();
// Wait for process completion
int exitCode = process.waitFor();
progressThread.join();
if (exitCode != 0) {
throw new Exception("FFmpeg process failed with exit code: " + exitCode);
}
}
public static class ProgressUpdate {
private final String currentTime;
private final String totalDuration;
private final String rawLine;
private final long timestamp;
public ProgressUpdate(String currentTime, String totalDuration, String rawLine) {
this.currentTime = currentTime;
this.totalDuration = totalDuration;
this.rawLine = rawLine;
this.timestamp = System.currentTimeMillis();
}
public double getProgressPercentage() {
if (currentTime == null || totalDuration == null) {
return 0.0;
}
try {
long currentSeconds = timeStringToSeconds(currentTime);
long totalSeconds = timeStringToSeconds(totalDuration);
if (totalSeconds > 0) {
return (double) currentSeconds / totalSeconds * 100.0;
}
} catch (Exception e) {
// Ignore parsing errors
}
return 0.0;
}
public String getCurrentTime() { return currentTime; }
public String getTotalDuration() { return totalDuration; }
public String getRawLine() { return rawLine; }
public long getTimestamp() { return timestamp; }
private long timeStringToSeconds(String timeStr) {
String[] parts = timeStr.split("[:.]");
long hours = Long.parseLong(parts[0]);
long minutes = Long.parseLong(parts[1]);
long seconds = Long.parseLong(parts[2]);
return hours * 3600 + minutes * 60 + seconds;
}
}
}

GUI Application

FFmpeg GUI Controller

package com.ffmpeg.gui;
import com.ffmpeg.analysis.MediaAnalyzer;
import com.ffmpeg.core.FFmpegCommandBuilder;
import com.ffmpeg.core.FFmpegExecutor;
import com.ffmpeg.monitoring.ProgressMonitor;
import com.ffmpeg.operations.VideoOperations;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.stage.FileChooser;
import javafx.stage.DirectoryChooser;
import java.io.File;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class FFmpegController {
@FXML private TextField inputFilePath;
@FXML private TextField outputFilePath;
@FXML private ComboBox<String> videoCodecCombo;
@FXML private ComboBox<String> audioCodecCombo;
@FXML private Slider qualitySlider;
@FXML private ProgressBar progressBar;
@FXML private TextArea logArea;
@FXML private Button convertButton;
@FXML private Button cancelButton;
private FFmpegExecutor executor;
private VideoOperations videoOps;
private MediaAnalyzer mediaAnalyzer;
private Process currentProcess;
@FXML
public void initialize() {
executor = new FFmpegExecutor();
videoOps = new VideoOperations(executor);
mediaAnalyzer = new MediaAnalyzer(executor);
// Initialize codec combos
videoCodecCombo.getItems().addAll("libx264", "libx265", "copy", "libvpx-vp9");
audioCodecCombo.getItems().addAll("aac", "libmp3lame", "copy", "libopus");
videoCodecCombo.setValue("libx264");
audioCodecCombo.setValue("aac");
qualitySlider.setValue(23); // Default CRF value
}
@FXML
private void browseInputFile() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select Media File");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("Media Files", 
"*.mp4", "*.avi", "*.mkv", "*.mov", "*.wmv", "*.flv", "*.webm",
"*.mp3", "*.wav", "*.aac", "*.flac", "*.ogg"),
new FileChooser.ExtensionFilter("All Files", "*.*")
);
File file = fileChooser.showOpenDialog(null);
if (file != null) {
inputFilePath.setText(file.getAbsolutePath());
autoGenerateOutputPath(file);
analyzeMediaFile(file);
}
}
@FXML
private void browseOutputFile() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save Output File");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("MP4", "*.mp4"),
new FileChooser.ExtensionFilter("AVI", "*.avi"),
new FileChooser.ExtensionFilter("MKV", "*.mkv"),
new FileChooser.ExtensionFilter("WebM", "*.webm"),
new FileChooser.ExtensionFilter("MP3", "*.mp3")
);
File file = fileChooser.showSaveDialog(null);
if (file != null) {
outputFilePath.setText(file.getAbsolutePath());
}
}
@FXML
private void browseOutputDirectory() {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Select Output Directory");
File directory = directoryChooser.showDialog(null);
if (directory != null && inputFilePath.getText() != null && !inputFilePath.getText().isEmpty()) {
File inputFile = new File(inputFilePath.getText());
String outputPath = directory.getAbsolutePath() + File.separator + 
getOutputFileName(inputFile);
outputFilePath.setText(outputPath);
}
}
@FXML
private void convertMedia() {
String inputPath = inputFilePath.getText();
String outputPath = outputFilePath.getText();
String videoCodec = videoCodecCombo.getValue();
String audioCodec = audioCodecCombo.getValue();
int crf = (int) qualitySlider.getValue();
if (inputPath.isEmpty() || outputPath.isEmpty()) {
showAlert("Error", "Please select input and output files.");
return;
}
convertButton.setDisable(true);
cancelButton.setDisable(false);
progressBar.setProgress(0);
CompletableFuture.runAsync(() -> {
try {
log("Starting conversion...");
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.videoCodec(videoCodec)
.audioCodec(audioCodec)
.crf(crf)
.output(outputPath);
ProgressMonitor.executeWithProgress(executor, builder.build(), update -> {
Platform.runLater(() -> {
double progress = update.getProgressPercentage() / 100.0;
progressBar.setProgress(progress);
log("Progress: " + String.format("%.1f%%", update.getProgressPercentage()));
});
});
Platform.runLater(() -> {
log("Conversion completed successfully!");
progressBar.setProgress(1.0);
convertButton.setDisable(false);
cancelButton.setDisable(true);
});
} catch (Exception e) {
Platform.runLater(() -> {
log("Conversion failed: " + e.getMessage());
showAlert("Conversion Error", e.getMessage());
convertButton.setDisable(false);
cancelButton.setDisable(true);
progressBar.setProgress(0);
});
}
});
}
@FXML
private void cancelConversion() {
if (currentProcess != null) {
currentProcess.destroy();
log("Conversion cancelled by user.");
}
convertButton.setDisable(false);
cancelButton.setDisable(true);
progressBar.setProgress(0);
}
@FXML
private void extractThumbnail() {
String inputPath = inputFilePath.getText();
if (inputPath.isEmpty()) {
showAlert("Error", "Please select an input file.");
return;
}
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save Thumbnail");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("JPEG", "*.jpg"),
new FileChoorer.ExtensionFilter("PNG", "*.png")
);
File outputFile = fileChooser.showSaveDialog(null);
if (outputFile != null) {
try {
videoOps.createThumbnail(inputPath, outputFile.getAbsolutePath(), 320, 240);
log("Thumbnail extracted: " + outputFile.getName());
} catch (Exception e) {
log("Thumbnail extraction failed: " + e.getMessage());
showAlert("Error", "Failed to extract thumbnail: " + e.getMessage());
}
}
}
private void autoGenerateOutputPath(File inputFile) {
String inputPath = inputFile.getAbsolutePath();
String outputPath = inputPath.substring(0, inputPath.lastIndexOf('.')) + "_converted.mp4";
outputFilePath.setText(outputPath);
}
private String getOutputFileName(File inputFile) {
String name = inputFile.getName();
int dotIndex = name.lastIndexOf('.');
if (dotIndex > 0) {
name = name.substring(0, dotIndex);
}
return name + "_converted.mp4";
}
private void analyzeMediaFile(File file) {
try {
MediaAnalyzer.MediaInfo info = mediaAnalyzer.getMediaInfo(file.getAbsolutePath());
log("Media analysis:");
log("  Format: " + info.getFormatName());
log("  Duration: " + String.format("%.2f seconds", info.getDuration()));
log("  Size: " + String.format("%.2f MB", info.getSize() / (1024.0 * 1024.0)));
info.getFirstVideoStream().ifPresent(video -> {
log("  Video: " + video.getCodecName() + " " + video.getResolution());
});
info.getFirstAudioStream().ifPresent(audio -> {
log("  Audio: " + audio.getCodecName() + " " + audio.getSampleRate() + "Hz");
});
} catch (Exception e) {
log("Media analysis failed: " + e.getMessage());
}
}
private void log(String message) {
Platform.runLater(() -> {
logArea.appendText(message + "\n");
});
}
private void showAlert(String title, String message) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.showAndWait();
});
}
public void shutdown() {
if (executor != null) {
executor.shutdown();
}
}
}

Usage Examples

Command Line Interface

package com.ffmpeg.demo;
import com.ffmpeg.analysis.MediaAnalyzer;
import com.ffmpeg.core.FFmpegCommandBuilder;
import com.ffmpeg.core.FFmpegExecutor;
import com.ffmpeg.operations.VideoOperations;
public class FFmpegDemo {
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: java FFmpegDemo <input> <output> [operation]");
System.out.println("Operations: convert, resize, thumbnail, info");
return;
}
String inputPath = args[0];
String outputPath = args[1];
String operation = args.length > 2 ? args[2] : "convert";
try {
FFmpegExecutor executor = new FFmpegExecutor();
switch (operation) {
case "convert":
convertVideo(executor, inputPath, outputPath);
break;
case "resize":
resizeVideo(executor, inputPath, outputPath);
break;
case "thumbnail":
createThumbnail(executor, inputPath, outputPath);
break;
case "info":
showMediaInfo(inputPath);
break;
default:
System.out.println("Unknown operation: " + operation);
}
executor.shutdown();
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
private static void convertVideo(FFmpegExecutor executor, String inputPath, String outputPath) 
throws Exception {
FFmpegCommandBuilder builder = new FFmpegCommandBuilder()
.overwriteOutput()
.hideBanner()
.input(inputPath)
.videoCodec("libx264")
.audioCodec("aac")
.crf(23)
.preset("medium")
.output(outputPath);
System.out.println("Converting video...");
executor.execute(builder);
System.out.println("Conversion completed: " + outputPath);
}
private static void resizeVideo(FFmpegExecutor executor, String inputPath, String outputPath) 
throws Exception {
VideoOperations videoOps = new VideoOperations(executor);
videoOps.resizeVideo(inputPath, outputPath, 1280, 720);
System.out.println("Video resized: " + outputPath);
}
private static void createThumbnail(FFmpegExecutor executor, String inputPath, String outputPath) 
throws Exception {
VideoOperations videoOps = new VideoOperations(executor);
videoOps.createThumbnail(inputPath, outputPath, 320, 240);
System.out.println("Thumbnail created: " + outputPath);
}
private static void showMediaInfo(String inputPath) throws Exception {
MediaAnalyzer analyzer = new MediaAnalyzer();
MediaAnalyzer.MediaInfo info = analyzer.getMediaInfo(inputPath);
System.out.println("Media Information:");
System.out.println("  File: " + info.getFilePath());
System.out.println("  Format: " + info.getFormatName());
System.out.println("  Duration: " + String.format("%.2f seconds", info.getDuration()));
System.out.println("  Size: " + String.format("%.2f MB", info.getSize() / (1024.0 * 1024.0)));
info.getFirstVideoStream().ifPresent(video -> {
System.out.println("  Video: " + video.getCodecName() + " " + video.getResolution());
});
info.getFirstAudioStream().ifPresent(audio -> {
System.out.println("  Audio: " + audio.getCodecName() + " " + 
audio.getSampleRate() + "Hz " + audio.getChannels() + " channels");
});
}
}

Summary

This comprehensive FFmpeg wrapper for Java provides:

  1. Command Building: Fluent API for constructing FFmpeg commands
  2. Process Execution: Robust process management with timeout handling
  3. Media Analysis: Detailed media file information extraction
  4. Common Operations: Video conversion, resizing, thumbnail generation, audio processing
  5. Progress Monitoring: Real-time progress tracking for long operations
  6. GUI Integration: JavaFX-based user interface
  7. Error Handling: Comprehensive exception handling and logging
  8. Async Support: Non-blocking operations for responsive applications

The wrapper abstracts FFmpeg's complexity while providing full access to its powerful multimedia processing capabilities, making it suitable for both simple conversions and complex multimedia applications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper