Skip to content

Instantly share code, notes, and snippets.

@mzhboy
Forked from v5tech/ffmpeg.md
Last active April 12, 2023 07:41
Show Gist options
  • Save mzhboy/82cf6cd11d9b45d3956f0fca9ba4ea5a to your computer and use it in GitHub Desktop.
Save mzhboy/82cf6cd11d9b45d3956f0fca9ba4ea5a to your computer and use it in GitHub Desktop.
ffmpeg视频合并、格式转换、截图

使用ffmpeg合并MP4文件

ffmpeg -i "Apache Sqoop Tutorial Part 1.mp4" -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate1.ts
ffmpeg -i "Apache Sqoop Tutorial Part 2.mp4" -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts
ffmpeg -i "Apache Sqoop Tutorial Part 3.mp4" -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate3.ts
ffmpeg -i "Apache Sqoop Tutorial Part 4.mp4" -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate4.ts
ffmpeg -i "concat:intermediate1.ts|intermediate2.ts|intermediate3.ts|intermediate4.ts" -c copy -bsf:a aac_adtstoasc "Apache Sqoop Tutorial.mp4"

视频分割

ffmpeg -ss 00:00:00 -t 00:00:03 -y -i test.mp4 -vcodec copy -acodec copy test1.mp4  

参数解释

说明:上面的这个例子是将test.mp4视频的前3秒,重新生成一个新视频。
-ss 开始时间,如: 00:00:00,表示从0秒开始,格式也可以00:00:0
-t 时长,如: 00:00:03,表示截取3秒长的视频,格式也可以00:00:3
-y 如果文件已存在强制替换;
-i 输入,后面是空格,紧跟着就是输入视频文件;
-vcodec copy 和 -acodec copy表示所要使用的视频和音频的编码格式,这里指定为copy表示原样拷贝;

硬件加速的批量转码

ffmpeg wiki HWAccelIntro 有介绍,缺点是无法设置 maxrate bufsize gop注意需要安装最新驱动 ,性能实测 1080P HEVC 转 avc 可以达到 50fps 以上,同时 cpu 占有率非常低。

对于安装了NVIDIA GPU 可以在输入前插入 -hwaccel cuvid 选项启用 NVIDIA 的解码器, 但是实测 CUVID 兼容性非常差(安装最新的驱动程序包括 CUDA),通过在输入文件后添加 -ss 选项测试发现没有视频能够成功打开加速(回落到软件解码);倒是 nvdec 能够解码部分视频流(主要是 avc , 根据硬件有别 )。因此使用 -hwaccel dxva2 代替,注意 dxva2 是由微软实现的,仅运行与 Windows 和 Xbox 平台。

更好的硬件解码使用 libnpp 实现,不过这需要自己手动编译。但是其性能是极好的,因为所有的处理都在显卡上完成,不像上述需要把数据从显卡取回内存然后交由 CPU 处理。

@echo off
mode con cols=200 lines=2500
for /r %%I in (*.mkv) do @ffmpeg -hide_banner -hwaccel nvdec -i "%%I" -ss 0:2:0 -t 0:2:0  -pix_fmt yuv420p -an -c:v h264_nvenc -preset slow -profile:v high -level 4.2 -bf 4 -rc:v vbr_minqp -qmin 19 -qmax 25 -maxrate:v 40M -g 60 o.mp4 -y
title finish
echopause

简要解释:

  • -hwaccel nvdec 设置硬件解码器为 nvdec ,根据硬件限制可能回落到软件解码

  • -pix_fmt yuv420p 是必须的,通常是 yuv420p 具体可用项目根据硬件有别。

  • -rc:v vbr_minqp 可变的 crf

  • -qmin 19 -qmax 25 允许的最大最小 crf

  • -bf 4 设置 b-frames 为4,实际是根据硬件限制,通常为2有效

  • -g 60 设置了 max-gop

  • -b:v 2500k 设置平均 bitrate

  • -maxrate:v 40M 设置最大比特率

  • -level 4.2 手动指定了profile level ,但是一般自动设置为最低比较好,这样更容易被硬件解码

使用ffmpeg转换flv到mp4

ffmpeg -i out.flv -vcodec copy -acodec copy out.mp4

使用ffmpeg截图

Create a thumbnail image every X seconds of the video

