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 多线程并发 ReentrantReadWriteLock详情
这篇文章主要介绍了Java多线程并发ReentrantReadWriteLock详情,ReentrantReadWriteLock可重入读写锁。实际使用场景中,我们需要处理的操作本质上是读与写,更多相关资料,感兴趣的小伙伴可以参考一下下面文章内容2022-06-06
Java利用Spire.Doc for Java实现在Word文档中插入图片
本文将深入探讨如何利用 Spire.Doc for Java 这一强大库,在 Java 程序中高效、灵活地实现 Word 文档的图片插入功能,包括不同环绕方式和指定位置的插入,有需要的小伙伴可以了解下2025-10-10
Java利用Spire.XLS for Java实现查找并替换Excel中的数据
在日常的数据处理工作中,Excel 文件无疑是最常见的载体之一,本文将深入探讨如何借助 Java 语言和强大的 Spire.XLS for Java 自动化处理 Excel 文件的查找替换任务,感兴趣的小伙伴可以了解下2025-09-09


最新评论