ffmpeg将图片与音频合并为视频文件

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);
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值