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;
    }
相关推荐
我命由我123457 分钟前
Kotlin 开发 - lateinit 关键字
android·java·开发语言·kotlin·android studio·android-studio·android runtime
aXin_ya8 分钟前
微服务第八天 Sentinel 四种分布式事务模式
java·数据库·微服务
Halo_tjn11 分钟前
Java Set集合相关知识点
java·开发语言·算法
Linsk17 分钟前
Java和JavaScript的关系真是雷峰和雷峰塔的关系吗?
java·javascript·oracle
许彰午30 分钟前
我手写了一个 Java 内存数据库(二):B+ 树的插入与分裂
java·开发语言·面试
zhouwy11331 分钟前
Java 快速入门笔记:从基础语法到 Spring Boot 实战
java
极创信息1 小时前
信创产品认证怎么做?信创产品测试认证的主要流程
java·大数据·数据库·金融·软件工程
SamDeepThinking1 小时前
并发量就算只有2,该上锁还得上呀
java·后端·架构
Sam_Deep_Thinking1 小时前
如何让订单系统和营销系统解耦
java·架构·系统架构
lzhdim2 小时前
SQL 入门 12:SQL 视图:创建、修改与可更新视图
java·大数据·服务器·数据库·sql