前言

  • 众所周知,我们可以将 Telegram 当作一个无限容量的云盘,但是 Telegram 对普通用户做出了限制,即单个文件不可超过 2GB。当需要上传较大体积的视频文件又不想过度压缩损失清晰度的时候,就可以选择将文件分割为数段上传。
  • FFmpeg 是一款非常强大的音视频处理工具,应用非常广泛,几乎所有跟音视频处理相关的工具都有用到它,可以称为最伟大的开源工具之一。
  • FFmpeg 虽然很强大,但作为一个命令行工具,还有非常多的命令参数,普通用户使用起来还是多有不便。不过这里只要简单地将视频文件切割,所以只要用个简单的脚本处理就好了。
  • 自用脚本,由 ChatGPT 创作,仅适用 macOS。

使用

创建脚本文件

1
sudo vim ~/.local/bin/split_video.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
131
132
133
134
135
136
137
138
139
#!/bin/bash

# ========= 环境检查 =========
if ! command -v ffmpeg &>/dev/null || ! command -v ffprobe &>/dev/null; then
echo "请先安装 ffmpeg 和 ffprobe"
exit 1
fi

echo "🎬 欢迎使用 视频分割小工具"

# ========= 文件大小格式化 =========
format_size() {
local bytes=$1
if (( bytes < 1073741824 )); then
size=$(echo "scale=2; $bytes / (1024 * 1024)" | bc)
printf "%.2fMB" "$size"
else
size=$(echo "scale=2; $bytes / (1024 * 1024 * 1024)" | bc)
printf "%.2fGB" "$size"
fi
}

# ========= 主循环 =========
while true; do
echo ""
echo "📥 拖入视频文件,或输入 q 退出:"
IFS= read -r input_line
[[ "$input_line" == "q" ]] && echo "👋 已退出" && exit 0

eval "set -- $input_line"
inputs=()
for arg in "$@"; do
inputs+=("$arg")
done

for input in "${inputs[@]}"; do
if [ ! -f "$input" ]; then
echo "❗ 文件不存在:$input,跳过..."
continue
fi

echo ""
echo "🚀 正在处理:$input"

filename=$(basename "$input")
basename_no_ext="${filename%.*}"
dir=$(dirname "$input")
output_dir="$dir/${basename_no_ext}_parts"
mkdir -p "$output_dir"

filesize=$(stat -f%z "$input")
filesize_human=$(format_size "$filesize")
echo "📋 文件大小:$filesize_human"

# ========= 输入每段大小 =========
echo ""
read -p "请输入每段大小(GB,默认 1.9): " size_gb
if [[ -z "$size_gb" ]]; then
size_gb=1.9
echo "📦 使用默认大小:1.9GB"
else
if ! [[ "$size_gb" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
echo "❌ 输入格式错误"
continue
fi
echo "📦 每段大小:${size_gb}GB"
fi

max_size=$(echo "$size_gb * 1024^3" | bc | cut -d'.' -f1)

duration=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$input")
duration_int=$(printf "%.0f" "$duration")
echo "📋 总时长:$duration_int 秒"

# ========= 智能估算分段 =========
estimated_parts=$(echo "($filesize + $max_size - 1) / $max_size" | bc)
part_duration=$(echo "$duration / $estimated_parts" | bc)
part_duration_int=$(printf "%.0f" "$part_duration")
((part_duration_int <= 0)) && part_duration_int=1

echo "🚀 预计分割为 $estimated_parts 段"
echo "⏱ 每段约 ${part_duration_int}s(关键帧对齐)"

# ========= 临时输出 =========
tmp_dir="$output_dir/_tmp_segments"
mkdir -p "$tmp_dir"

ffmpeg -loglevel quiet \
-fflags +genpts \
-i "$input" \
-c copy \
-map 0 \
-f segment \
-segment_time "$part_duration_int" \
-segment_time_delta 5 \
-reset_timestamps 1 \
-avoid_negative_ts make_zero \
"$tmp_dir/segment%03d.${filename##*.}"

# ========= 重命名 + 尾段合并 =========
idx=1
files=($(ls "$tmp_dir" | sort))
num_files=${#files[@]}

for i in "${!files[@]}"; do
f="${files[$i]}"
file_path="$tmp_dir/$f"
size=$(stat -f%z "$file_path")
# 检查是否是最后一段且小于前一段 5%
if (( i == num_files - 1 && num_files > 1 )); then
prev_file="$output_dir/${basename_no_ext}_part$(printf "%03d" $((idx-1))).${filename##*.}"
prev_size=$(stat -f%z "$prev_file")
threshold=$(echo "$prev_size * 0.05" | bc | cut -d'.' -f1)
if (( size < threshold )); then
# 合并到前一段
echo "⚡ 尾段太小,合并到上一段..."
ffmpeg -loglevel quiet -i "concat:$prev_file|$file_path" -c copy "$prev_file.tmp" && mv "$prev_file.tmp" "$prev_file"
continue
fi
fi

mv "$file_path" "$output_dir/${basename_no_ext}_part$(printf "%03d" $idx).${filename##*.}"
((idx++))
done

rm -rf "$tmp_dir"

echo ""
echo "✅ 分割完成"
echo "📂 输出目录:$output_dir"

for file in "$output_dir"/*; do
size_human=$(du -h "$file" | cut -f1)
echo " - $(basename "$file") [$size_human]"
done

open "$output_dir"
done
done

赋权

1
sudo chmod +x ~/.local/bin/split_video.sh

执行

1
split_video.sh

后记

  • 直接将需要切割的视频文件拖入终端,即可指定切割完成后的单个文件大小。
  • 脚本会在原视频同级目录下创建名为video-name_parts的文件夹,分割完成后的文件会自动命名并放入此文件夹。
  • 支持中文路径和中文文件名。