使用ffmpeg 命令行给视频添加文字水印,利用drawtext滤镜,如果是代码上api调用,也是一样的用法,创建好滤镜图, 在滤镜图描述字符串中,把这里命令行的参数拷贝过去替换就行,改动非常方便。
给视频添加文字,简单地添加一个helloworld 很方便,在《FFMPEG 从入门到精通》-刘歧 书第6.2章 有讲解,不过讲的非常浅显,不解其中参数的意义,总是期望哪里有一份官方说明使用手册来告诉我们这些参数的意义,遗憾并没有找到,这本书虽然是ffmpeg官网推荐的却也非常浅显,基本没有涉及源码,索性从源码里去寻找答案,这才是最完整的第一手资料。这里结合源代码,来分析分析这个滤镜用法。 源码vf_drawtext.c
添加文字我们关心的几个问题
1.0 文字内容。 固定的内容最简单,能否每一帧都添加不同的内容?能否添加程序自动计算出来的一些信息?
2.0 文字位置。固定位置也最简单,能否每一帧都添加在不同的位置?用一个公式去动态自己确定位置?
3.0 字体,颜色,背景色,逐渐变换的颜色。
一条典型的命令
叠加上每一帧的序号,解码成 raw yuv420p格式
ffmpeg -i native.mp4 -t 10 -vf "drawtext=fontsize=100:x=n*5:y=100:fontcolor=red:box=1:text='%{n}'" -pix_fmt yuv420p 1280x720_yuv420p_test.yuv
# -t 10 取前10s ,默认开始时间是0,也可以用参数:-ss 00:00:00 指定时间点
# -vf "" 这里就开始指定滤镜
#fontsize=100 字体大小 还有fontfile='xx' 指定使用的字体文件,我这里wind上执行的使用的系统缺省字体
#x=n*5 y=100 可以直接写固定数值,这里写了个表达式,n*5 n指的是帧序号,所以x坐标是在变化的。
#fontcolor=red 颜色, freetype字库接口支持的
#box=1 box是指字体背景“盒子”, 默认的话是背景透明的,即box=0 . 现在box=1是绘制背景的,box默认颜色白色,也可以通过 boxcolor 修改
#text='%{n}' 这个就有意思了,原本可以直接通过text='hello world' 设置固定的文字,这里是以%{xx}格式,表示扩展功能,当前用的这个扩展功能会将文字设置为每一帧的序号。
#文字还可以从文件加载,通过设置textfile=test_file 设置文件。
上面的输出如下,添加的文字信息是每一帧自己的序号 ,从0开始, 坐标因为设置了 x=n*5,也是和帧序号相关。
一:查看help信息
ffmpeg 命令行查看某一个滤镜的参数:
#ffmpeg --help filter=drawtext
这些help信息,都是直接在源码中可以查看到的,在源码vf_drawtext.c 的 AVOption结构体中,上面的输出就是从该结构体中取出的字符串,每一个滤镜都有这么一个选项结构体。
二:文字内容的几种来源
1.0 固定输入: 选项 -text='hello world'
2.0 从文件导入 -textfile='xxx'
3.0 %{xx} 扩展功能 文字内容从这个扩展函数里面生成
3.1 每一帧的pts 时间戳信息
text='%{pts}'
3.2 当前系统 时间
text='%{localtime}'
text='%{gmtime}' //gmtime和localtime的区别,就不啰嗦了
3.3 每一帧的序号
text='%{frame_num}' 或者简写成 text='%{n}'
3.4 添加mete 媒体信息
text='%{metadata\:width}' 这个功能必须携带一个参数,不给参数会报警,这里给的是width,源数据用的mp4,结果发现根本就么有mete信息.
在源码中://下面printf个人添加的,因为使用mp4文件的时候怎么添加mete都没有,结果发现这里 av_dict_count() 为0, 根本没有添加过meta信息, 这个dict, 词典, 存储着键值对信息。。实验失败,没啥用
static int func_metadata(AVFilterContext *ctx, AVBPrint *bp,
char *fct, unsigned argc, char **argv, int tag)
{
DrawTextContext *s = ctx->priv;
AVDictionaryEntry *e = av_dict_get(s->metadata, argv[0], NULL, 0);
//下面printf个人添加的,因为使用mp4文件的时候怎么添加mete都没有,结果发现这里 av_dict_count() 为0, 根本没有添加过meta信息, 这个dict, 词典, 存储着键值对信息。
printf("[%s%d]argv[0] :%s metacount:%d \n",__FUNCTION__,__LINE__,argv[0],av_dict_count(s->metadata));
if (e && e->value)
av_bprintf(bp, "%s", e->value);
else if (argc >= 2)
av_bprintf(bp, "%s", argv[1]);
return 0;
}
3.5 显示帧类型
text='%{pict_type}'
如下图,drawtext了两次同时加上序号和帧类型。
要注意,这里的帧类型,P帧,I帧,B帧, 指的是解码前 h264数据的类型,如果在命令操作的时候保存的是再编码的数据,再编码出来的数据可就和这里的帧类型无关了,滤镜都是在解码后,重编码前作的对raw数据做的像素级别的操作。
所以用命令行:
#ffmpeg -i native.mp4 -t 10 -vf "drawtext=fontsize=100:x=0:y=0:fontcolor=red:box=1:text='%{pict_type}'" -vcodec libx264 -bf 0 test_pict_type.h264 输出的重编码的 test_pict_type.h264 画面显示的 log 帧类型,是指 输入文件native.mp4 的帧类型,重编码后的 test_pict_type.h264 帧类型是不一样的。
3.6 扩展表达式。
{ "expr_int_format", 2, 3, 0, func_eval_expr_int_format },
{ "eif", 2, 3, 0, func_eval_expr_int_format },
这个就比较厉害了,ffmpeg命令行的参数都支持 表达式类型,比如条件判断,啥都都行,比较复杂,这里就不在此深究了。
源码中可以看到支持的扩展函数功能:
需要以%{} 格式输入:
源码中相关的函数
static int expand_text(AVFilterContext *ctx, char *text, AVBPrint *bp)
{
int ret;
av_bprint_clear(bp);
while (*text) {
if (*text == '\\' && text[1]) {
av_bprint_chars(bp, text[1], 1);
text += 2;
} else if (*text == '%') {
text++;
if ((ret = expand_function(ctx, bp, &text)) < 0)
return ret;
} else {
av_bprint_chars(bp, *text, 1);
text++;
}
}
if (!av_bprint_is_complete(bp))
return AVERROR(ENOMEM);
return 0;
}
三:坐标的控制
可以支持表达式扩展 ffmpeg命令行的参数都支持 表达式类型,比如条件判断,啥都都行,比较复杂,这里就不在此深究了。
四:使能,显示时机的控制。
enable=xxx
这个参数并不是本滤镜所特有的,是滤镜控制的一个公共的参数,上面 help信息中就有说明:
This filter has support for timeline through the 'enable' option.
同样这个参数可以支持表达式形式。