SpringBoot集成FFmpeg实现生成图片预览图与缩略图

 更新时间:2026年02月13日 09:40:55   作者:god_cvz  
这篇文章主要为大家详细介绍了SpringBoot如何集成FFmpeg实现生成图片预览图与缩略图,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下

本文介绍了SpringBoot集成FFmpeg生成图片预览图与缩略图功能的实现方案。首先配置Maven依赖,包含JavaCV核心库和FFmpeg等组件。核心类PicturePreviewProcessor实现了从URL获取图片、生成不同尺寸预览图(720×540)、缩略图(200×150)和WebP格式图片的功能。通过Java2DFrameConverter和FFmpegFrameRecorder实现图片格式转换,提供图片缩放计算方法getChangeSize()保持原图比例。建议使用线程池异步处理图片生成任务,并自行实现OSS文件上传逻

一、导入pom依赖

    <profiles>
        <profile>
            <id>local</id>
            <properties>
                <profiles.active>local</profiles.active>
                <!--根据环境进行切换-->
                <ffmpeg.classifier>windows-x86_64</ffmpeg.classifier>
<!--                <ffmpeg.classifier>linux-x86_64</ffmpeg.classifier>-->
            </properties>
        </profile>
    </profiles> 
        <!-- javacv+javacpp核心库-->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>${javacv.version}</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacpp-platform</artifactId>
            <version>${javacv.version}</version>
        </dependency>

        <!-- ffmpeg最小依赖包,必须包含上面的javacv+javacpp核心库 -->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>5.1.2-${javacv.version}</version>
            <classifier>${ffmpeg.classifier}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacpp</artifactId>
            <version>${javacv.version}</version>
            <classifier>${ffmpeg.classifier}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>openblas</artifactId>
            <version>0.3.21-${javacv.version}</version>
            <classifier>${ffmpeg.classifier}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>opencv</artifactId>
            <version>4.6.0-${javacv.version}</version>
            <classifier>${ffmpeg.classifier}</classifier>
        </dependency>

二、预览图生成代码

package xxxx;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * 图片预览图处理器
 *
 * @author god_cvz
 */
@Component
@Slf4j
public class PicturePreviewProcessor {

    /**
     * 异步执行,防止阻碍主流程,建议使用线程池
     *
     * @param pictureUrl
     */
    public void asyncProcess(String pictureUrl) {
        new Thread(() -> process(pictureUrl)).start();
    }

    public void process(String pictureUrl) {
        log.info("开始处理照片 pictureUrl: {}", pictureUrl);
        try (InputStream inputStream = getInputStreamFromUrl(pictureUrl)) {
            // 安全地重置一个输入流以重新读取图像数据,若流不支持重置,则从网络重新下载图像‌,确保图像处理流程的可靠性
            if (inputStream.markSupported()) {
                // 将流指针归位,复用原流,避免重复下载,提升效率与节省带宽
                inputStream.reset();
                processImage(inputStream);
            } else {
                try (InputStream newInputStream = getInputStreamFromUrl(pictureUrl)) {
                    processImage(newInputStream);
                }
            }
        } catch (Exception e) {
            log.error("处理照片失败: {}", e.getMessage());
        }
    }

