Processing videos in Java: transcode, resize, and watermark

In today's digital landscape, video content is crucial. Whether you're developing a media application or managing a video platform, efficiently processing videos is essential. This guide walks you through transcoding, resizing, and watermarking videos in Java using the open source library JavaCV, which provides Java wrappers for FFmpeg.
Prerequisites
Before we begin, ensure you have the following installed:
- Java Development Kit (JDK): Version 11 or higher
- Maven: For managing project dependencies
- FFmpeg: Installed and accessible in your system's PATH
For Ubuntu/Linux systems, install the required packages:
sudo apt-get update
sudo apt-get install ffmpeg build-essential
Add the following dependency to your pom.xml
to use JavaCV:
<dependencies>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.11</version>
</dependency>
</dependencies>
Transcoding videos in Java
Below is an example of how to transcode a video from one format to another using JavaCV. The code emphasizes robust error handling and resource management.
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
public class VideoTranscoder {
public static void transcode(String inputFile, String outputFile) throws Exception {
FFmpegFrameGrabber grabber = null;
FFmpegFrameRecorder recorder = null;
try {
grabber = new FFmpegFrameGrabber(inputFile);
grabber.start();
recorder = new FFmpegFrameRecorder(outputFile, grabber.getImageWidth(), grabber.getImageHeight());
recorder.setVideoCodec(grabber.getVideoCodec());
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setPixelFormat(grabber.getPixelFormat());
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioCodec(grabber.getAudioCodec());
recorder.setSampleRate(grabber.getSampleRate());
}
recorder.start();
Frame frame;
while ((frame = grabber.grab()) != null) {
recorder.record(frame);
}
} catch (FFmpegFrameGrabber.Exception e) {
if (e.getMessage().contains("No such file")) {
throw new IllegalArgumentException("Video file not found", e);
} else if (e.getMessage().contains("codec not found")) {
throw new UnsupportedOperationException("Video codec not supported", e);
}
throw e;
} finally {
if (recorder != null) {
try {
recorder.stop();
} catch (FFmpegFrameRecorder.Exception e) {
// Log error but continue cleanup
} finally {
recorder.release();
}
}
if (grabber != null) {
try {
grabber.release();
} catch (FFmpegFrameGrabber.Exception e) {
// Log error but continue cleanup
}
}
}
}
}
Resizing videos using javacv
To resize videos, process each frame and adjust its dimensions. This example uses OpenCV with JavaCV to resize video frames while ensuring resources are properly released.
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Size;
import static org.bytedeco.opencv.global.opencv_imgproc.resize;
public class VideoResizer {
public static void resizeVideo(String inputFile, String outputFile, int width, int height) throws Exception {
FFmpegFrameGrabber grabber = null;
FFmpegFrameRecorder recorder = null;
OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
try {
grabber = new FFmpegFrameGrabber(inputFile);
grabber.start();
recorder = new FFmpegFrameRecorder(outputFile, width, height);
recorder.setVideoCodec(grabber.getVideoCodec());
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setVideoBitrate(grabber.getVideoBitrate());
recorder.setPixelFormat(grabber.getPixelFormat());
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioCodec(grabber.getAudioCodec());
recorder.setSampleRate(grabber.getSampleRate());
}
recorder.start();
Frame frame;
while ((frame = grabber.grabImage()) != null) {
Mat mat = converter.convert(frame);
Mat resizedMat = new Mat();
resize(mat, resizedMat, new Size(width, height));
Frame resizedFrame = converter.convert(resizedMat);
recorder.record(resizedFrame);
mat.release();
resizedMat.release();
}
} finally {
if (recorder != null) {
try {
recorder.stop();
} catch (FFmpegFrameRecorder.Exception e) {
// Log error but continue cleanup
} finally {
recorder.release();
}
}
if (grabber != null) {
try {
grabber.release();
} catch (FFmpegFrameGrabber.Exception e) {
// Log error but continue cleanup
}
}
}
}
}
Adding watermarks to videos
Overlaying a text watermark onto each frame can be achieved by drawing with OpenCV's functions. The example below demonstrates how to add a simple text watermark to a video using JavaCV.
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
public class VideoWatermarker {
public static void addWatermark(String inputFile, String outputFile, String watermarkText) throws Exception {
FFmpegFrameGrabber grabber = null;
FFmpegFrameRecorder recorder = null;
OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
try {
grabber = new FFmpegFrameGrabber(inputFile);
grabber.start();
recorder = new FFmpegFrameRecorder(outputFile, grabber.getImageWidth(), grabber.getImageHeight());
recorder.setVideoCodec(grabber.getVideoCodec());
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setPixelFormat(grabber.getPixelFormat());
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioCodec(grabber.getAudioCodec());
recorder.setSampleRate(grabber.getSampleRate());
}
recorder.start();
Scalar color = new Scalar(255, 255, 255, 0);
int font = FONT_HERSHEY_SIMPLEX;
Frame frame;
while ((frame = grabber.grab()) != null) {
if (frame.image != null) {
Mat mat = converter.convert(frame);
putText(mat, watermarkText, new Point(50, 50), font, 1.0, color, 2, LINE_AA, false);
Frame watermarkedFrame = converter.convert(mat);
recorder.record(watermarkedFrame);
mat.release();
} else {
recorder.record(frame);
}
}
} finally {
if (recorder != null) {
try {
recorder.stop();
} catch (FFmpegFrameRecorder.Exception e) {
// Log error but continue cleanup
} finally {
recorder.release();
}
}
if (grabber != null) {
try {
grabber.release();
} catch (FFmpegFrameGrabber.Exception e) {
// Log error but continue cleanup
}
}
}
}
}
Memory management and performance optimization
Efficient video processing requires both optimized performance and effective memory management. In the example below, we use Java's ExecutorService to process multiple videos concurrently, taking advantage of all available processor cores.
import java.util.*;
import java.util.concurrent.*;
public class VideoProcessingService {
private static final int NUM_THREADS = Runtime.getRuntime().availableProcessors();
private final ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
public void processVideosInParallel(List<String> videoFiles) {
List<Future<?>> futures = new ArrayList<>();
for (String file : videoFiles) {
futures.add(executor.submit(() -> {
try {
processVideo(file);
} catch (Exception e) {
throw new RuntimeException("Failed to process video: " + file, e);
}
}));
}
// Wait for each task to complete
for (Future<?> future : futures) {
try {
future.get();
} catch (Exception e) {
// Handle or log errors as needed
}
}
}
private void processVideo(String file) {
// Implement your video processing logic here
}
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
Testing video processing
Implement tests to verify your video processing code functions as expected. The following JUnit tests check that a video is transcoded without errors and that proper exceptions are thrown for invalid input files.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.io.File;
public class VideoProcessingTest {
@Test
public void testVideoTranscoding() {
File input = new File("test.mp4");
File output = new File("output.mp4");
assertDoesNotThrow(() -> {
VideoTranscoder.transcode(input.getPath(), output.getPath());
assertTrue(output.exists());
assertTrue(output.length() > 0);
});
}
@Test
public void testInvalidVideoFile() {
assertThrows(IllegalArgumentException.class, () -> {
VideoTranscoder.transcode("nonexistent.mp4", "output.mp4");
});
}
}
Conclusion
Processing videos in Java with JavaCV provides powerful capabilities for transcoding, resizing, and watermarking. The examples above demonstrate essential techniques such as proper resource management, error handling, and performance optimization. For high-volume or enterprise-scale video processing, consider exploring Transloadit's Video Encoding service, which offers scalable and efficient solutions.