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;
    }
相关推荐
xcl09254 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐5 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长6 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn6 小时前
Spring 注解使用详解
java·spring
一条小小yu6 小时前
Spring IoC的理解
java·后端·spring
落魄实习生6 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈6 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
自强的小白8 小时前
核心功能(Service接口)
java·mybatis
乌暮9 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习