0
0
Files
fys-Multi-tenant/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java
DragonWenLong 9bbddee1d5 feat(equipment): 添加阿里巴巴TTS语音合成工具类
- 实现文本转语音功能,支持多种声音、语速、音量等参数调节
- 集成阿里云TTS服务,支持访问令牌自动刷新与缓存
- 提供HTTP客户端配置与请求处理逻辑
- 支持生成标准PCM数据及WAV格式音频文件
- 实现音频文件保存与错误处理机制
- 添加参数校验与日志记录功能
- 集成Redis缓存管理访问令牌- 支持URL编码与请求构建逻辑
- 实现响应处理与音频数据写入文件功能
- 添加静默删除临时文件与错误响应处理机制
2025-10-24 11:22:35 +08:00

194 lines
6.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.fuyuanshen.web.util;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
/**
* 视频处理工具类
*/
@Slf4j
@Component
public class VideoProcessUtil {
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/**
* 创建临时视频文件
*/
public File createTempVideoFile(MultipartFile file) throws Exception {
File tempFile = Files.createTempFile("upload-", ".mp4").toFile();
file.transferTo(tempFile);
log.debug("创建临时视频文件: {}", tempFile.getAbsolutePath());
return tempFile;
}
/**
* 处理视频并转换为Hex字符串列表
*/
public List<String> processVideoToHex(File videoFile, int frameRate, int duration, int width, int height) throws Exception {
// 1. 提取视频帧
List<BufferedImage> frames = extractFramesFromVideo(videoFile, frameRate, duration, width, height);
// 2. 转换为RGB565格式
byte[] binaryData = convertFramesToRGB565(frames);
// 3. 转换为Hex字符串列表
return bytesToHexList(binaryData);
}
/**
* 从视频中提取帧
*/
private List<BufferedImage> extractFramesFromVideo(File videoFile, int frameRate, int duration, int width, int height) throws Exception {
List<BufferedImage> frames = new ArrayList<>();
int totalFramesToExtract = frameRate * duration;
try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoFile)) {
grabber.start();
long totalFramesInVideo = grabber.getLengthInFrames();
int fps = (int) Math.round(grabber.getFrameRate());
if (fps <= 0) fps = 30;
double durationSeconds = (double) totalFramesInVideo / fps;
if (durationSeconds < duration) {
throw new IllegalArgumentException("视频太短,至少需要 " + duration + "");
}
double frameInterval = (double) totalFramesInVideo / totalFramesToExtract;
for (int i = 0; i < totalFramesToExtract; i++) {
int targetFrameNumber = (int) Math.round(i * frameInterval);
if (targetFrameNumber >= totalFramesInVideo) {
throw new IllegalArgumentException("目标帧超出范围: " + targetFrameNumber);
}
grabber.setFrameNumber(targetFrameNumber);
Frame frame = grabber.grab();
if (frame != null && frame.image != null) {
BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
frames.add(cropImage(bufferedImage, width, height));
} else {
throw new IllegalArgumentException("无法获取第 " + targetFrameNumber + "");
}
}
grabber.stop();
}
log.debug("从视频中提取了 {} 帧", frames.size());
return frames;
}
/**
* 将所有帧转换为 RGB565 格式字节数组
*/
private byte[] convertFramesToRGB565(List<BufferedImage> frames) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
for (BufferedImage image : frames) {
byte[] rgb565Bytes = convertToRGB565(image);
byteArrayOutputStream.write(rgb565Bytes);
}
byte[] result = byteArrayOutputStream.toByteArray();
log.debug("转换RGB565数据完成总字节数: {}", result.length);
return result;
}
/**
* 将字节数组转换为Hex字符串列表
*/
private List<String> bytesToHexList(byte[] bytes) {
List<String> hexList = new ArrayList<>();
for (byte b : bytes) {
int value = b & 0xFF;
char high = HEX_ARRAY[value >>> 4];
char low = HEX_ARRAY[value & 0x0F];
hexList.add(String.valueOf(high) + low);
}
return hexList;
}
/**
* 删除临时文件
*/
public void deleteTempFile(File file) {
if (file != null && file.exists()) {
if (file.delete()) {
log.debug("删除临时文件成功: {}", file.getAbsolutePath());
} else {
log.warn("无法删除临时文件: {}", file.getAbsolutePath());
}
}
}
/**
* 裁剪图像到目标尺寸
*/
private BufferedImage cropImage(BufferedImage img, int targetWidth, int targetHeight) {
int w = Math.min(img.getWidth(), targetWidth);
int h = Math.min(img.getHeight(), targetHeight);
return img.getSubimage(0, 0, w, h);
}
/**
* 将 BufferedImage 转换为 RGB565 格式的字节数组
*/
private byte[] convertToRGB565(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
byte[] result = new byte[width * height * 2];
int index = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int rgb = image.getRGB(x, y);
int r = ((rgb >> 16) & 0xFF) >> 3;
int g = ((rgb >> 8) & 0xFF) >> 2;
int b = (rgb & 0xFF) >> 3;
short pixel = (short) ((r << 11) | (g << 5) | b);
result[index++] = (byte) (pixel >> 8);
result[index++] = (byte) pixel;
}
}
return result;
}
/**
* 保存帧到本地(用于调试)
*/
public void saveFramesToLocal(List<BufferedImage> frames, String prefix) {
File outputDir = new File("output_frames");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
int index = 0;
for (BufferedImage frame : frames) {
try {
File outputImage = new File(outputDir, prefix + "_" + (index++) + ".png");
ImageIO.write(frame, "png", outputImage);
log.debug("保存帧图片成功: {}", outputImage.getAbsolutePath());
} catch (Exception e) {
log.error("保存帧图片失败", e);
}
}
}
}