package com.ruoyi.pay.service.impl;
import com.ruoyi.pay.service.IVideoConversionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author test
* @version 1.0
* @title
* @date 2024/9/28/周六 19:33
*/
@Service
public class VideoConversionServiceImpl implements IVideoConversionService
{
private static final Logger log = LoggerFactory.getLogger(VideoConversionServiceImpl.class);
/**
* 将图片与音频合并为视频文件。
*
* @param imagePath 要转换的图片文件路径
* @param audioPath 音频文件路径
* @param outputPath 输出视频文件路径
* @throws IOException 如果读写文件时发生错误
*/
public void convertImageAndAudioToVideo(String imagePath, String audioPath, String outputPath) throws IOException, InterruptedException
{
// 验证路径有效性
validatePaths(imagePath, audioPath, outputPath);
//ffmpeg -r 10 -f image2 -loop 1 -i 03_20240928161210A005.jpg -i 03_20240928161219A006.mp3 -s 1920x1080
// -pix_fmt yuvj420p -t 30 -vcodec libx264 output.mp4
// 使用 FFmpeg 合并图片和音频
ProcessBuilder processBuilder = new ProcessBuilder(
"ffmpeg",
"-r", "10",
"-f", "image2",
"-loop", "1",
"-i", imagePath,
"-i", audioPath,
"-s", "1920x1080",
"-pix_fmt", "yuvj420p",
"-t", "30",
"-vcodec", "libx264",
outputPath,
"-y"
);
/**
-r 10:fps设置为10帧/秒(不同位置有不同含义)
-f image2:表示输入或输出文件的格式是image2格式, -f是format(格式)的意思
-loop 1:因为只有一张图片所以必须加入这个参数(循环这张图片)
-i:输入
-s:指定视频的分辨率
-pix_fmt:指定图片输入格式(有yuv420,yuv444等各种格式)
-t:图片转换成视频的持续时长,单位是秒(S),必须指定该值,否则会无限制生成视频
-vcodec:生成视频的编码格式,这里指定的是x264
*/
// 设置工作目录
processBuilder.directory(new File("."));
// 启动进程
Process process = processBuilder.start();
dealStream(process);
// 等待进程结束
int exitCode = process.waitFor();
if (exitCode != 0) {
// 获取错误输出
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line;
StringBuilder errorMessage = new StringBuilder();
while ((line = errorReader.readLine()) != null) {
errorMessage.append(line).append("\n");
}
throw new IOException("FFmpeg process failed with exit code " + exitCode + ": " + errorMessage.toString());
}
}
/**
* 处理process输出流和错误流,防止进程阻塞
* 在process.waitFor();前调用
* @param process
*/
private static void dealStream(Process process) {
if (process == null) {
return;
}
// 处理InputStream的线程
new Thread() {
@Override
public void run() {
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
try {
while ((line = in.readLine()) != null) {
log.info("output: " + line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
// 处理ErrorStream的线程
new Thread() {
@Override
public void run() {
BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = null;
try {
while ((line = err.readLine()) != null) {
log.info("err: " + line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
err.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
private void validatePaths(String... paths) throws IOException {
for (String path : paths) {
File file = new File(path);
if (!file.exists() || !file.canRead()) {
throw new IOException("Invalid or inaccessible path: " + path);
}
}
}
}