JavaCV simplifies processing mulitmedia files in Java, such as generating thumbnails for uploaded videos. A common challenge is that mobile-recorded videos often contain metadata indicating rotation (e.g., 90°, 180°, 270°), which must be corrected to display the image correctly.
Extracting a Specific Frame
The following example captures the 20th frame from a local video file and corrects its orientation based on metadata.
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.Frame;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class VideoFrameExtractor {
public static void main(String[] args) throws IOException {
int targetFramePosition = 20;
FFmpegFrameGrabber mediaGrabber = new FFmpegFrameGrabber("C:/media/sample.mp4");
mediaGrabber.start();
Frame videoFrame = null;
for (int i = 0; i < targetFramePosition; i++) {
videoFrame = mediaGrabber.grabImage();
if (videoFrame == null) break;
}
String rotationMeta = mediaGrabber.getVideoMetadata("rotate");
Java2DFrameConverter imageConverter = new Java2DFrameConverter();
BufferedImage capturedImage = imageConverter.convert(videoFrame);
if (rotationMeta != null) {
capturedImage = applyRotation(capturedImage, Integer.parseInt(rotationMeta));
}
String outputPath = "C:/media/thumb_" + System.currentTimeMillis() + ".jpg";
ImageIO.write(capturedImage, "jpg", new File(outputPath));
mediaGrabber.stop();
}
private static BufferedImage applyRotation(BufferedImage source, int angle) {
int w = source.getWidth();
int h = source.getHeight();
int imageType = source.getColorModel().getTransparency();
Rectangle boundingBox = calculateNewBounds(new Rectangle(w, h), angle);
BufferedImage rotatedImg = new BufferedImage(boundingBox.width, boundingBox.height, imageType);
Graphics2D canvas = rotatedImg.createGraphics();
canvas.translate((boundingBox.width - w) / 2, (boundingBox.height - h) / 2);
canvas.rotate(Math.toRadians(angle), w / 2.0, h / 2.0);
canvas.drawImage(source, 0, 0, null);
canvas.dispose();
return rotatedImg;
}
private static Rectangle calculateNewBounds(Rectangle original, int angleDegrees) {
if (angleDegrees >= 90) {
if ((angleDegrees / 90) % 2 == 1) {
int temp = original.height;
original.height = original.width;
original.width = temp;
}
angleDegrees %= 90;
}
double diagonal = Math.sqrt(original.width * original.width + original.height * original.height) / 2.0;
double sideLength = 2 * Math.sin(Math.toRadians(angleDegrees) / 2.0) * diagonal;
double alpha = (Math.PI - Math.toRadians(angleDegrees)) / 2.0;
double deltaW = Math.atan((double) original.height / original.width);
double deltaH = Math.atan((double) original.width / original.height);
int extraW = (int) (sideLength * Math.cos(Math.PI - alpha - deltaW));
int extraH = (int) (sideLength * Math.cos(Math.PI - alpha - deltaH));
return new Rectangle(original.width + extraW * 2, original.height + extraH * 2);
}
}
Web Integration: Video Upload with Cover Generation
In a web environment (e.g., Spring MVC), you can process uploaded videos to genearte a cover image from the first frame immediately after saving the file.
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.imageio.ImageIO;
@Controller
@RequestMapping("/media")
public class MediaUploadController {
@PostMapping(value = "/uploadVideo", produces = "application/json;charset=UTF-8")
@ResponseBody
public String handleVideoUpload(@RequestParam("file") MultipartFile multipartFile, HttpServletRequest request) {
try {
if (multipartFile.isEmpty()) return "{\"error\":\"No file\"}";
String baseName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String originalName = multipartFile.getOriginalFilename();
String extension = originalName.substring(originalName.lastIndexOf("."));
String uploadDir = request.getServletContext().getRealPath("/uploads/videos/") + baseName;
File dir = new File(uploadDir);
if (!dir.exists()) dir.mkdirs();
String videoFullPath = uploadDir + extension;
multipartFile.transferTo(new File(videoFullPath));
// Generate Cover
FFmpegFrameGrabber vidGrabber = new FFmpegFrameGrabber(videoFullPath);
vidGrabber.start();
Java2DFrameConverter cvt = new Java2DFrameConverter();
BufferedImage cover = cvt.convert(vidGrabber.grabImage());
String rotateInfo = vidGrabber.getVideoMetadata("rotate");
if (rotateInfo != null) {
cover = applyRotation(cover, Integer.parseInt(rotateInfo));
}
String coverPath = uploadDir + "_cover.jpg";
ImageIO.write(cover, "jpg", new File(coverPath));
vidGrabber.stop();
return "{\"cover\":\"" + coverPath + "\"}";
} catch (IOException e) {
return "{\"error\":\"Processing failed\"}";
}
}
// Rotation logic similar to the previous example
private BufferedImage applyRotation(BufferedImage source, int angle) {
int w = source.getWidth();
int h = source.getHeight();
int imageType = source.getColorModel().getTransparency();
Rectangle boundingBox = calculateNewBounds(new Rectangle(w, h), angle);
BufferedImage rotatedImg = new BufferedImage(boundingBox.width, boundingBox.height, imageType);
Graphics2D canvas = rotatedImg.createGraphics();
canvas.translate((boundingBox.width - w) / 2, (boundingBox.height - h) / 2);
canvas.rotate(Math.toRadians(angle), w / 2.0, h / 2.0);
canvas.drawImage(source, 0, 0, null);
canvas.dispose();
return rotatedImg;
}
private Rectangle calculateNewBounds(Rectangle original, int angleDegrees) {
if (angleDegrees >= 90) {
if ((angleDegrees / 90) % 2 == 1) {
int temp = original.height;
original.height = original.width;
original.width = temp;
}
angleDegrees %= 90;
}
double diagonal = Math.sqrt(original.width * original.width + original.height * original.height) / 2.0;
double sideLength = 2 * Math.sin(Math.toRadians(angleDegrees) / 2.0) * diagonal;
double alpha = (Math.PI - Math.toRadians(angleDegrees)) / 2.0;
double deltaW = Math.atan((double) original.height / original.width);
double deltaH = Math.atan((double) original.width / original.height);
int extraW = (int) (sideLength * Math.cos(Math.PI - alpha - deltaW));
int extraH = (int) (sideLength * Math.cos(Math.PI - alpha - deltaH));
return new Rectangle(original.width + extraW * 2, original.height + extraH * 2);
}
}