视频压缩 - 本地视频批量压缩(统一输出 MP4)
概述
基于 FFmpeg(系统本地命令行版本)的批量视频压缩 skill。用户指定一个目录和一个目标文件大小,skill 会扫描该目录下的各类视频文件,把每个视频压缩到不超过目标大小,并统一输出为 MP4 格式,输出文件存放在原目录中,命名为 <原名>_压缩版.mp4。
- 统一输出 MP4 — 无论源文件是 MKV/WebM/AVI/FLV/WMV/MOV/TS/MTS/M2TS/3GP/VOB/OGV 等何种格式,输出一律为兼容性最好的 MP4
- 智能编码兼容 — 自动检测源视频/音频编码;对 MP4 容器不兼容的编码(如 VP9、VP8、RV40、Opus、Cook、Vorbis、PCM 等)自动重编码为 H.264/AAC,兼容的编码(H.264/HEVC + AAC/MP3/AC3)在码率允许时直接流复制,省时保真
- 本地处理 — 不上传任何文件,完全在用户机器上完成
- 按目标大小智能编码 — 先读取视频时长,按目标大小反算比特率,再调用 ffmpeg
- 两遍编码可选 — 默认 CRF 单遍(更快),可加
--two-pass启用两遍编码(更精准) - 支持输入格式 — MP4、MOV、MKV、AVI、FLV、WebM、M4V、TS、MTS、M2TS、3GP、WMV、VOB、OGV、F4V、MPG、MPEG、RM、RMVB
- 覆盖保护 — 默认拒绝覆盖已存在的输出文件,需要
--overwrite才覆盖 - 保留原文件 — 压缩后原文件保持不变,压缩版以
_压缩版.mp4后缀保存 - 日志记录 — 所有操作记录到控制台和日志文件
- 预览 + 确认 — 默认是预览模式,只显示计划;加
--apply才真正执行
工作流程
1. 确认关键参数
向用户确认或推断:
| 参数 | 必填 | 说明 | 示例 |
|---|---|---|---|
| 目标目录 | ✅ | 视频所在的文件夹(绝对路径) | D:\videos\旅行 |
| 目标大小 | ✅ | 每个输出文件不超过的大小 | 25 MB |
| 输出后缀 | 否 | 默认 _压缩版 |
_small、_8mb |
| 编码器 | 否 | libx264(默认) / libx265(更小,更慢) |
libx264 |
| 预设速度 | 否 | ultrafast / medium(默认) / slow |
medium |
| 音频处理 | 否 | copy(不重编码,默认) / aac(更小) |
aac |
| 递归子目录 | 否 | 默认否 | --recursive |
| 覆盖已存在 | 否 | 默认否 | --overwrite |
| 两遍编码 | 否 | 默认否 | --two-pass |
> 输出格式固定为 MP4(--output-ext 默认 mp4)。如需其它输出容器,可显式传 --output-ext(如 mkv),但默认场景下所有视频统一转为 MP4。
如果用户已经说了目录和大致大小,可以直接进入预览。如果参数不全(特别是目录),先问。
2. 先预览,再执行
始终先用预览模式跑一次,把要处理的文件清单、源编码、预估输出参数展示给用户,再让用户确认。
# 预览:扫描 D:\videos\旅行,把每个视频压到 25 MB,输出 MP4
python ~/.workbuddy/skills/video_compress_mp4/scripts/compress_videos.py \
"D:\videos\旅行" \
--target-size 25 \
--unit MB
# 确认后真正执行
python ~/.workbuddy/skills/video_compress_mp4/scripts/compress_videos.py \
"D:\videos\旅行" \
--target-size 25 \
--unit MB \
--apply
3. 报告结果
执行完成后,汇报:
- 处理的文件数(成功 / 失败 / 跳过)
- 总原始大小、总压缩后大小、总体压缩率
- 失败项的错误原因
- 任何警告(如 "输出文件大于目标大小 — 时长太短,无法再压")
- 触发了编码重转的文件(如 WebM 的 VP9/Opus 已重编码为 H.264/AAC)
- 日志文件位置:告知用户日志已保存到目录中的 compress_log_<时间戳>.txt
输出文件命名规则
- 默认:
<原文件名(去扩展名)>_压缩版.mp4 - 例:
vacation.mov→vacation_压缩版.mp4 - 例:
clip.webm→clip_压缩版.mp4 - 例:
old.avi→old_压缩版.mp4 - 自定义后缀:
<原名><后缀>.mp4 - 例:
--suffix _8mb→vacation_8mb.mp4
> 同一目录下若已存在目标文件:默认跳过(SKIP),加 --overwrite 才覆盖。
MP4 编码兼容性逻辑
输出统一为 MP4 容器。为兼容各类源格式,脚本会自动处理编码兼容问题:
| 源视频编码 | 处理方式 |
|---|---|
| h264 / hevc / mpeg4 / av1 | MP4 兼容,目标码率允许时直接流复制(-c:v copy) |
| vp9 / vp8 / rv40 / wmv3 / msmpeg4 等不兼容编码 | 自动重编码为 H.264(libx264)或 H.265(libx265) |
| 源音频编码 | 处理方式 |
|---|---|
| aac / mp3 / ac3 / eac3 / alac | MP4 兼容,按 --audio copy 直接复制 |
| opus / vorbis / cook / pcm / flac 等不兼容编码 | 自动重编码为 AAC(-c:a aac) |
- 当源编码不兼容触发重编码时,预览和日志会标注原因(如"音频 opus 不兼容 MP4,重编码为 AAC")
- MP4 输出统一加
-movflags +faststart,便于在线播放与流式加载
大小反算逻辑
目标视频比特率由下面公式计算:
总比特率 (kbps) = (目标大小 bits) / 时长 (秒)
视频比特率 (kbps) = 总比特率 - 音频比特率 - 1% 余量
- 音频默认按 96 kbps(AAC)保留,若选择
--audio copy则按输入流的实际音频比特率 - 时长通过
ffprobe读取 - 最小视频比特率钳制为 100 kbps(再低画质会极差)
- 如果反算出的视频比特率超过原视频比特率且源编码 MP4 兼容,skill 会保留原视频流(
copy)以避免无谓的质量损失
核心能力详解
compress_videos.py 参数说明
python compress_videos.py <目录> [选项]
必填:
<目录> 视频所在文件夹
目标大小(两种方式二选一):
--target-size N 目标大小数值
--unit MB|GB|KB 大小单位(默认 MB)
输出命名:
--suffix <后缀> 输出文件后缀,默认 "_压缩版"
--output-ext <扩展名> 输出扩展名,默认 mp4(所有格式统一转为 MP4)
(如需其它容器可传 mkv 等,但默认即 MP4)
编码参数:
--codec libx264|libx265 视频编码器(默认 libx264)
--preset ultrafast|...|veryslow
编码速度/质量权衡(默认 medium)
--crf <0-51> CRF 质量(默认 23,仅在单遍模式下使用)
--audio copy|aac 音频处理(默认 copy:直接复制不重编码;不兼容 MP4 时自动改 aac)
--audio-bitrate <kbps> AAC 比特率(默认 96)
--two-pass 启用两遍编码(更精准但慢一倍)
过滤:
--ext mp4,mov,mkv 只处理指定扩展名(默认含 mp4,mov,mkv,avi,flv,webm,m4v,ts,mts,m2ts,3gp,wmv,vob,ogv,f4v,mpg,mpeg,rm,rmvb)
--recursive 递归处理子目录
执行控制:
--apply 实际执行(否则仅预览)
--overwrite 覆盖已存在的输出文件
--keep-going 遇到错误继续处理下一个文件(默认就是继续)
--dry-run-ffmpeg 预览模式且只打印 ffmpeg 命令,不执行
--ffmpeg <路径> 指定 ffmpeg 可执行路径
--ffprobe <路径> 指定 ffprobe 可执行路径
输出参数示例
对于一个 5 分钟(300 秒)的视频,压到 25 MB:
目标总比特率 = 25 * 1024 * 1024 * 8 / 300 = 699,051 bps ≈ 683 kbps
视频比特率 = 683 - 96 (音频) - 1% ≈ 581 kbps
→ ffmpeg -c:v libx264 -b:v 581k -c:a aac -b:a 96k -movflags +faststart <out>.mp4
若源是 WebM(VP9 + Opus),脚本会自动改为:
→ ffmpeg -c:v libx264 -b:v 581k -c:a aac -b:a 96k -movflags +faststart <out>.mp4
(VP9 不兼容 MP4 → 重编码视频;Opus 不兼容 MP4 → 重编码音频)
常见预设(用户口头说明时直接套用)
| 用户说法 | 目标大小 | 对应参数 |
|---|---|---|
| "压到 8MB 给 Discord" | 8 MB | --target-size 8 --unit MB |
| "压到 25MB 发邮件" | 25 MB | --target-size 25 --unit MB |
| "压到 100MB 传大文件" | 100 MB | --target-size 100 --unit MB |
| "压到 1GB" | 1 GB | --target-size 1 --unit GB |
| "高画质压" | - | 加 --two-pass --codec libx265 --preset slow |
| "快速压,不在意画质" | - | 加 --preset ultrafast |
| "把 MKV/WebM 转成 MP4" | 按需 | 默认即输出 MP4,直接 --apply 即可 |
错误处理
| 场景 | 行为 |
|---|---|
| 找不到 ffmpeg/ffprobe | 提示用户安装或用 --ffmpeg / --ffprobe 指定路径 |
| 时长无法读取 | 跳过该文件并报告错误 |
| 反算出的视频比特率 < 100 kbps | 提示"目标过小,无法进一步压缩"并跳过 |
| 反算出的视频比特率 ≥ 原视频比特率(且编码 MP4 兼容) | 自动切换为视频流 copy(保留原画质) |
| 源视频/音频编码不兼容 MP4 | 自动重编码为 H.264/AAC,日志标注原因 |
| ffmpeg 返回非 0 | 记录 stderr 摘要,继续下一个文件 |
| 输出已存在 | 跳过,除非加 --overwrite |
日志记录
每次运行(包括预览模式和实际执行)都会在目标目录下自动生成一个日志文件:
文件名: compress_log_YYYYMMDD_HHMMSS.txt(如 compress_log_20260703_143025.txt)
日志内容:
- 任务开始时间、目标目录、目标大小、编码参数
- 每个文件的源编码、处理计划(时长、原大小、目标码率)
- 每个文件的处理结果 — [成功] / [失败] / [跳过]
- 跳过文件的跳过原因
- 触发编码重转的备注说明
- 失败文件的具体错误信息
- 任务完成时间和结果汇总(成功 N 个 / 失败 N 个 / 跳过 N 个)
- 总体压缩率统计
控制台也同步输出:
- 日志路径会打印在最终结果中:日志已保存: D:\videos\compress_log_20260703_143025.txt
- 任务完成后,用简洁的一行总结告知用户结果
Resources
scripts/
compress_videos.py— 主脚本:扫描目录、检测编码兼容性、计算比特率、调用 ffmpeg 统一输出 MP4
references/
ffmpeg_guide.md— ffmpeg 常用参数、码率控制原理、MP4 容器兼容性、常见问题排查