java 获取 ffmpeg 转码进度

java 获取 ffmpeg 转码进度

说明:由于需要用到转码,需要获取进度,找很多资料都不是很理想。下面是我自己参考资料改善的。废话不多说。直接上代码,代码如下

  1. 定义一个实体,主要用于获取视频信息

    import com.alibaba.fastjson.annotation.JSONField;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.List;
    
    /**
     * @author: pinlin
     * @date: 2021/8/18 14:07
     * @description: 视频信息输出实体
     */
    @NoArgsConstructor
    @Data
    public class VideoInfoVO {
    
        @JSONField(name = "streams")
        private List<StreamsVO> streams;
    
        @NoArgsConstructor
        @Data
        public static class StreamsVO {
            @JSONField(name = "duration_ts")
            private String durationTs;
            @JSONField(name = "duration")
            private String duration;
            @JSONField(name = "height")
            private String height;
            @JSONField(name = "level")
            private String level;
            @JSONField(name = "profile")
            private String profile;
            @JSONField(name = "width")
            private String width;
            @JSONField(name = "bit_rate")
            private String bitRate;
        }
    }
    
    
  2. 转码工具类代码如下

    
    import cn.hutool.core.util.StrUtil;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.google.common.collect.Lists;
    import com.sf.yh.sys.test.VideoInfoVO;
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.*;
    import java.util.List;
    import java.util.Objects;
    
    /**
     * @author: pinlin
     * @date: 2021/8/16 9:14
     * @description:
     */
    @Slf4j
    public class TranscodeUtil {
    
        private static final String X = "x";
        private static final String STR_TIME = "time=";
        private static final String STR_BITRATE = " bitrate=";
    
        private static final String DEFAULT_WIDTH = "1280";
        private static final String DEFAULT_HEIGHT = "720";
    
        private TranscodeUtil() {
            throw new IllegalStateException("Utility class");
        }
    
        /**
         * 执行视频转码
         *
         * @param sourcePath 目标文件地址,需要加上文件名
         * @param targetPath 目标文件地址(转码后存放的文件地址,需要带上文件名)
         * @author zpl
         * @date 2021/8/16 16:17
         */
        public static Boolean transVideo(String sourcePath, String targetPath) {
            try {
                File f0 = new File(sourcePath);
                // 如果传入的命名不是文件,直接结束执行
                if (!f0.isFile()) {
                    log.info("传入路径不是文件,请检查 传入的路径为 sourcePath : {}", sourcePath);
                    return Boolean.FALSE;
                }
    
                long startTime = System.currentTimeMillis();
                VideoInfoVO videoInfoVO = getFileInfo(sourcePath);
                log.info("获取视频总耗时 {}", (System.currentTimeMillis() - startTime));
                if (Objects.isNull(videoInfoVO)) {
                    return Boolean.FALSE;
                }
                VideoInfoVO.StreamsVO streamsVO = videoInfoVO.getStreams().get(0);
                String width = streamsVO.getWidth() == null ? DEFAULT_WIDTH : streamsVO.getWidth();
                String height = streamsVO.getHeight() == null ? DEFAULT_HEIGHT : streamsVO.getHeight();
    
                // 封装命令参数
                List<String> command = Lists.newArrayList();
                command.add("ffmpeg");
                // 把转码过程过程输出到文件中,主要用于进度展示
                //command.add("-progress");
                //command.add("F:\\公司资料\\video\\视频\\tr\\progress.txt");
                command.add("-i");
                command.add(sourcePath);
                command.add("-s");
                command.add(width + X + height);
                command.add("-threads");
                command.add("8");
                command.add("-pix_fmt");
                command.add("yuv420p");
                command.add("-vcodec");
                command.add("libx264");
                command.add("-preset");
                command.add("veryslow");
                command.add("-profile:v");
                command.add("main");
                command.add("-level:v");
                command.add("4.1");
                command.add("-crf");
                command.add("30");
                command.add("-acodec");
                command.add("aac");
                // 设置音频采样率
                //command.add("-ar");
                //command.add("44100");
                // 音频声道数
                //command.add("-ac");
                //command.add("2");
                // 设置音频比特率
                //command.add("-b:a");
                // command.add(60k);
                // 设置视频比特率
                command.add("-b:v");
                command.add("90k");
                // 禁用音频
                command.add("-an");
                command.add(targetPath);
    
                ProcessBuilder builder = new ProcessBuilder();
                builder.command(command);
    
                // 临时变量,用于记录是否转码完成
                boolean temp = false;
    
                Process process = builder.start();
    
                try (
                        BufferedInputStream in = new BufferedInputStream(process.getErrorStream());
                        BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
                    String lineStr;
                    String totalDuration = streamsVO.getDuration();
    
                    while ((lineStr = reader.readLine()) != null) {
                        // 转码开始时间索引
                        int strTimeStartIndex = lineStr.indexOf(STR_TIME);
    
                        // 判断是否存在,存在则获取转码过程输出的时间
                        if (strTimeStartIndex > 0 && lineStr.indexOf(STR_BITRATE) > 0) {
                            // 转码过程时间结束索引
                            int strTimeEndIndex = lineStr.indexOf(STR_BITRATE, strTimeStartIndex);
    
                            if (strTimeEndIndex > strTimeStartIndex) {
                                // 编码过程时间
                                String strProcessTime = lineStr.substring(strTimeStartIndex + 5, strTimeEndIndex);
                                log.info("转码进度时间为 {}", strProcessTime);
    
                                String strParseTime = parseStrTime(strProcessTime);
                                // 判断下,防止索引越界异常
                                if (strParseTime.length() > 3) {
                                    strParseTime = strParseTime.substring(0, 3);
                                }
    
                                String strTotalTime = totalDuration.split("\\.")[0];
    
                                if (strTotalTime.equals(strParseTime)) {
                                    temp = true;
                                }
                            }
    
                        }
                        log.info("进度信息 {}", lineStr);
                    }
                }
    
                // 0 表示正常终止
                int status = process.waitFor();
                log.info("status {}", status);
    
                // 0 表示正常终止
                int exitValue = process.exitValue();
    
                log.info("exitValue {}", status);
    
                if (status != 0 && exitValue != 0) {
                    log.error("》》》》》》》》》》命令执行失败》》》》》》》》》》》》》》");
                }
    
                // 终止子进程执行
                process.destroy();
                return temp;
            } catch (Exception e) {
                log.error("创建转码进程异常", e);
                Thread.currentThread().interrupt();
            }
            log.info("进程结束");
            return Boolean.FALSE;
        }
    
        /**
         * 获取视频的所有信息
         *
         * @param sourcePath 文件路径,带上文件名
         * @return {@link VideoInfoVO}
         * @author zpl
         * @date 2021/8/16 16:15
         */
        public static VideoInfoVO getFileInfo(String sourcePath) throws IOException {
            List<String> command = Lists.newArrayList();
            command.add("ffprobe");
            command.add("-v");
            command.add("quiet");
            command.add("-show_streams");
            command.add("-print_format");
            command.add("json");
            command.add(sourcePath);
    
            ProcessBuilder builder = new ProcessBuilder(command);
            Process process = builder.start();
    
            VideoInfoVO videoInfoVO = null;
    
            InputStream in = process.getInputStream();
            try (
                    // 使用缓冲流读取
                    BufferedInputStream buff = new BufferedInputStream(in)) {
    
                byte[] bytes = new byte[1024];
                // 用来接收读取的字节数组
                StringBuilder sb = new StringBuilder();
                // 读取到的字节数组长度,为-1时表示没有数据
                int length;
                // 循环取数据
                while ((length = buff.read(bytes)) != -1) {
                    // 将读取的内容转换成字符串
                    sb.append(new String(bytes, 0, length));
                }
    
                if (sb.length() > 0) {
                    String result = sb.toString();
                    videoInfoVO = JSON.parseObject(result, VideoInfoVO.class);
                }
            }
    
            in.close();
            process.destroy();
    
            return videoInfoVO;
        }
    
     	/**
     	 * 解析时间字符串
     	 *
     	 * @param strTime 时间字符串
     	 * @return {@link String}
     	 * @author zpl
     	 * @date 2021/8/18 17:25
     	 */
        private static String parseStrTime(String strTime) {
            if (StrUtil.isBlank(strTime)) {
                return "";
            }
    
            String strHour = strTime.substring(0, 1 + 1);
            String strMinute = strTime.substring(3, 4 + 1);
            String strSecond = strTime.substring(6, 7 + 1);
            String strMilliSecond = strTime.substring(9);
            int totalDuration = (Integer.parseInt(strHour) * 3600
                    + Integer.parseInt(strMinute) * 60
                    + Integer.parseInt(strSecond))
                    * 1000
                    + Integer.parseInt(strMilliSecond);
            return String.valueOf(totalDuration);
        }
    
        public static void main(String[] args) {
            parseStrTime("00:04:13.79");
        }
    }
    
    

    要在界面实时展示,可以把获取的进度存入缓存,然后前端定时查询即可,既可以通过WebSocket 来实现,如有描述不对还请大家指出。谢谢。

posted @ 2021-08-18 17:12  品霖  阅读(772)  评论(0编辑  收藏  举报