无论是歌曲,小说,还是技术文章从古至今都是各种抄袭,各种拿来就用,随着技术的发展,朋友圈的图片,商铺的图片,也开始各种拿来就用,所以发明了水印,当然不止图片有水印,视频也可以有的。

这期我们介绍的是添加静态文字水印,当然可以添加图片水印,动态文字水印,只是这期的重点是添加静态文字水印。添加静态文字水印有两种方法。

不论怎么样,都要引入 Maven

<!-- https://mvnrepository.com/artifact/org.bytedeco/javacv-platform -->
<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5.2</version>
</dependency>

第一种(使用 FFmpeg 原生的)

FFmpegFrameFilter (过滤器)

FFmpegFrameFilter 功能很强大,这个类一共有三个构造方法。

第一个

//过滤音频,给音频做一些处理
public FFmpegFrameFilter(String afilters, int audioChannels)

第二个

//过滤视频,给视频做一些处理
public FFmpegFrameFilter(String filters, int imageWidth, int imageHeight)

第三个

//同时过滤音频与视频,给音视频做一些处理
public FFmpegFrameFilter(String videoFilters, String audioFilters, int imageWidth, int imageHeight, int audioChannels)

videoFilters 这个是视频的一些处理设置,具体参考 ffmpeg.
audioFilters 这个是音频的一些处理设置,具体参考 ffmpeg.
imageWidth imageHeight这个是视频的宽和高。
audioChannels 这个是音频的通道。

都说清楚了,那我们上代码吧。

常量代码

//设置修改视频的路径
public static final String VIDEO_OLD_PATH =
        "/voicedemo/src/main/java/xin/zhuyao/voicedemo/video/1608599180608-0.mp4";
//设置新生产视频的路径
public static final String VIDEO_NEW_PATH =
        "/voicedemo/src/main/java/xin/zhuyao/voicedemo/test/1608599180608-1.mp4";
//字体
private static final String FONT_PATH =
        "/Library/Fonts/Arial\\ Unicode.ttf";

核心代码

/**
    * 添加字体水印
    * @param content 水印内容
    * @throws Exception Exception
    */
public static void addTextByFFmpegFrame(String content) throws Exception {

        //抓取器
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(VIDEO_OLD_PATH);
        //打开抓取器
        grabber.start();

        //录播器
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(
                VIDEO_NEW_PATH,
                grabber.getImageWidth(),
                grabber.getImageHeight(),
                grabber.getAudioChannels());

        // 视频相关配置,取原视频配置
        recorder.setFrameRate(grabber.getFrameRate());
        recorder.setVideoCodec(grabber.getVideoCodec());
        recorder.setVideoBitrate(grabber.getVideoBitrate());

        // 音频相关配置,取原音频配置
        recorder.setSampleRate(grabber.getSampleRate());
        recorder.setAudioCodec(grabber.getAudioCodec());
        recorder.setAudioBitrate(grabber.getAudioBitrate());
        //开启录播器
        recorder.start();

        //过滤器
        String filterContent = "drawtext=fontfile="+ FONT_PATH + ": text='" + content + "': fontsize=h/30: x=(w-text_w)/2: y=(h-text_h*2)";
        FFmpegFrameFilter fFmpegFrameFilter = new FFmpegFrameFilter(
                filterContent,
                grabber.getImageWidth(),
                grabber.getImageHeight());
        //开启视频过滤器
        fFmpegFrameFilter.start();

        Frame frame;
        while ((frame = grabber.grab()) != null) {
            if (frame.image != null) {
                fFmpegFrameFilter.push(frame);
                recorder.record(fFmpegFrameFilter.pullImage());
            }
        }
    fFmpegFrameFilter.close();
    recorder.close();
    grabber.close();
}

测试

public static void main(String[] args) throws Exception {
    addTextByFFmpegFrame("程序员小朱");
}

第二种(我取每一帧,然后把文字加在每一帧上)

这个逻辑很清楚吧,就是视频里取每一帧图片,然后添加上文字在放上去。

常量代码

//设置修改视频的路径
public static final String VIDEO_OLD_PATH =
        "/voicedemo/src/main/java/xin/zhuyao/voicedemo/video/1608599180608-0.mp4";
//设置新生产视频的路径
public static final String VIDEO_NEW_PATH =
        "/voicedemo/src/main/java/xin/zhuyao/voicedemo/test/1608599180608-1.mp4";
//字体
private static final String FONT_PATH =
        "/Library/Fonts/Arial\\ Unicode.ttf";

核心代码

public static void addTextByGraphics(String context) throws Exception {
    // 设置源视频、加字幕后的视频文件路径
    FFmpegFrameGrabber grabber =
            new FFmpegFrameGrabber(VIDEO_OLD_PATH);
    grabber.start();
    FFmpegFrameRecorder recorder =
            new FFmpegFrameRecorder(VIDEO_NEW_PATH,
                    grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());

    // 视频相关配置,取原视频配置
    recorder.setFrameRate(grabber.getFrameRate());
    recorder.setVideoCodec(grabber.getVideoCodec());
    recorder.setVideoBitrate(grabber.getVideoBitrate());

    // 音频相关配置,取原音频配置
    recorder.setSampleRate(grabber.getSampleRate());
    recorder.setAudioCodec(grabber.getAudioCodec());
    recorder.setAudioBitrate(grabber.getAudioBitrate());
    recorder.start();

    Java2DFrameConverter converter = new Java2DFrameConverter();

    Frame frame;
    while ((frame =grabber.grab()) != null) {
        // 从视频帧中获取图片
        if (frame.image != null) {
            BufferedImage bufferedImage = converter.getBufferedImage(frame);
            // 对图片进行文本合入
            bufferedImage = addText(bufferedImage, context);
            // 视频帧赋值,写入输出流
            recorder.record(converter.getFrame(bufferedImage));
        }
        //配置音乐
        if (frame.samples != null) {
            recorder.record(frame);
        }

    }
    recorder.close();
    grabber.close();
}


/**
    * 图片添加文本
    *
    * @param bufImg bufImg
    * @param content content
    * @return BufferedImage
    */
private static BufferedImage addText(BufferedImage bufImg, String content) {
    Graphics2D graphics = bufImg.createGraphics();
    graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

    //设置图片背景
    graphics.drawImage(bufImg, 0, 0, bufImg.getWidth(), bufImg.getHeight(), null);

    //设置字体
    Font font= new Font("宋体", Font.BOLD, 32);
    FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
    graphics.setFont(font);
    int textWidth =metrics.stringWidth(content);
    graphics.drawString(content, (bufImg.getWidth() - textWidth) / 2, bufImg.getHeight() / 2);
    graphics.dispose();
    return bufImg;
}

测试

public static void main(String[] args) throws Exception {
    addTextByGraphics("程序员小朱");
}

Q.E.D.