ffmpeg -ss 00:10:00 -i "Apache Sqoop Tutorial.mp4" -y -f image2 -vframes 1 test.png

ffmpeg -ss 10 -i input.flv -y -f image2  -vframes 100 -s 352x240 b-%03d.jpg
ffmpeg -i test.mp4 -y -f mjpeg -ss 3 -t 1  test1.jpg
ffmpeg -i test.mp4 -y -f image2 -ss 3 -vframes 1 test1.jpg
  • 根据输出尺寸输出图片,并保证输出是偶数。

有时候输入和输入尺寸并不相等,可能需要拉伸显示像素。如输入为 SAR=4:3 ,但是 PAR 被设置为 4:3,这样输出为 SAR*PAR=4:3 。播放器可以识别这个问题并自动拉伸像素,但是ffmpeg产生缩略图时输出的图像是正方形的像素,这时候就需要 重设分辨率了 ,使用 scale='trunc(ih*dar):ih',setsar=1/1 ,为了输出偶数分辨率使用 scale='trunc(ih*dar/2)*2:trunc(ih/2)*2',setsar=1/1 。注意这里设置输出高度与输入相同,但是调整了输出宽度。如果用固定输入宽度调整输出高度的方式用如下例 scale='w=480:trunc(ow/dar/2)*2:flags=lanczos',setsar=1/1

ffmpeg -hide_banner -ss 0:7:0 -i input.mp4 -vf "scale='trunc(ih*dar/2)*2:trunc(ih/2)*2',setsar=1/1" -vframes 1 -q:v 80 out.jpg -y

参数解释:

-i  输入文件
-y  覆盖
-f  生成图片格式
-ss 开始截图时间 seconds or in hh:mm:ss[.xxx] 如果截图开始时间越接近篇尾,所花费的时间就会越长
-vframes  截图帧数 或者 使用 -t : 截图时长 seconds, or hh:mm:ss[.xxx]
-q:v 设置输出图像质量
-s  图片宽高比
b-%3d.jpg 格式化文件命名,会生成 b-001.jpg,b-002.jpg 等。

注意:把-ss 10放到第一个参数的位置,速度比放到放到其他位置快,且不会出现如下错误

UPDATE

更方便的保持显示比例的方法是 scale=480:-2 -2这个参数类似 -1 会自动调整宽高以维持显示比例;但是不同之处在于会把数字变成偶数,这样避免了某些解码器输入奇数报错的问题。ref: Maintaining FFMPEG aspect ratio Keeping the Aspect Ratio

批量调用ffmpeg截图

  • Windows batch file
mkdir b:\snap
for /R "G:\videodir\" %%I in (*.mkv,*mp4) do @ffmpeg -hide_banner -ss 0:7:0 -i "%%I" -vf "scale='trunc(ih*dar/2)*2:trunc(ih/2)*2',setsar=1/1" -vframes 1 -q:v 80 "b:\snap\%%~nxI.jpg" -y

简单解释: 该脚本在 G:\videodir\ 文件夹下寻找以 mkv/mp4 结尾的文件,传递给ffmpeg执行。输出的图像是根据视频 DAR 设置,输出文件名是 input.mp4.jpg 这样的格式。需要注意的是输出文件都在一个文件夹,在输入文件的 文件名 相同但 路径名 不同的情况下,会发生同文件名覆盖的问题。

  • linux shell
#!/bin/sh
mkdir 0_snap/
do_snap(){
    fname=$(basename "$1")
    ffmpeg -ss 0:8:0 -i "${fname}" -f image2 -vframes 1 0_snap/"${fname}.jpg"
}

export -f do_snap
find . -maxdepth 2 -type f -name "*.mkv" -exec bash -c 'do_snap "$0"' {} \;

简单解释:大致原理同上面 batch 批处理,根据 Unix 需要做了额外的处理。 find 命令的输出格式始终带 ./ 或者完整路径名,但是我要求输出仅仅有 文件名+后缀 ,通常的方法已不能办到。为此引入 shell function ,由于 shell function 只能被 shell 认识、执行。这就需要 export function_name 然后交由 shell 执行。

  • Windows batch file 批量截图,特定时间一定数量
