FFmpeg 简单用法
FFmpeg 是视频处理最常用的开源软件。
它功能强大,用途广泛,大量用于视频网站和商业软件(比如 Youtube 和 iTunes),也是许多音频和视频格式的标准编码/解码实现。
安装
最简单的方法就是用包管理工具如:apt 安装:
apt update
apt install ffmpeg
或者也可以从源码安装,可以参考我之前的教程:https://blog.niekun.net/archives/891.html
常用指令
查看 ffmpeg 版本:
ffmpeg -version
查看支持的编码格式:如 h.264, h.265
ffmpeg -codecs
查看支持的容器:如 mp4, mp3, mkv
ffmpeg -formats
查看已安装的编码器:如 libx264, libx265, libvpx, aac
ffmpeg -encoders
使用格式
FFmpeg 的命令行参数非常多,可以分成五个部分。
ffmpeg {1} {2} -i {3} {4} {5}
上面命令中,五个部分的参数依次如下:
全局参数
输入文件参数
输入文件
输出文件参数
输出文件
参数太多的时候,为了便于查看,ffmpeg 命令可以写成多行:
$ ffmpeg \
[全局参数] \
[输入文件参数] \
-i [输入文件] \
[输出文件参数] \
[输出文件]
下面是一个例子:
ffmpeg \
-y \ # 全局参数
-c:a libfdk_aac -c:v libx264 \ # 输入文件参数
-i input.mp4 \ # 输入文件
-c:v libvpx-vp9 -c:a libvorbis \ # 输出文件参数
output.webm # 输出文件
上面的命令将 mp4 文件转成 webm 文件,这两个都是容器格式。输入的 mp4 文件的音频编码格式是 aac,视频编码格式是 H.264;输出的 webm 文件的视频编码格式是 VP9,音频格式是 Vorbis。
如果不指明编码格式,FFmpeg 会自己判断输入文件的编码。一般可以省略输入文件参数。
常用命令参数
-c:指定编码器
-c copy:直接复制,不经过重新编码(这样比较快)
-c:v:指定视频编码器
-c:a:指定音频编码器
-i:指定输入文件
-an:去除音频流
-vn: 去除视频流
-preset:指定输出的视频质量,会影响文件的生成速度,有以下几个可用的值 ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow。
-y:不经过确认,输出时直接覆盖同名文件。
常规使用方法
查看元数据信息,如时长,比特率等:
ffmpeg -i test.mp4
输出的信息较多,可以通过 -hide_banner 只显示媒体文件信息:
ffmpeg -i test.mp4 -hide_banner
转码,如 avi to h.264:
ffmpeg -i test.avi -c:v libx264 test.mp4
转换容器:
ffmpeg -i test.mp4 -c copy test.webm
转换容器不需要转码,所以直接 copy 即可。
转换码率,转换成固定码率:
ffmpeg -i test.mp4 -b:v 500k test_out.mp4
转换码率,转换成一个码率范围:
ffmpeg -i test.mp4 -minrate 964K -maxrate 3856K -bufsize 2000K test_out.mp4
改变分辨率:转换成 480p
ffmpeg \
-i input.mp4 \
-vf scale=480:-1 \
output.mp4
视频中提取音频:
ffmpeg \
-i input.mp4 \
-vn -c:a copy \
output.aac
上面例子中,-vn
表示去掉视频,-c:a copy
表示不改变音频编码,直接拷贝。
视频截图:下面的例子是从指定时间开始,连续对1秒钟的视频进行截图
ffmpeg \
-y \
-i input.mp4 \
-ss 00:01:24 -t 00:00:01 \
output_%3d.jpg
%3d
在 shell 里表示至少输出3个字符空间的数字:
% means "Print a variable here"
3 means "use at least 3 spaces to display, padding as needed"
d means "The variable will be an integer"
如果只需要截一张图,可以指定只截取一帧。
$ ffmpeg \
-ss 01:23:45 \
-i input \
-vframes 1 -q:v 2 \
output.jpg
上面例子中,-vframes 1
指定只截取一帧,-q:v 2
表示输出的图片质量,一般是1到5之间(1 为质量最高)。
裁剪:
裁剪(cutting)指的是,截取原始视频里面的一个片段,输出为一个新视频。可以指定开始时间(start)和持续时间(duration),也可以指定结束时间(end)。
$ ffmpeg -ss [start] -i [input] -t [duration] -c copy [output]
$ ffmpeg -ss [start] -i [input] -to [end] -c copy [output]
下面是实际的例子。
# 从1分50秒开始截取10.5秒
ffmpeg -ss 00:01:50 -i test.mp4 -t 10.5 -c copy out.mp4
# 从25秒开始截取10秒
ffmpeg -ss 25 -i test.mp4 -to 10 -c copy out.mp4
ffmpeg -i test.mp4 -ss 25 -to 10 -c copy out.mp4
上面例子中,-c copy
表示不改变音频和视频的编码格式,直接拷贝,这样会快很多。
高级用法
压缩视频内容到指定容量大小
使用的技术主要是 ffmpeg 的 2 pass 方法和 ffprobe 得到码率和时长信息。
bash脚本:
#!/bin/bash
target_video_size_MB="$2"
origin_duration_s=$(ffprobe -v error -show_streams -select_streams a "$1" | grep -Po "(?<=^duration\=)\d*\.\d*")
origin_audio_bitrate_kbit_s=$(ffprobe -v error -pretty -show_streams -select_streams a "$1" | grep -Po "(?<=^bit_rate\=)\d*\.\d*")
target_audio_bitrate_kbit_s=$origin_audio_bitrate_kbit_s # TODO for now, make audio bitrate the same
target_video_bitrate_kbit_s=$(\
awk \
-v size="$target_video_size_MB" \
-v duration="$origin_duration_s" \
-v audio_rate="$target_audio_bitrate_kbit_s" \
'BEGIN { print ( ( size * 8192.0 ) / ( 1.048576 * duration ) - audio_rate ) }')
ffmpeg \
-y \
-i "$1" \
-c:v libx264 \
-b:v "$target_video_bitrate_kbit_s"k \
-pass 1 \
-an \
-f mp4 \
/dev/null \
&& \
ffmpeg \
-i "$1" \
-c:v libx264 \
-b:v "$target_video_bitrate_kbit_s"k \
-pass 2 \
-c:a aac \
-b:a "$target_audio_bitrate_kbit_s"k \
"${1%.*}-$2mB.mp4"
使用方法:压缩视频到 50 MB 大小
./script.sh test.mp4 50
切割视频到指定时长的多个视频
使用的技术主要是 python,ffprobe 得到视频时长,然后计算需要切割为几个视频。
python 脚本:
#!/usr/bin/env python
import csv
import subprocess
import math
import json
import os
import shlex
from optparse import OptionParser
def split_by_manifest(filename, manifest, vcodec="copy", acodec="copy",
extra="", **kwargs):
if not os.path.exists(manifest):
print("File does not exist: %s" % manifest)
raise SystemExit
with open(manifest) as manifest_file:
manifest_type = manifest.split(".")[-1]
if manifest_type == "json":
config = json.load(manifest_file)
elif manifest_type == "csv":
config = csv.DictReader(manifest_file)
else:
print("Format not supported. File must be a csv or json file")
raise SystemExit
split_cmd = ["ffmpeg", "-i", filename, "-vcodec", vcodec,
"-acodec", acodec, "-y"] + shlex.split(extra)
try:
fileext = filename.split(".")[-1]
except IndexError as e:
raise IndexError("No . in filename. Error: " + str(e))
for video_config in config:
split_str = ""
split_args = []
try:
split_start = video_config["start_time"]
split_length = video_config.get("end_time", None)
if not split_length:
split_length = video_config["length"]
filebase = video_config["rename_to"]
if fileext in filebase:
filebase = ".".join(filebase.split(".")[:-1])
split_args += ["-ss", str(split_start), "-t",
str(split_length), filebase + "." + fileext]
print("########################################################")
print("About to run: "+" ".join(split_cmd+split_args))
print("########################################################")
subprocess.check_output(split_cmd+split_args)
except KeyError as e:
print("############# Incorrect format ##############")
if manifest_type == "json":
print("The format of each json array should be:")
print("{start_time: <int>, length: <int>, rename_to: <string>}")
elif manifest_type == "csv":
print("start_time,length,rename_to should be the first line ")
print("in the csv file.")
print("#############################################")
print(e)
raise SystemExit
def get_video_length(filename):
output = subprocess.check_output(("ffprobe", "-v", "error", "-show_entries",
"format=duration", "-of", "default=noprint_wrappers=1:nokey=1", filename)).strip()
video_length = int(float(output))
print("Video length in seconds: "+str(video_length))
return video_length
def ceildiv(a, b):
return int(math.ceil(a / float(b)))
def split_by_seconds(filename, split_length, vcodec="copy", acodec="copy",
extra="", video_length=None, **kwargs):
if split_length and split_length <= 0:
print("Split length can't be 0")
raise SystemExit
if not video_length:
video_length = get_video_length(filename)
split_count = ceildiv(video_length, split_length)
if(split_count == 1):
print("Video length is less then the target split length.")
raise SystemExit
split_cmd = ["ffmpeg", "-i", filename, "-vcodec",
vcodec, "-acodec", acodec] + shlex.split(extra)
try:
filebase = ".".join(filename.split(".")[:-1])
fileext = filename.split(".")[-1]
except IndexError as e:
raise IndexError("No . in filename. Error: " + str(e))
for n in range(0, split_count):
split_args = []
if n == 0:
split_start = 0
else:
split_start = split_length * n
split_args += ["-ss", str(split_start), "-t", str(split_length),
filebase + "-" + str(n+1) + "-of-" +
str(split_count) + "." + fileext]
print("About to run: "+" ".join(split_cmd+split_args))
subprocess.check_output(split_cmd+split_args)
def main():
parser = OptionParser()
parser.add_option("-f", "--file",
dest="filename",
help="File to split, for example sample.avi",
type="string",
action="store"
)
parser.add_option("-s", "--split-size",
dest="split_length",
help="Split or chunk size in seconds, for example 10",
type="int",
action="store"
)
parser.add_option("-c", "--split-chunks",
dest="split_chunks",
help="Number of chunks to split to",
type="int",
action="store"
)
parser.add_option("-S", "--split-filesize",
dest="split_filesize",
help="Split or chunk size in bytes (approximate)",
type="int",
action="store"
)
parser.add_option("--filesize-factor",
dest="filesize_factor",
help="with --split-filesize, use this factor in time to"
" size heuristics [default: %default]",
type="float",
action="store",
default=0.95
)
parser.add_option("--chunk-strategy",
dest="chunk_strategy",
help="with --split-filesize, allocate chunks according to"
" given strategy (eager or even)",
type="choice",
action="store",
choices=['eager', 'even'],
default='eager'
)
parser.add_option("-m", "--manifest",
dest="manifest",
help="Split video based on a json manifest file. ",
type="string",
action="store"
)
parser.add_option("-v", "--vcodec",
dest="vcodec",
help="Video codec to use. ",
type="string",
default="copy",
action="store"
)
parser.add_option("-a", "--acodec",
dest="acodec",
help="Audio codec to use. ",
type="string",
default="copy",
action="store"
)
parser.add_option("-e", "--extra",
dest="extra",
help="Extra options for ffmpeg, e.g. '-e -threads 8'. ",
type="string",
default="",
action="store"
)
(options, args) = parser.parse_args()
def bailout():
parser.print_help()
raise SystemExit
if not options.filename:
bailout()
if options.manifest:
split_by_manifest(**(options.__dict__))
else:
video_length = None
if not options.split_length:
video_length = get_video_length(options.filename)
file_size = os.stat(options.filename).st_size
split_filesize = None
if options.split_filesize:
split_filesize = int(
options.split_filesize * options.filesize_factor)
if split_filesize and options.chunk_strategy == 'even':
options.split_chunks = ceildiv(file_size, split_filesize)
if options.split_chunks:
options.split_length = ceildiv(
video_length, options.split_chunks)
if not options.split_length and split_filesize:
options.split_length = int(
split_filesize / float(file_size) * video_length)
if not options.split_length:
bailout()
split_by_seconds(video_length=video_length, **(options.__dict__))
if __name__ == '__main__':
main()
使用方法:将视频切割为单个视频100秒
./split.py -f test.mp4 -s 100
ffprobe 使用
ffprobe 可以用来得到视频信息。
视频时长:秒
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4
视频码率:bit
ffprobe -v error -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1 input.mp4
参考链接
http://www.ruanyifeng.com/blog/2020/01/ffmpeg.html
https://stackoverflow.com/questions/29082422/ffmpeg-video-compression-specific-file-size
https://github.com/c0decracker/video-splitter
https://trac.ffmpeg.org/wiki/FFprobeTips
标签:无