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;
    }
相关推荐
S***267523 分钟前
基于SpringBoot和Leaflet的行政区划地图掩膜效果实战
java·spring boot·后端
马剑威(威哥爱编程)42 分钟前
鸿蒙6开发视频播放器的屏幕方向适配问题
java·音视频·harmonyos
JIngJaneIL1 小时前
社区互助|社区交易|基于springboot+vue的社区互助交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·社区互助
V***u4531 小时前
MS SQL Server partition by 函数实战二 编排考场人员
java·服务器·开发语言
这是程序猿2 小时前
基于java的ssm框架旅游在线平台
java·开发语言·spring boot·spring·旅游·旅游在线平台
i***t9192 小时前
基于SpringBoot和PostGIS的云南与缅甸的千里边境线实战
java·spring boot·spring
k***08292 小时前
【监控】spring actuator源码速读
java·spring boot·spring
麦麦鸡腿堡2 小时前
Java_网络编程_InetAddress类与Socket类
java·服务器·网络
vx_dmxq2112 小时前
【PHP考研互助系统】(免费领源码+演示录像)|可做计算机毕设Java、Python、PHP、小程序APP、C#、爬虫大数据、单片机、文案
java·spring boot·mysql·考研·微信小程序·小程序·php
5***g2983 小时前
新手如何快速搭建一个Springboot项目
java·spring boot·后端