@echo off
mkdir b:\snap
for /R "G:\inputdir\" %%I in (*.mkv,*mp4) do @ffmpeg -hide_banner -ss 0:10:4 -i "%%I" -vf "[in]scale='trunc(ih*dar/2)*2:trunc(ih/2)*2',setsar=1/1 [fixaspect]; [fixaspect] fps=1/2 [out]" -vframes 5 -q:v 80 "b:\snap\%%~nxI%%03d.jpg" -y

这里使用了 labeled filtergraph Example: labeled filtergraph outputs 。先把输入重设了宽度,高度保持原样并保证为偶数,设置了输出像素比例为 1:1 ( setsar=1/1 ); 然后设置了输出频率为 1/2fps 即2秒一帧。然后输出截图5张,输出后缀序列按3位数字(%03d)升序排列。 特别注意:videofileter(vf) 需要指定输入输出,其中 [in] [out] 是默认的;ffmpeg输出序列格式是 printf 类似的格式,为了得到有效的 % 要在 % 前面使用 % 来转义它,最终需要写 %%03d 来得到有效输入 %03d


为了方便检查结果,给每个被处理视频文件添加一个处理前缀序号

@echo off
mode con cols=200 lines=2500
set outN=8
set outdir=e:\snap0%outN%
IF NOT EXIST %outdir% MD %outdir%
setlocal enabledelayedexpansion
set n=1000
for /R "G:\inputdir\" %%I in (*.mkv,*mp4) do (
set /a n+=1
title !n:~-3!
echo !n:~-3!-%%~fsI > b:\ffmpeg_processing.txt
cd /d "%%~dpI"
@ffmpeg -hide_banner -ss 0:5:24 -hwaccel nvdec -i "%%~nxI" -f image2 -vf "fps=1/10,scale='w=480:trunc(ow/dar/2)*2:flags=lanczos',setsar=1/1" -vframes 5 -q:v 90 "%outdir%\%%~nxI-%%03d.jpg" -y
if !errorlevel! neq 0 echo !n:~-3!-%%~fsI >> "%outdir%\ffmpeg_err.txt"
if !errorlevel! neq 0 echo !n:~-3!-%%~fsI >> b:\ffmpeg_err.txt
)

这里使用了优化的 scale 命令,直接设置了输出宽度,并设置输出高度根据输出宽度和 DAR 匹配并保证为偶数。为了添加序号,需要在处理每个文件之前生成序列号。生成序号使用 set /a 功能进行计算。由于批处理中 for 语句 do 后面括号内的内容被视为一行指令,变量 n 自增的结果并不会立即应用给ffmpeg的参数,为了能够立即使用需要启用 setlocal enabledelayedexpansion 功能。而读取延迟变量使用 !n! 的方式,这里取其最后3位数字。最后得到了类似 006-input.mkv-005.jpg 这样的文件名。

参考:

批处理产生001、002序列数字的文件名

批处理中setlocal enabledelayedexpansion的作用详细整理

批量转换音频

  • 至原文件夹
find input_dir -maxdepth 1 -type f -name "*.flac" -exec ffmpeg -hide_banner -i {} -t 10 -c:a aac -q:a 1.2 {}.m4a -y \;

简要解释:从输入文件夹 input_dir 寻找 flac 文件,寻找深度为 1 ,这意味着不会寻找子文件夹。找到的文件被传递给 ffmpeg 转码,转码长度 10s ,输出格式 aac 使用了 ffmpeg buildin aac 编码器,输出质量 q 范围 0.1~2 ,这里设置为 1.2 ,输出到原文件夹并且名字是 input.flac.m4a 这样的格式。

  • 至原文件夹不带源文件后缀名
find inputdir -maxdepth 1 -type f -name "*.flac" -exec bash -c 'fin="$0";ffmpeg -hide_banner -i "$0" -t 10 -c:a aac -q:a 1.2 "${fin%.*}.m4a"' {} \;

简要解释:输出的设置跟上面的类似,为了获取不带后缀名的文件名,使用了 Shell Parameter Expansion , 格式 ${FILE%.*} 是非贪婪匹配,仅仅消除最后一个 . 右侧的字符串。 ${FILE%%.*} 是贪婪匹配会消除如 filename.tar.gz 中的后2位,结果是 filename . 或者用 Shell Parameter Expansion 的字符串替换也可以。需要特别注意的是 shell 中是 大小写敏感 的。

