javacv添加字幕 剧中显示

介绍

javacv目前不能像ffmpeg那样 直接加载字体文件到视频 参考这里

所以实现流程为:提取帧 -> 转图片 -> 编辑图片增加文字 -> 转回帧 -> 输出视频

上代码

java 复制代码
/**
     * 添加字幕
     *
     * @param videoFile      原始视频文件
     * @param outputFile     输出视频文件
     * @param subtitleFrames 字幕帧数组,帧是要有序的 根据时间由小到大;
     */
    public static void addSubtitle(File videoFile, File outputFile, List<SubtitleFrame> subtitleFrames) {
        Font font;
        // 加载字体文件,防止中文乱码
        try (InputStream fontStream = CvUtil.class.getResourceAsStream("/fonts/msyh.ttc")) {
            if (fontStream == null) {
                log.error("字体文件加载失败");
                throw new BusinessException(ErrorCodeEnum.Unknow);
            }
            font = Font.createFont(Font.TRUETYPE_FONT, fontStream).deriveFont(Font.BOLD, 24);
        } catch (Exception e) {
            log.error("字体文件加载失败", e);
            throw new BusinessException(e);
        }

        try (FFmpegFrameGrabber grabberVideo = new FFmpegFrameGrabber(videoFile)) {
            grabberVideo.start();

            try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputFile, grabberVideo.getImageWidth(), grabberVideo.getImageHeight())) {
                // 视频参数
                recorder.setFrameRate(grabberVideo.getFrameRate());
                recorder.setVideoCodec(grabberVideo.getVideoCodec());
                recorder.setFormat("mp4");
                recorder.setVideoBitrate(grabberVideo.getVideoBitrate());
                // 音频参数
                recorder.setAudioCodec(grabberVideo.getAudioCodec()); // 音频编码
                recorder.setAudioBitrate(grabberVideo.getAudioBitrate());
                recorder.setSampleRate(grabberVideo.getSampleRate()); // 采样率
                recorder.setAudioChannels(grabberVideo.getAudioChannels()); // 必须大于0,如立体声为2
                recorder.start();

                Frame frame;
                Iterator<SubtitleFrame> subtitleFrameIterator = subtitleFrames.iterator();
                SubtitleFrame subtitleFrame = subtitleFrameIterator.next();
                // 是否使用过当前字幕帧,只有只用过才可以迭代下一个帧; 这个标志可以帮助实现这种 0-2秒 2-4秒 7-10秒 这种时间不连续的字幕
                boolean useSubtitleFrame = false;

                while ((frame = grabberVideo.grabImage()) != null) {
                    // 转换为秒
                    long time = frame.timestamp / 1000;
                    String subtitle = null;

                    if (time >= subtitleFrame.sta && time < subtitleFrame.end) {
                        subtitle = subtitleFrame.text;
                        useSubtitleFrame = true;
                    } else if (useSubtitleFrame && subtitleFrameIterator.hasNext()) {
                        // 当前的字幕帧使用过 才可以切换下一个
                        subtitleFrame = subtitleFrameIterator.next();
                        useSubtitleFrame = false; // 新字幕帧标志为未使用
                    }

                    if (subtitle != null) {
                        BufferedImage image = Java2DFrameUtils.toBufferedImage(frame);
                        Graphics2D g2 = image.createGraphics();
                        g2.setFont(font);
                        // 计算字体宽度,方便剧中
                        FontMetrics fm = g2.getFontMetrics();
                        int textWidth = fm.stringWidth(subtitleFrame.text);
                        // 字幕位置
                        int[] subtitlePosition = calcSubtitlePosition(grabberVideo.getImageWidth(), grabberVideo.getImageHeight(), textWidth);
                        g2.drawString(subtitle, subtitlePosition[0], subtitlePosition[1]);
                        g2.dispose();
                        recorder.record(Java2DFrameUtils.toFrame(image));
                    } else {
                        recorder.record(frame);
                    }
                }
            }

        } catch (Exception e) {
            throw new BusinessException(e);
        }
    }

    /**
     * 计算字幕位置
     * 字体高度 10%  横向剧中
     *
     * @return int[]{x,y}
     */
    public static int[] calcSubtitlePosition(int videoWidth, int videoHeight, int textWidth) {
        int y = videoHeight - videoHeight / 10;
        // 24号字体约等于 32px
        int x = videoWidth / 2 - textWidth / 2;

        return new int[]{x, y};
    }

    /**
     * 字幕帧
     */
    public static class SubtitleFrame {

        public SubtitleFrame() {
        }

        public SubtitleFrame(long sta, long end, String text) {
            this.sta = sta;
            this.end = end;
            this.text = text;
        }

        // 起始时间 毫秒
        public long sta;

        // 结束时间 毫秒
        public long end;

        // 字幕文本
        public String text;
    }
相关推荐
工具罗某人2 分钟前
docker快速部署kafka
java·nginx·docker
秋饼4 分钟前
【手撕 @EnableAsync:揭秘 SpringBoot @Enable 注解的魔法开关】
java·spring boot·后端
Good_Starry8 分钟前
Java——正则表达式
java·开发语言·正则表达式
萤丰信息11 分钟前
开启园区“生命体”时代——智慧园区系统,定义未来的办公与生活
java·大数据·运维·数据库·人工智能·生活·智慧园区
欧洵.15 分钟前
Java.基于UDP协议的核心内容
java·开发语言·udp
xunyan623422 分钟前
第九章 JAVA常用类
java·开发语言
China_Yanhy38 分钟前
AWS S3 深度配置指南:每一栏每个选项有什么作用
java·数据库·aws
秃了也弱了。1 小时前
FASTJSON库:阿里出品java界json解析库,使用与踩坑记录
java·开发语言·json
安全渗透Hacker1 小时前
参数未校验导致的DOS(服务拒绝)问题典型场景
java·安全·web安全·网络安全·安全性测试
Chan161 小时前
微服务 - Higress网关
java·spring boot·微服务·云原生·面试·架构·intellij-idea