java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot FFmpeg生成图片预览图

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

作者: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生成图片预览图内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文