SpringBoot集成FFmpeg實現(xiàn)生成圖片預(yù)覽圖與縮略圖
本文介紹了SpringBoot集成FFmpeg生成圖片預(yù)覽圖與縮略圖功能的實現(xiàn)方案。首先配置Maven依賴,包含JavaCV核心庫和FFmpeg等組件。核心類PicturePreviewProcessor實現(xiàn)了從URL獲取圖片、生成不同尺寸預(yù)覽圖(720×540)、縮略圖(200×150)和WebP格式圖片的功能。通過Java2DFrameConverter和FFmpegFrameRecorder實現(xiàn)圖片格式轉(zhuǎn)換,提供圖片縮放計算方法getChangeSize()保持原圖比例。建議使用線程池異步處理圖片生成任務(wù),并自行實現(xiàn)OSS文件上傳邏
一、導(dǎo)入pom依賴
<profiles>
<profile>
<id>local</id>
<properties>
<profiles.active>local</profiles.active>
<!--根據(jù)環(huán)境進行切換-->
<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>二、預(yù)覽圖生成代碼
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;
/**
* 圖片預(yù)覽圖處理器
*
* @author god_cvz
*/
@Component
@Slf4j
public class PicturePreviewProcessor {
/**
* 異步執(zhí)行,防止阻礙主流程,建議使用線程池
*
* @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)) {
// 安全地重置一個輸入流以重新讀取圖像數(shù)據(jù),若流不支持重置,則從網(wǎng)絡(luò)重新下載圖像?,確保圖像處理流程的可靠性
if (inputStream.markSupported()) {
// 將流指針歸位,復(fù)用原流,避免重復(fù)下載,提升效率與節(jié)省帶寬
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 (需要調(diào)用方手動關(guān)閉)
* @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);
// 檢查響應(yīng)碼
int responseCode = connection.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new IOException("下載失敗,HTTP 響應(yīng)碼:" + responseCode);
}
log.info("[DownloadUtils] 連接成功[{}],開始接收數(shù)據(jù)...", 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);
// 生成預(yù)覽圖
generatePreview(sourceImage);
// 生成縮略圖
generateThumbnail(sourceImage);
// 生成webp縮略圖
generateWebp(sourceImage);
}
private void generatePreview(BufferedImage sourceImage) {
// 預(yù)覽圖寬度
int previewWidth = 720;
// 預(yù)覽圖高度
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);
// 創(chuàng)建目標(biāo)文件緩沖區(qū)
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 目標(biāo)寬度
* @param targetHeight 目標(biāo)高度
* @return int[]
*/
public int[] getChangeSize(int originalWidth, int originalHeight, int targetWidth, int targetHeight) {
// 設(shè)置縮放比例
double scale = Math.min(targetWidth / (double) originalWidth, targetHeight / (double) originalHeight);
// 計算縮放后的寬高
return new int[]{(int) (originalWidth * scale), (int) (originalHeight * scale)};
}
/**
* 修改圖片尺寸
*
* @param originImage 原圖文件
* @param targetWidth 目標(biāo)寬度
* @param targetHeight 目標(biāo)高度
* @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文件存儲服務(wù)器
*
* @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轉(zhuǎn)換為Frame
Java2DFrameConverter converter = new Java2DFrameConverter();
Frame frame = converter.convert(sourceImage);
File outputFile = new File(filename);
FileInputStream inputStream = null;
// 設(shè)置輸出圖像的參數(shù)
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.代碼中可自行修改預(yù)覽圖縮略圖寬高
3.文件上傳邏輯各自處理
到此這篇關(guān)于SpringBoot集成FFmpeg實現(xiàn)生成圖片預(yù)覽圖與縮略圖的文章就介紹到這了,更多相關(guān)SpringBoot FFmpeg生成圖片預(yù)覽圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java import導(dǎo)入及訪問控制權(quán)限修飾符原理解析
這篇文章主要介紹了Java import導(dǎo)入及訪問控制權(quán)限修飾符過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11
Java?多線程并發(fā)?ReentrantReadWriteLock詳情
這篇文章主要介紹了Java多線程并發(fā)ReentrantReadWriteLock詳情,ReentrantReadWriteLock可重入讀寫鎖。實際使用場景中,我們需要處理的操作本質(zhì)上是讀與寫,更多相關(guān)資料,感興趣的小伙伴可以參考一下下面文章內(nèi)容2022-06-06
Java利用Spire.Doc for Java實現(xiàn)在Word文檔中插入圖片
本文將深入探討如何利用 Spire.Doc for Java 這一強大庫,在 Java 程序中高效、靈活地實現(xiàn) Word 文檔的圖片插入功能,包括不同環(huán)繞方式和指定位置的插入,有需要的小伙伴可以了解下2025-10-10
Java利用Spire.XLS?for?Java實現(xiàn)查找并替換Excel中的數(shù)據(jù)
在日常的數(shù)據(jù)處理工作中,Excel?文件無疑是最常見的載體之一,本文將深入探討如何借助?Java?語言和強大的?Spire.XLS?for?Java?自動化處理?Excel?文件的查找替換任務(wù),感興趣的小伙伴可以了解下2025-09-09

