视频相关的命令和脚本 2023-11-22 • 更新于 2026-02-18
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 container 一些视频流和音频流无法放到同一容器中,例如 WMA 音频不能与 H.264 视频兼容,解决方式是将音频也转码:
1
ffmpeg -i input.wmv -c:v libx264 -c:a aac output.mp4
旋转视频而不转码 逆时针 旋转 90 度:
1
ffmpeg -display_rotation 90 -i input.mp4 -c copy output.mp4
裁切视频 1
ffmpeg -i input.mp4 -vf "crop=out_w:out_h:x:y" -c:a copy output.mp4
crop= 后以 : 分隔的四个参数分别表示:输出宽度、输出高度、左上角横坐标、左上角纵坐标。
调整分辨率 将高度调整为 1080,比例不变:
1
ffmpeg -i input.mp4 -vf scale = -1:1080 -c:a copy output.mp4
循环重复 循环重复 3 次:
1
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'
然后:
1
ffmpeg -f concat -i input.txt -c copy output.mp4
去隔行扫描 1
ffmpeg -i input.vob -vf yadif -c:a copy output.mp4
视频加速/减速 加速两倍:
1
ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" output.mp4
减速一半:
1
ffmpeg -i input.mp4 -vf "setpts=2*PTS" output.mp4
视频反转 仅反转视频:
1
ffmpeg -i input.mp4 -vf reverse output.mp4
同时反转视频和音频:
1
ffmpeg -i input.mp4 -vf reverse -af areverse output.mp4
hev1 转 hvc1 一些 HEVC 在 macOS 上无法显示缩略图,也无法用原生 APP 打开,这是因为使用了 hev1:
1
2
$ ffprobe -v error -select_streams v -show_entries stream = codec_tag_string -of default = noprint_wrappers = 1:nokey= 1 input.mp4
hev1
转换为 hvc1 即可:
1
ffmpeg -i input.mp4 -tag:v hvc1 -c copy output.mp4
1
2
$ 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 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#!/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
示例:
1
2
3
4
5
6
7
8
$ 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 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#!/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
示例:
1
2
3
4
5
6
7
$ 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 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/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 丢弃数据流:
1
ffmpeg -i input.mp4 -map 0 -map -0:d -c copy -c:v libx265 output.mp4
另外,由于脚本只使用 MP4 和 MKV 尝试,也可能存在不支持的音频流和字幕流等 ,此时也可以按需转码或丢弃,例如音频转码
-c:a acc,丢弃字幕 -map -0:s。
示例 批量转码 支持通配符:
1
2
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 下所有文件进行转码,结果保存在当前目录:
1
find /path/to/videos -type f -exec hevcize.sh -o . {} +
显卡加速 比如 NVIDIA 显卡可以使用 hevc_nvenc 编码器进行加速,Intel 显卡使用 hevc_qsv,这些都需要硬件和驱动支持。
macOS 使用 hevc_videotoolbox 编码器进行显卡加速(hevcize.sh -h 查看有哪些可用编码器):
1
2
3
4
5
$ 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。