    /**
     * 从指定 URL 下载资源并返回 InputStream
     *
     * @param urlString 资源的 URL 地址
     * @return InputStream (需要调用方手动关闭)
     * @throws IOException 下载或连接失败时抛出异常
     */
    public InputStream getInputStreamFromUrl(String urlString) throws Exception {
        if (urlString == null || urlString.isEmpty()) {
            throw new IllegalArgumentException("URL 不能为空");
        }
        log.info("[DownloadUtils] 开始下载文件:{}", urlString);
        long startTime = System.currentTimeMillis();

        try {
            URL url = new URL(urlString);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(10_000);
            connection.setReadTimeout(15_000);
            connection.setDoInput(true);
            // 检查响应码
            int responseCode = connection.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new IOException("下载失败,HTTP 响应码:" + responseCode);
            }
            log.info("[DownloadUtils] 连接成功[{}],开始接收数据...", url);
            InputStream inputStream = connection.getInputStream();
            long endTime = System.currentTimeMillis();
            log.info("[DownloadUtils] 下载完成,用时 {} ms", (endTime - startTime));
            return inputStream;
        } catch (Exception e) {
            log.error("[DownloadUtils] 下载文件失败[{}], error:{}", urlString, e.getMessage());
            throw new Exception("[DownloadUtils] 下载文件失败: " + e.getMessage());
        }
    }

    private void processImage(InputStream inputStream) throws IOException {
        BufferedImage sourceImage = ImageIO.read(inputStream);
        // 生成预览图
        generatePreview(sourceImage);
        // 生成缩略图
        generateThumbnail(sourceImage);
        // 生成webp缩略图
        generateWebp(sourceImage);
    }

    private void generatePreview(BufferedImage sourceImage) {
        // 预览图宽度
        int previewWidth = 720;
        // 预览图高度
        int previewHeight = 540;
        String previewFileName = UUID.randomUUID() + ".jpg";
        String objectKey = "/bucket/preview/" + previewFileName;
        uploadImage(sourceImage, previewWidth, previewHeight, objectKey, previewFileName);
    }

    private void generateThumbnail(BufferedImage sourceImage) {
        // 缩略图宽度
        int thumbnailWidth = 200;
        // 缩略图高度
        int thumbnailHeight = 150;
        String thumbnailFileName = UUID.randomUUID() + ".jpg";
        String objectKey = "/bucket/thumbnail/" + thumbnailFileName;
        uploadImage(sourceImage, thumbnailWidth, thumbnailHeight, objectKey, thumbnailFileName);
    }

    private void uploadImage(BufferedImage sourceImage, Integer targetWith, Integer targetHeight, String objectKey, String fileName) {
        File outputFile = new File(fileName);
        FileInputStream fileInputStream = null;
        try {
            int[] changeSize = this.getChangeSize(sourceImage.getWidth(), sourceImage.getHeight(), targetWith, targetHeight);
            // 创建目标文件缓冲区
            BufferedImage outputImage = this.resizeImage(sourceImage, changeSize[0], changeSize[1]);
            // 保存图片到文件
            ImageIO.write(outputImage, "jpg", outputFile);
            fileInputStream = new FileInputStream(outputFile);
            // 上传至oss
            this.uploadFile(fileInputStream, objectKey);
            log.info("生成图片成功: objectKey: {}", objectKey);
        } catch (Exception e) {
            log.error("生成图片失败: objectKey: {}, 原因: {}", objectKey, e.getMessage());
        } finally {
            IoUtil.closeIfPosible(fileInputStream);
            FileUtil.del(outputFile);
        }
    }

    /**
     * 计算图像 按比例缩放后的宽高
     * 选择宽高缩放后较大的一边,固定其长度,另一边按原图比例缩放
     *
     * @param originalWidth  图像原宽度
     * @param originalHeight 图像原长度
     * @param targetWidth    目标宽度
     * @param targetHeight   目标高度
     * @return int[]
     */
    public int[] getChangeSize(int originalWidth, int originalHeight, int targetWidth, int targetHeight) {
        // 设置缩放比例
        double scale = Math.min(targetWidth / (double) originalWidth, targetHeight / (double) originalHeight);
        // 计算缩放后的宽高
        return new int[]{(int) (originalWidth * scale), (int) (originalHeight * scale)};
    }

    /**
     * 修改图片尺寸
     *
     * @param originImage  原图文件
     * @param targetWidth  目标宽度
     * @param targetHeight 目标高度
     * @return 制定尺寸图片
     */
    public BufferedImage resizeImage(BufferedImage originImage, int targetWidth, int targetHeight) {
        BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = resizedImage.createGraphics();
        graphics.drawImage(originImage, 0, 0, targetWidth, targetHeight, null);
        graphics.dispose();
        return resizedImage;
    }

    /**
     * todo:自定义上传至OSS文件存储服务器
     *
     * @param fileInputStream 文件流
     * @param objectKey       OSS存储key
     */
    private void uploadFile(FileInputStream fileInputStream, String objectKey) {

    }

    private void generateWebp(BufferedImage sourceImage) {
        String webpFileName = UUID.randomUUID() + ".webp";
        String objectKey = "/bucket/webp/" + webpFileName;
        uploadWebp(sourceImage, objectKey, webpFileName);
    }

    private void uploadWebp(BufferedImage sourceImage, String objectKey, String filename) {
        // 将BufferedImage转换为Frame
        Java2DFrameConverter converter = new Java2DFrameConverter();
        Frame frame = converter.convert(sourceImage);
        File outputFile = new File(filename);
        FileInputStream inputStream = null;
        // 设置输出图像的参数
        try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputFile, frame.imageWidth, frame.imageHeight)) {
            recorder.setFormat("webp");
            // 记录frame
            recorder.start();
            recorder.record(frame);
            recorder.stop();
            inputStream = new FileInputStream(outputFile);
            this.uploadFile(inputStream, objectKey);
            log.info("生成webp成功: objectKey: {}", objectKey);
        } catch (Exception | Error e) {
            log.error("生成webp失败: objectKey: {} - 原因: {}", objectKey, e.getMessage());
        } finally {
            IoUtil.closeIfPosible(inputStream);
            IoUtil.closeIfPosible(converter);
            FileUtil.del(outputFile);
        }
    }
}

