尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python视频压缩实战:从码率计算到自动化批量处理

Python视频压缩实战:从码率计算到自动化批量处理 前言作为开发者日常少不了和视频文件打交道——录了个 Bug 复现视频 200MB想发给同事结果企业微信限制 50MB游戏录屏 1GB传 GitHub Release 要等到地老天荒。手动打开 FFmpeg 敲命令能解决但每次都算码率、调 crf、试参数实在太低效了。最近我封装了一套 Python 脚本把视频压缩流程自动化了同时也发现了一些实用的在线工具。这篇文章把经验分享出来。基础概念视频文件大小到底由什么决定在动手写代码之前先搞清楚几个核心概念。一个视频文件的大小主要由三个参数决定文件大小 ≈ 码率(bitrate) × 时长 音频大小其中视频码率是压缩时最需要关注的变量它又受以下因素影响参数作用典型值分辨率画面像素尺寸1080p (1920×1080)、720p (1280×720)帧率每秒画面数量30fps、60fps编码格式压缩算法H.264 (兼容性最好)、H.265 (压缩率更高)、AV1 (最新)CRF恒定质量因子越低画质越好18接近无损、23默认、28可接受码率反推公式当我们需要「压缩到指定大小」时核心是把目标文件大小反推为目标码率python# 目标码率计算kbps # 目标大小单位 MB时长单位秒 target_bitrate (target_size_mb * 8192) / duration_seconds - audio_bitrate举个例子一段 5 分钟的视频要压缩到 8MBDiscord 限制音频码率取 128kbpspythontarget_size_mb 8 duration_seconds 5 * 60 # 300秒 audio_bitrate 128 target_bitrate (8 * 8192) / 300 - 128 # 结果 ≈ 90 kbps print(f目标视频码率: {target_bitrate:.0f} kbps)这就是每次手动敲 FFmpeg 命令时心里要做的那道数学题。接下来把它自动化。环境准备Python 3.8系统需安装 FFmpeg 并在 PATH 中https://ffmpeg.org/download.html依赖库Python 自带 subprocess无需额外安装验证安装bashffmpeg -version # 应输出版本信息 python -c import subprocess; print(OK)实现步骤Step 1: 用 ffprobe 提取视频信息压缩的第一步是获取视频的元数据——分辨率、码率、时长、编码格式。可以用 FFmpeg 自带的 ffprobe 提取pythonimport subprocess import json from pathlib import Path def get_video_info(video_path: str) - dict: 提取视频元数据 Args: video_path: 视频文件路径 Returns: 包含分辨率、码率、时长等信息的字典 cmd [ ffprobe, -v, quiet, -print_format, json, -show_format, -show_streams, str(video_path), ] result subprocess.run(cmd, capture_outputTrue, textTrue, checkTrue) data json.loads(result.stdout) # 从 streams 中找视频流 video_stream None audio_stream None for stream in data.get(streams, []): if stream[codec_type] video: video_stream stream elif stream[codec_type] audio: audio_stream stream if not video_stream: raise ValueError(未找到视频流) info { path: str(video_path), duration: float(data[format][duration]), file_size_mb: Path(video_path).stat().st_size / (1024 * 1024), resolution: f{video_stream[width]}x{video_stream[height]}, fps: eval(video_stream.get(r_frame_rate, 0/1)), video_codec: video_stream.get(codec_name, unknown), video_bitrate_kbps: int(video_stream.get(bit_rate, 0)) // 1000 if video_stream.get(bit_rate) else 0, } if audio_stream: info[audio_codec] audio_stream.get(codec_name, unknown) info[audio_bitrate_kbps] ( int(audio_stream.get(bit_rate, 0)) // 1000 if audio_stream.get(bit_rate) else 0 ) return infoStep 2: 根据目标大小计算压缩参数有了视频信息后下一步是根据目标文件大小自动计算 FFmpeg 所需的压缩参数pythondef calculate_compression_params( video_info: dict, target_size_mb: float, audio_bitrate_kbps: int 64, codec: str hevc, ) - dict: 根据目标文件大小计算 FFmpeg 压缩参数 Args: video_info: get_video_info() 返回的视频信息 target_size_mb: 目标文件大小MB audio_bitrate_kbps: 目标音频码率kbps默认 64 codec: 视频编码器hevcH.265, h264H.264 Returns: 包含码率、编码器等参数的字典 # 将目标大小转为 kbits target_size_kbits target_size_mb * 8192 # 1 MB 8192 kbits # 分配音频占固定部分剩余给视频 audio_kbits audio_bitrate_kbps * video_info[duration] video_kbits target_size_kbits - audio_kbits # 反推视频码率 target_video_bitrate video_kbits / video_info[duration] # H.265 比 H.264 约节省 30%-50% 码率 codec_map { hevc: { encoder: libx265, crf: 28, # H.265 默认 CRF efficiency: 1.0, # 基准 }, h264: { encoder: libx264, crf: 23, # H.264 默认 CRF efficiency: 0.6, # 同样画质需更多码率 }, } config codec_map.get(codec, codec_map[hevc]) # 根据压缩比决定是否降低分辨率 original_bitrate video_info.get(video_bitrate_kbps, 0) or ( video_info[file_size_mb] * 8192 / video_info[duration] ) compression_ratio original_bitrate / max(target_video_bitrate, 1) # 如果压缩比 5建议降低分辨率 new_resolution video_info[resolution] if compression_ratio 5 and x in new_resolution: w, h map(int, new_resolution.split(x)) if h 1080: new_resolution 1280x720 elif h 720: new_resolution 960x540 return { target_video_bitrate_kbps: int(target_video_bitrate), target_audio_bitrate_kbps: audio_bitrate_kbps, encoder: config[encoder], crf: config[crf], resolution: new_resolution, compression_ratio: round(compression_ratio, 1), }Step 3: 执行压缩最后一步拼装 FFmpeg 命令并执行pythondef compress_video( input_path: str, output_path: str, params: dict, overwrite: bool True, ) - bool: 执行视频压缩 Args: input_path: 输入视频路径 output_path: 输出路径 params: calculate_compression_params() 返回的参数字典 overwrite: 是否覆盖已有输出文件 Returns: 压缩是否成功 cmd [ ffmpeg, -i, str(input_path), -c:v, params[encoder], -b:v, f{params[target_video_bitrate_kbps]}k, -maxrate, f{params[target_video_bitrate_kbps]}k, -bufsize, f{params[target_video_bitrate_kbps] * 2}k, -c:a, aac, -b:a, f{params[target_audio_bitrate_kbps]}k, -preset, medium, ] # 如果需要降分辨率 if params[resolution] ! get_video_info(input_path)[resolution]: cmd.extend([-vf, fscale{params[resolution]}]) if overwrite: cmd.append(-y) cmd.append(str(output_path)) result subprocess.run(cmd, capture_outputTrue, textTrue) if result.returncode ! 0: print(f压缩失败: {result.stderr[-200:]}) return False output_size Path(output_path).stat().st_size / (1024 * 1024) print(f压缩完成: {output_size:.1f}MB) return True def batch_compress(input_dir: str, output_dir: str, target_size_mb: float 20): 批量压缩目录下的所有视频 input_path Path(input_dir) output_path Path(output_dir) output_path.mkdir(parentsTrue, exist_okTrue) video_extensions {.mp4, .mov, .avi, .mkv, .webm} videos [f for f in input_path.iterdir() if f.suffix.lower() in video_extensions] print(f找到 {len(videos)} 个视频文件目标大小: {target_size_mb}MB\n) success 0 for i, video in enumerate(videos, 1): print(f[{i}/{len(videos)}] 处理: {video.name}) try: info get_video_info(str(video)) params calculate_compression_params(info, target_size_mb) out_file output_path / f{video.stem}_compressed.mp4 if compress_video(str(video), str(out_file), params): success 1 except Exception as e: print(f 错误: {e}) print(f\n完成: {success}/{len(videos)} 个文件压缩成功)完整代码把以上三步整合到一个脚本中保存为video_compressor.pypython#!/usr/bin/env python3 视频批量压缩脚本 用法: python video_compressor.py input.mp4 8 # 压缩单个视频到 8MB python video_compressor.py ./videos/ ./output/ 20 # 批量压缩目录到 20MB import sys import subprocess import json from pathlib import Path def get_video_info(video_path: str) - dict: cmd [ffprobe, -v, quiet, -print_format, json, -show_format, -show_streams, str(video_path)] result subprocess.run(cmd, capture_outputTrue, textTrue, checkTrue) data json.loads(result.stdout) video_stream next((s for s in data.get(streams, []) if s[codec_type] video), None) audio_stream next((s for s in data.get(streams, []) if s[codec_type] audio), None) if not video_stream: raise ValueError(未找到视频流) info { duration: float(data[format][duration]), file_size_mb: Path(video_path).stat().st_size / 1048576, resolution: f{video_stream[width]}x{video_stream[height]}, video_bitrate_kbps: int(video_stream.get(bit_rate, 0)) // 1000 if video_stream.get(bit_rate) else 0, } return info def calculate_params(info: dict, target_mb: float, codec: str hevc) - dict: target_kbits target_mb * 8192 audio_kbps 64 target_video_bitrate (target_kbits - audio_kbps * info[duration] ) / info[duration] codec_config { hevc: {encoder: libx265, crf: 28}, h264: {encoder: libx264, crf: 23}, } config codec_config.get(codec, codec_config[hevc]) return { encoder: config[encoder], bitrate: int(target_video_bitrate), audio_bitrate: audio_kbps, resolution: info[resolution], } def compress(input_path: str, output_path: str, params: dict) - bool: cmd [ffmpeg, -y, -i, str(input_path), -c:v, params[encoder], -b:v, f{params[bitrate]}k, -c:a, aac, -b:a, f{params[audio_bitrate]}k, str(output_path)] result subprocess.run(cmd, capture_outputTrue, textTrue) return result.returncode 0 def main(): if len(sys.argv) 3: print(用法: python video_compressor.py 输入 目标MB) print( python video_compressor.py video.mp4 8) print( python video_compressor.py ./input/ ./output/ 20) sys.exit(1) target_mb float(sys.argv[-1]) source Path(sys.argv[-3] if len(sys.argv) 4 else sys.argv[-2]) if source.is_file(): info get_video_info(str(source)) params calculate_params(info, target_mb) out source.parent / f{source.stem}_compressed.mp4 if compress(str(source), str(out), params): output_size out.stat().st_size / 1048576 print(f完成: {source.name} → {output_size:.1f}MB (目标 {target_mb}MB)) elif source.is_dir(): output_dir Path(sys.argv[-2]) if len(sys.argv) 4 else Path(compressed) output_dir.mkdir(exist_okTrue) exts {.mp4, .mov, .avi, .mkv, .webm} videos [f for f in source.iterdir() if f.suffix.lower() in exts] for v in videos: info get_video_info(str(v)) params calculate_params(info, target_mb) out output_dir / f{v.stem}_compressed.mp4 if compress(str(v), str(out), params): print(f完成: {v.name}) if __name__ __main__: main()运行效果单文件压缩bashpython video_compressor.py Screen_Recording_2024.mp4 8 # 输出: # 完成: Screen_Recording_2024.mp4 → 7.8MB (目标 8MB)原始视频 327MB1080p 30fps H.264压缩到 7.8MB画质虽然有轻微下降但用于发给同事看 Bug 复现步骤完全够用。批量压缩bashpython video_compressor.py ./raw_videos/ ./compressed/ 20 # 输出: # 完成: Gameplay_Footage.mp4 # 完成: Meeting_Recording.mov # 完成: Demo_Walkthrough.avi一键处理整个目录每个文件都按目标大小自动计算最优码率。什么时候用脚本什么时候用在线工具上面的自动化方案适合批量处理和需要精确控制编码参数的场景。但还有一种常见情况——偶尔处理一两个视频懒得打开终端敲命令。这种时候用浏览器端工具更省事。VideoCompress 就是这类工具的代表打开网页 → 拖入视频 → 输入目标大小比如 8MB→ 下载。不需要算码率、不需要装 FFmpeg。它本质上是把「码率反推 编码压缩」这个流程完全黑盒化了。几个关键参数实测所得维度数据输入格式40 种MP4 / MOV / AVI / MKV / WebM 等单文件限制最大 1GB免费额度每月 30 积分水印无注册要求无需强制注册隐私上传加密24 小时自动删除画质有损压缩目标「肉眼无差别」visually lossless一句话总结选型逻辑批量处理、精确控制参数 → Python 脚本 FFmpeg偶尔快速压缩一两个视频、不想折腾 → VideoCompress 直接浏览器搞定专业影视级编码、逐帧调色 → HandBrake / FFmpeg 命令行
返回列表