视频相关的命令和脚本 2023-11-22 • 更新于 2024-05-27
FFmpeg 实用命令 剪切视频而不重新转码 $ 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 container 一些视频流和音频流无法放到同一容器中,例如 WMA 音频不能与 H.264 视频兼容,解决方式是将音频也转码:
$ ffmpeg -i input.wmv -c:v libx264 -c:a aac output.mp4
旋转视频而不转码 逆时针 旋转 90 度:
$ ffmpeg -display_rotation 90 -i input.mp4 -c copy output.mp4
裁切视频 $ ffmpeg -i input.mp4 -vf "crop=out_w:out_h:x:y" -c:a copy output.mp4
crop=
后以 :
分隔的四个参数分别表示:输出宽度、输出高度、左上角横坐标、左上角纵坐标。
循环重复 循环重复 3 次:
$ ffmpeg -stream_loop 3 -i input.mp4 -c copy output.mp4
合并多个同类文件而不转码 使用一个文本保存需合并的文件路径,如 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
去隔行扫描 $ ffmpeg -i input.vob -vf yadif -c:a copy output.mp4
视频加速/减速 加速两倍:
$ ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" output.mp4
减速一半:
$ ffmpeg -i input.mp4 -vf "setpts=2*PTS" output.mp4
视频反转 仅反转视频:
$ ffmpeg -i input.mp4 -vf reverse output.mp4
同时反转视频和音频:
$ ffmpeg -i input.mp4 -vf reverse -af areverse output.mp4
hev1 转 hvc1 一些 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 等容器。
top-videos-by-bitrate.sh #!/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 个,不支持含换行符的文件名。
top-videos-by-size.sh #!/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>
指定需要忽略的编码。
hevcize.sh #!/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
已知问题 一些视频除了视频流、音频流、字幕流外,还带有数据流、附件流,这些流可能无法在转码后的容器中支持(即使转码前后容器相同) 。以下是一个容器无法支持数据流的报错:
[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 尝试,也可能存在不支持的音频流和字幕流等 ,此时也可以按需转码或丢弃,例如音频转码 -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。