FFmpeg 实用命令
剪切视频而不重新转码1
$ ffmpeg -ss 00:00:30.0 -to 00:00:40.0 -i input.mp4 -c copy output.mp4
-ss
和 -to
分别指定起始时间。
这种方式输出的视频有一定问题,最好还是转码(不要用 -c copy
)。
codec not currently supported in container2
一些视频流和音频流无法放到同一容器中,例如 WMA 音频不能与 H.264 视频兼容,解决方式是将音频也转码:
$ ffmpeg -i input.wmv -c:v libx264 -c:a aac output.mp4
旋转视频而不转码3
逆时针旋转 90 度:
$ ffmpeg -display_rotation 90 -i input.mp4 -c copy output.mp4
裁切视频4
$ ffmpeg -i input.mp4 -vf "crop=out_w:out_h:x:y" -c:a copy output.mp4
crop=
后以 :
分隔的四个参数分别表示:输出宽度、输出高度、左上角横坐标、左上角纵坐标。
循环重复5
循环重复 3 次:
$ ffmpeg -stream_loop 3 -i input.mp4 -c copy output.mp4
合并多个同类文件而不转码6
使用一个文本保存需合并的文件路径,如 input.txt
:
file 'path/to/part1.mp4'
file 'path/to/part2.mp4'
file 'path/to/part3.mp4'
然后:
$ ffmpeg -f concat -i input.txt -c copy output.mp4
去隔行扫描7
$ ffmpeg -i input.vob -vf yadif -c:a copy output.mp4
视频加速/减速8
加速两倍:
$ ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" output.mp4
减速一半:
$ ffmpeg -i input.mp4 -vf "setpts=2*PTS" output.mp4
视频反转9
仅反转视频:
$ ffmpeg -i input.mp4 -vf reverse output.mp4
同时反转视频和音频:
$ ffmpeg -i input.mp4 -vf reverse -af areverse output.mp4
hev1 转 hvc110
一些 HEVC 在 macOS 上无法显示缩略图,也无法用原生 APP 打开,这是因为使用了 hev1:
$ ffprobe -v error -select_streams v -show_entries stream=codec_tag_string -of default=noprint_wrappers=1:nokey=1 input.mp4
hev1
转换为 hvc1 即可:
$ ffmpeg -i input.mp4 -tag:v hvc1 -c copy output.mp4
$ ffprobe -v error -select_streams v -show_entries stream=codec_tag_string -of default=noprint_wrappers=1:nokey=1 output.mp4
hvc1
脚本
列出码率最高的视频
默认列出前 10 个,不支持含换行符的文件名。
使用 -v
选项以首个视频流码率列出(而不是总体码率),但不支持 MKV、TS、WebM 等容器。
#!/usr/bin/env sh
BRIEF=false
VIDEO_EXT="3gp avi flv m4v mkv mov mp4 mpeg mpg ts vob webm wmv"
NUM=10
VIDEO_BITRATE=false
SCRIPT=$(basename "$0")
USAGE=$(
cat <<-END
Usage: $SCRIPT [<options>] <path>...
List top videos by bitrate.
-b do not prepend bitrate to output lines
-e <ext> video file extensions, separated by spaces, case-insensitive
defaults to '$VIDEO_EXT'
-n <num> list top num video files
defaults to '$NUM'
-v list by the first video stream bitrate instead of the overall bitrate
not applicable for containers like MKV, TS, WebM, etc
-h display this help and exit
Home page: <https://binac.org/posts/video-related-commands-and-scripts/>
END
)
error() { printf "%s\n" "$@" >&2; }
_exit() {
error "$USAGE"
exit 2
}
while getopts "bhve:n:" c; do
case $c in
b) BRIEF=true ;;
e) VIDEO_EXT=$OPTARG ;;
n) NUM=$OPTARG ;;
v) VIDEO_BITRATE=true ;;
h) error "$USAGE" && exit ;;
*) _exit ;;
esac
done
is_integer() {
[ -n "$1" ] && [ "$1" -eq "$1" ] 2>/dev/null
}
is_integer "$NUM" || _exit
shift $((OPTIND - 1))
[ $# -eq 0 ] && _exit
min() {
[ "$1" -le "$2" ] && echo "$1" || echo "$2"
}
get_bitrate() {
if [ "$VIDEO_BITRATE" = false ]; then
ffprobe -v error -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1 "$1"
else
ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1:nokey=1 "$1" | head -n 1
fi
}
TMP_FILE=$(mktemp) || exit 1
trap 'rm -f "$TMP_FILE"' EXIT
count=0
grep_expression="$(echo " $VIDEO_EXT" | sed 's/ /\$|\\./g' | cut -c3-)$"
find "$@" -type f | grep -i -E "$grep_expression" | while IFS= read -r f; do
: $((count += 1))
printf "Detecting videos: %s\r" "$count" >&2
br=$(get_bitrate "$f")
if is_integer "$br"; then
echo "$(numfmt --to iec "$br")|$f" >>"$TMP_FILE"
else
printf "\n%s\n" "Cannot get bitrate: $f" >&2
fi
done
count=$(wc -l <"$TMP_FILE" | xargs)
printf "\n" >&2
if [ "$count" -eq 0 ]; then
error "No videos found"
exit
else
error "Detected $count videos"
fi
error "Top $(min "$NUM" "$count") videos by bitrate:"
[ "$BRIEF" = true ] || printf "%-16s%s\n" "Bitrate" "File"
sort -t '|' -k1 -hr "$TMP_FILE" | head -n "$NUM" | while IFS= read -r line; do
br=$(echo "${line%%|*}" | xargs)
f=${line#*|}
[ "$BRIEF" = true ] && echo "$f" || printf "%-16s%s\n" "$br" "$f"
done
示例:
$ top-videos-by-bitrate.sh -n 3 /path/to/videos
Detecting videos: 100
Detected 100 videos
Top 3 videos by bitrate:
Bitrate File
8.8M /path/to/videos/1.mp4
4.2M /path/to/videos/2/3.avi
3.8M /path/to/videos/4/5/6.mkv
列出占用空间最大的视频
使用 du 命令检测磁盘占用空间(而不是实际大小)。
默认列出前 10 个,不支持含换行符的文件名。
#!/usr/bin/env sh
BRIEF=false
VIDEO_EXT="3gp avi flv m4v mkv mov mp4 mpeg mpg ts vob webm wmv"
NUM=10
SCRIPT=$(basename "$0")
USAGE=$(
cat <<-END
Usage: $SCRIPT [<options>] <path>...
List top videos by size.
-b do not prepend size to output lines
-e <ext> video file extensions, separated by spaces, case-insensitive
defaults to '$VIDEO_EXT'
-n <num> list top num video files
defaults to '$NUM'
-h display this help and exit
Home page: <https://binac.org/posts/video-related-commands-and-scripts/>
END
)
error() { printf "%s\n" "$@" >&2; }
_exit() {
error "$USAGE"
exit 2
}
while getopts "bhe:n:" c; do
case $c in
b) BRIEF=true ;;
e) VIDEO_EXT=$OPTARG ;;
n) NUM=$OPTARG ;;
h) error "$USAGE" && exit ;;
*) _exit ;;
esac
done
is_integer() {
[ -n "$1" ] && [ "$1" -eq "$1" ] 2>/dev/null
}
is_integer "$NUM" || _exit
shift $((OPTIND - 1))
[ $# -eq 0 ] && _exit
min() {
[ "$1" -le "$2" ] && echo "$1" || echo "$2"
}
TMP_FILE=$(mktemp) || exit 1
trap 'rm -f "$TMP_FILE"' EXIT
grep_expression="$(echo " $VIDEO_EXT" | sed 's/ /\$|\\./g' | cut -c3-)$"
find "$@" -type f -exec du -h {} + | grep -i -E "$grep_expression" | sort -h -r >"$TMP_FILE"
count=$(wc -l <"$TMP_FILE" | xargs)
if [ "$count" -eq 0 ]; then
error "No videos found"
exit
else
error "Detected $count videos"
fi
error "Top $(min "$NUM" "$count") videos by size:"
[ "$BRIEF" = true ] || printf "%-16s%s\n" "Size" "File"
head -n "$NUM" "$TMP_FILE" | while IFS= read -r line; do
sz=$(echo "${line%% *}" | xargs)
f=${line#* }
[ "$BRIEF" = true ] && echo "$f" || printf "%-16s%s\n" "$sz" "$f"
done
示例:
$ top-videos-by-size.sh -n 3 /path/to/videos
Detected 100 videos
Top 3 videos by size:
Size File
4.2G /path/to/videos/1.mp4
420M /path/to/videos/2/3.avi
42M /path/to/videos/4/5/6.mkv
HEVC 转码
将文件批量转为 HEVC 编码,只转码首条视频流,其他视频流、音频流、字幕流原封不动。
默认会忽略本身已经是 hevc/av1/vp9 编码的文件,因为这些编码已经效率很高了。使用 -c <codecs>
指定需要忽略的编码。
#!/usr/bin/env sh
ARGUMENTS=
CODEC_WHITELIST="hevc av1 vp9"
AVAILABLE_ENCODERS=$(ffmpeg -codecs 2>/dev/null | grep '^.\{8\}hevc' | sed -n 's/.*(encoders: \([^)]*\)).*/\1/p' | xargs)
ENCODER=libx265
OUTPUT_DIR=
SCRIPT=$(basename "$0")
USAGE=$(
cat <<-END
Usage: $SCRIPT [<options>] <file>...
List top videos by size.
-a <args> pass extra arguments to ffmpeg
-c <codecs> videos with these codecs will NOT be encoded
defaults to '$CODEC_WHITELIST'
-e <encoder> set HEVC encoder, this could accelerate transcoding but reduce the quality
you may need to use '-a <args>'
available encoders: $AVAILABLE_ENCODERS
defaults to '$ENCODER'
-o <dir> set output directory
by default, the output file will be placed in the same directory as the original video
-h display this help and exit
Home page: <https://binac.org/posts/video-related-commands-and-scripts/>
END
)
THIN_LINE=$(printf '%.s-' $(seq 1 80))
BOLD_LINE=$(printf '%.s=' $(seq 1 80))
error() { printf "%s\n" "$@" >&2; }
_exit() {
error "$USAGE"
exit 2
}
while getopts "ha:c:e:o:" c; do
case $c in
a) ARGUMENTS=$OPTARG ;;
c) CODEC_WHITELIST=$OPTARG ;;
e) ENCODER=$OPTARG ;;
o) OUTPUT_DIR=$OPTARG ;;
h) error "$USAGE" && exit ;;
*) _exit ;;
esac
done
contains() {
echo "$1" | grep -qs "\<$2\>"
}
if ! contains "$AVAILABLE_ENCODERS" "$ENCODER"; then
error "Invalid encoder: '$ENCODER'"
_exit
fi
if [ -z "$ARGUMENTS" ] && [ "$ENCODER" = libx265 ]; then
ARGUMENTS="-x265-params log-level=error"
fi
if [ -n "$OUTPUT_DIR" ]; then
if [ ! -d "$OUTPUT_DIR" ] || [ ! -w "$OUTPUT_DIR" ]; then
error "Cannot access: '$OUTPUT_DIR'"
exit 1
fi
OUTPUT_DIR=$(realpath "$OUTPUT_DIR")
fi
shift $((OPTIND - 1))
[ $# -eq 0 ] && _exit
do_ffmpeg() {
if [ -f "$2" ]; then
error "File already exists: '$2'"
return 0
fi
if ffmpeg -nostdin -v error -hide_banner -stats -i "$1" -map 0 -c copy -c:v:0 "$ENCODER" $ARGUMENTS -tag:v:0 hvc1 "$2"; then
error "==> '$2'"
else
rm -f "$2"
return 1
fi
}
FAIL_LIST=$(mktemp) || exit 1
trap 'rm -f "$FAIL_LIST"' EXIT
count=1
total="$#"
for f in "$@"; do
error "$THIN_LINE"
error "==> $count/$total: '$f'"
: $((count += 1))
vc=$(ffprobe "$f" -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 | head -n 1)
if [ -z "$vc" ]; then
error "Unknown input: '$f'"
echo "$f" >>"$FAIL_LIST"
continue
fi
if contains "$CODEC_WHITELIST" "$vc"; then
error "Skipping codec: '$vc'"
continue
fi
if [ -n "$OUTPUT_DIR" ]; then
output="$OUTPUT_DIR/$(basename "$f").HEVC.mp4"
else
output="$f.HEVC.mp4"
fi
do_ffmpeg "$f" "$output" && continue
error "Unable to use MP4 container, retrying with MKV"
output="${output%.*}.mkv"
do_ffmpeg "$f" "$output" && continue
error "Unable to use MKV container"
error "Skipping file: '$f'"
echo "$f" >>"$FAIL_LIST"
done
if [ -s "$FAIL_LIST" ]; then
error "$BOLD_LINE"
error "Unable to encode the following $(wc -l <"$FAIL_LIST" | xargs) files:"
cat "$FAIL_LIST" >&2
exit 1
fi
已知问题
一些视频除了视频流、音频流、字幕流外,还带有数据流、附件流,这些流可能无法在转码后的容器中支持(即使转码前后容器相同)11。以下是一个容器无法支持数据流的报错:
[mp4 @ 0x55af0c59d0c0] Could not find tag for codec none in stream #3, codec not currently supported in container
Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
Error initializing output stream 0:0 --
Unable to use MP4 container, retrying with MKV
[matroska @ 0x55ceccef70c0] Only audio, video, and subtitles are supported for Matroska.
Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
Error initializing output stream 0:0 --
此时可以手动转码并使用 -map -0:d
丢弃数据流:
$ ffmpeg -i input.mp4 -map 0 -map -0:d -c copy -c:v libx265 output.mp4
另外,由于脚本只使用 MP4 和 MKV 尝试,也可能存在不支持的音频流和字幕流等12,此时也可以按需转码或丢弃,例如音频转码 -c:a acc
,丢弃字幕 -map -0:s
。
示例
批量转码
支持通配符:
$ hevcize.sh /path/to/videos/1.mp4 /path/to/videos/2/3.avi /path/to/videos/4/5/6.mkv
$ hevcize.sh /path/to/videos/*.mp4
将 /path/to/videos
下所有文件进行转码,结果保存在当前目录:
$ find /path/to/videos -type f -exec hevcize.sh -o . {} +
显卡加速
比如 NVIDIA 显卡可以使用 hevc_nvenc 编码器进行加速,Intel 显卡使用 hevc_qsv,这些都需要硬件和驱动支持。
macOS 使用 hevc_videotoolbox 编码器进行显卡加速(hevcize.sh -h
查看有哪些可用编码器):
$ hevecize.sh -e hevc_videotoolbox /path/to/videos/1.mp4
--------------------------------------------------------------------------------
==> 1/1: '/path/to/videos/1.mp4'
frame= 8000 fps= 80 q=-0.0 Lsize= 15000kB time=00:05:00.00 bitrate= 500.0kbits/s dup=33 drop=23 speed=3.00x
==> '/path/to/videos/1.mp4.HEVC.mp4'
显卡加速转码很快,CPU 占用率低,但比起默认的 libx265(CPU 软编码)质量较差。
通过 -a <args>
可以传递更多参数给 ffmpeg。