find inputdir -maxdepth 1 -type f -name "*.flac" -exec bash -c 'fin="$0";ffmpeg -hide_banner -i "$0" -t 10 -c:a aac -q:a 1.2 "${fin/%.flac/.m4a}"' {} \;
  • 至当前文件夹不带源文件后缀名

如果要不带 .flac 的输出使用 basename $input .flac 这样的格式,如下。由于使用了 basename , 不会转换到原文件夹下,需要指定输出文件夹

find input_dir -maxdepth 1 -type f -name "*.flac" -exec ffmpeg -hide_banner -i {} -t 10 -c:a aac -q:a 1.2 outdir/$(basename {} .flac).m4a -y \;

添加水印

  • 水印局中
ffmpeg -i out.mp4 -i sxyx2008@163.com.gif -filter_complex overlay="(main_w/2)-(overlay_w/2):(main_h/2)-(overlay_h)/2" output.mp4

参数解释

-i out.mp4(视频源)
-i sxyx2008@163.com.gif(水印图片)
overlay 水印的位置
output.mp4 输出文件

翻转和旋转

翻转

水平翻转语法: -vf hflip

ffplay -i out.mp4 -vf hflip

垂直翻转语法:-vf vflip

ffplay -i out.mp4 -vf vflip

旋转

语法:transpose={0,1,2,3}

0:逆时针旋转90°然后垂直翻转

1:顺时针旋转90°

2:逆时针旋转90°

3:顺时针旋转90°然后水平翻转

将视频顺时针旋转90度

ffplay -i out.mp4 -vf transpose=1

将视频水平翻转(左右翻转)

ffplay -i out.mp4 -vf hflip

顺时针旋转90度并水平翻转

ffplay -i out.mp4 -vf transpose=1,hflip

添加字幕

有的时候你需要给视频加一个字幕(subtitle),使用ffmpeg也可以做。一般我们见到的字幕以srt字幕为主,在ffmpeg里需要首先将srt字幕转化为ass字幕,然后就可以集成到视频中了(不是单独的字幕流,而是直接改写视频流)。

ffmpeg -i my_subtitle.srt my_subtitle.ass
ffmpeg -i inputfile.mp4 -vf ass=my_subtitle.ass outputfile.mp4

但是值得注意的是:

my_subtitle.srt需要使用UTF8编码,老外不会注意到这一点,但是中文这是必须要考虑的;

将字幕直接写入视频流需要将每个字符渲染到画面上,因此有一个字体的问题,在ass文件中会指定一个缺省字体,例如Arial,但是我们首先需要让ffmpeg能找到字体文件,不然文字的渲染就无从谈起了。ffmpeg使用了fontconfig来设置字体配置。你需要首先设置一下FONTCONFIG_PATH或者FONTCONFIG_FILE环境变量,不然fontconfig是无法找到配置文件的,这一点请参看这篇文章,如果你设置的是FONTCONFIG_PATH,那把配置文件保存为%FONTCONFIG_PATH%/font.conf即可,然后你可以在font.conf文件中配置字体文件的路径之类的。

Windows下为fontconfig设置如下的环境变量

FC_CONFIG_DIR=C:\ffmpeg
FONTCONFIG_FILE=font.conf
FONTCONFIG_PATH=C:\ffmpeg
PATH=C:\ffmpeg\bin;%PATH%

下面是一个简单的Windows版font.conf文件。

<?xml version="1.0"?>
<fontconfig>

<dir>C:\WINDOWS\Fonts</dir>

<match target="pattern">
   <test qual="any" name="family"><string>mono</string></test>
   <edit name="family" mode="assign"><string>monospace</string></edit>
</match>

<match target="pattern">
   <test qual="all" name="family" mode="not_eq"><string>sans-serif</string></test>
   <test qual="all" name="family" mode="not_eq"><string>serif</string></test>
   <test qual="all" name="family" mode="not_eq"><string>monospace</string></test>
   <edit name="family" mode="append_last"><string>sans-serif</string></edit>
</match>

<alias>
   <family>Times</family>
   <prefer><family>Times New Roman</family></prefer>
   <default><family>serif</family></default>
</alias>
<alias>
   <family>Helvetica</family>
   <prefer><family>Arial</family></prefer>
   <default><family>sans</family></default>
</alias>
<alias>
   <family>Courier</family>
   <prefer><family>Courier New</family></prefer>
   <default><family>monospace</family></default>
