Extracting Video Frames and Handling Rotation with JavaCV

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

Tags: JavaCV FFmpegFrameGrabber Video Processing java Image Rotation

Posted on Mon, 01 Jun 2026 17:26:16 +0000 by ohdang888