tips:

1.建议使用线程池进行异步处理

2.代码中可自行修改预览图缩略图宽高

3.文件上传逻辑各自处理

到此这篇关于SpringBoot集成FFmpeg实现生成图片预览图与缩略图的文章就介绍到这了,更多相关SpringBoot FFmpeg生成图片预览图内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 关于java中多个JDK和切换版本介绍

    关于java中多个JDK和切换版本介绍

    大家好,本篇文章主要讲的是关于java中多个JDK和切换版本介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2022-01-01
  • Java import导入及访问控制权限修饰符原理解析

    Java import导入及访问控制权限修饰符原理解析

    这篇文章主要介绍了Java import导入及访问控制权限修饰符过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java 多线程并发 ReentrantReadWriteLock详情

    Java 多线程并发 ReentrantReadWriteLock详情

    这篇文章主要介绍了Java多线程并发ReentrantReadWriteLock详情,ReentrantReadWriteLock可重入读写锁。实际使用场景中,我们需要处理的操作本质上是读与写,更多相关资料,感兴趣的小伙伴可以参考一下下面文章内容
    2022-06-06
  • 解决idea 项目编译后没有class文件的问题

    解决idea 项目编译后没有class文件的问题

    这篇文章主要介绍了解决idea 项目编译后没有class文件的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • java 如何判断是否可以ping通某个地址

    java 如何判断是否可以ping通某个地址

    这篇文章主要介绍了java 如何判断是否可以ping通某个地址,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Spring 整合 MyBatis的实现步骤

    Spring 整合 MyBatis的实现步骤

    SpringMVC 本来就是 Spring 框架的一部分,这两者无须再做整合,所以 SSM 整合的关键就是Spring对MyBatis的整合,三大框架整合完成后,将以 Spring 为核心,调用有关资源,高效运作,这篇文章主要介绍了 Spring 整合 MyBatis的实现步骤,需要的朋友可以参考下
    2023-02-02
  • Java利用Spire.Doc for Java实现在Word文档中插入图片

    Java利用Spire.Doc for Java实现在Word文档中插入图片

    本文将深入探讨如何利用 Spire.Doc for Java 这一强大库,在 Java 程序中高效、灵活地实现 Word 文档的图片插入功能,包括不同环绕方式和指定位置的插入,有需要的小伙伴可以了解下
    2025-10-10
  • Java利用Spire.XLS for Java实现查找并替换Excel中的数据

    Java利用Spire.XLS for Java实现查找并替换Excel中的数据

    在日常的数据处理工作中,Excel 文件无疑是最常见的载体之一,本文将深入探讨如何借助 Java 语言和强大的 Spire.XLS for Java 自动化处理 Excel 文件的查找替换任务,感兴趣的小伙伴可以了解下
    2025-09-09
  • 微信支付java版本之JSAPI支付+发送模板消息

    微信支付java版本之JSAPI支付+发送模板消息

    这篇文章主要介绍了微信支付java版本之JSAPI支付,发送模板消息,感兴趣的小伙伴们可以参考一下
    2016-08-08
  • Springboot热加载JAR包的实现方法

    Springboot热加载JAR包的实现方法

    SpringBoot作为一个开发快速、部署方便的微服务框架,具有自动配置、约定优于配置的特点,能够极大地提高开发效率,它提供了丰富的扩展点,非常适合实现动态加载Jar包的功能,本文将深入探讨如何在SpringBoot应用中实现动态加载Jar包的方案,感兴趣的朋友一起看看吧
    2024-04-04

最新评论