</alias>
<alias>
   <family>serif</family>
   <prefer><family>Times New Roman</family></prefer>
</alias>
<alias>
   <family>sans</family>
   <prefer><family>Arial</family></prefer>
</alias>
<alias>
   <family>monospace</family>
   <prefer><family>Andale Mono</family></prefer>
</alias>
<match target="pattern">
   <test name="family" mode="eq">
      <string>Courier New</string>
   </test>
   <edit name="family" mode="prepend">
      <string>monospace</string>
   </edit>
</match>
<match target="pattern">
   <test name="family" mode="eq">
      <string>Courier</string>
   </test>
   <edit name="family" mode="prepend">
      <string>monospace</string>
   </edit>
</match>

</fontconfig>

下面这个是Linux系统下改版过来的

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<!-- /etc/fonts/fonts.conf file to configure system font access -->
<fontconfig>
<!-- 
	Find fonts in these directories
-->
<dir>C:/Windows/Fonts</dir>
<!--
<dir>/usr/X11R6/lib/X11/fonts</dir>
-->
<!--
	Accept deprecated 'mono' alias, replacing it with 'monospace'
-->
<match target="pattern">
	<test qual="any" name="family"><string>mono</string></test>
	<edit name="family" mode="assign"><string>monospace</string></edit>
</match>

<!--
	Load per-user customization file, but don't complain
	if it doesn't exist
-->
<include ignore_missing="yes" prefix="xdg">fontconfig/fonts.conf</include>

<!--
	Load local customization files, but don't complain
	if there aren't any
-->
<include ignore_missing="yes">conf.d</include>
<include ignore_missing="yes">local.conf</include>

<!--
	Alias well known font names to available TrueType fonts.
	These substitute TrueType faces for similar Type1
	faces to improve screen appearance.
-->
<alias>
	<family>Times</family>
	<prefer><family>Times New Roman</family></prefer>
	<default><family>serif</family></default>
</alias>
<alias>
	<family>Helvetica</family>
	<prefer><family>Arial</family></prefer>
	<default><family>sans</family></default>
</alias>
<alias>
	<family>Courier</family>
	<prefer><family>Courier New</family></prefer>
	<default><family>monospace</family></default>
</alias>

<!--
	Provide required aliases for standard names
	Do these after the users configuration file so that
	any aliases there are used preferentially
-->
<alias>
	<family>serif</family>
	<prefer><family>Times New Roman</family></prefer>
</alias>
<alias>
	<family>sans</family>
	<prefer><family>Arial</family></prefer>
</alias>
<alias>
	<family>monospace</family>
	<prefer><family>Andale Mono</family></prefer>
</alias>
</fontconfig>

http://blog.raphaelzhang.com/2013/04/video-streaming-and-ffmpeg-transcoding/

嵌入字幕

在一个MP4文件里面添加字幕,不是把 .srt 字幕文件集成到 MP4 文件里,而是在播放器里选择字幕,这种集成字幕比较简单,速度也相当快

ffmpeg -i input.mp4 -i subtitles.srt -c:s mov_text -c:v copy -c:a copy output.mp4

希望字幕直接显示出来,其实也不难

ffmpeg -i subtitle.srt subtitle.ass
ffmpeg -i input.mp4 -vf ass=subtitle.ass output.mp4

http://blog.neten.de/posts/2013/10/06/use-ffmpeg-to-burn-subtitles-into-the-video/

截图

每隔一秒截一张图

ffmpeg -i out.mp4 -f image2 -vf fps=fps=1 out%d.png

每隔20秒截一张图

ffmpeg -i out.mp4 -f image2 -vf fps=fps=1/20 out%d.png

多张截图合并到一个文件里(2x3)每隔一千帧(秒数=1000/fps25)即40s截一张图

ffmpeg -i out.mp4 -frames 3 -vf "select=not(mod(n\,1000)),scale=320:240,tile=2x3" out.png

从视频中生成GIF图片

ffmpeg -i out.mp4 -t 10 -pix_fmt rgb24 out.gif

转换视频为图片(每帧一张图)

ffmpeg -i out.mp4 out%4d.png

图片转换为视频

ffmpeg -f image2 -i out%4d.png -r 25 video.mp4

添加字幕

ffmpeg -i out.mp4 -vf subtitles=out.srt output.mp4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment