我想减小视频的大小,以便能够通过电子邮件等发送。我看了这个问题:如何使用 ffmpeg 减小视频大小?在那里我得到了关于如何减少它的好建议。问题是我需要手动计算比特率。而且在执行此操作时,我必须通过手动尝试和错误来完成。
有没有什么好的方法可以使用ffmpeg
(或其他工具)将视频的大小减小到目标尺寸?
请注意来自frostschutz 的评论:
过于担心大小并选择固定的文件大小,而不管视频长度/分辨率/帧速率/内容如何,大多数时候都不会给出令人满意的结果...将其上传到某处,通过电子邮件发送链接。如果必须对其进行编码,请设置质量级别,并使比特率保持动态。如果明显无法满足尺寸要求,请取消编码并相应地调整质量级别。冲洗并重复。
总体来说是好的建议,但它不适合我的用例。由于技术限制,上传到外部链接不是一种选择,其中之一是接收者没有 http 访问权限。
我也可以澄清一下,我不是在寻找确切地我指定的尺寸。如果它相当接近我想要的(可能低 5% 或 10% 左右)就足够了,但应该保证它不会超过我的目标极限。
答案1
@philip-couling 的答案很接近,但缺少几部分:
- 所需大小(以 MB 为单位)需要乘以 8 才能获得比特率
- 对于比特率,1Mb/s = 1000 kb/s = 1000000 b/s;乘数不是 1024(还有另一个前缀 KiB/s,即 1024 B/s,但 ffmpeg 似乎没有使用它)
- 截断或向下舍入非整数
length
(而不是精确使用或向上舍入)将导致文件大小稍微超过目标大小 - 该
-b
标志指定平均比特率:实际上,编码器会让比特率稍微跳跃,结果可能会超出您的目标大小。但是,您可以使用-maxrate
和来指定最大比特率-bufsize
对于我的用例,我还想要一个硬编码的音频比特率,然后相应地调整视频(这似乎也更安全,我不能 100% 确定 ffmpeg 的-b
标志是否一起指定视频和音频流的比特率)。
考虑到所有这些并严格限制为 25MB:
file="input.mp4"
target_size_mb=25 # 25MB target size
target_size=$(( $target_size_mb * 1000 * 1000 * 8 )) # target size in bits
length=`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file"`
length_round_up=$(( ${length%.*} + 1 ))
total_bitrate=$(( $target_size / $length_round_up ))
audio_bitrate=$(( 128 * 1000 )) # 128k bit rate
video_bitrate=$(( $total_bitrate - $audio_bitrate ))
ffmpeg -i "$file" -b:v $video_bitrate -maxrate:v $video_bitrate -bufsize:v $(( $target_size / 20 )) -b:a $audio_bitrate "${file}-${target_size_mb}mb.mp4"
请注意,当-maxrate
限制-bufsize
最大比特率时,平均比特率必然会降低,因此在我的测试中视频将低于目标大小多达 5-10%(在各种目标大小的 20 秒视频上)。的值-bufsize
很重要,上面使用的计算值(基于目标大小)是我的最佳猜测。太小了,它会大大降低质量,并且低于目标大小约 50%,但太大,我认为它可能会超过目标大小?
如果您没有严格的最大文件大小,为了给编码器提供更大的灵活性,删除-maxrate
和-bufsize
会带来更好的质量,但在我的测试中可能会导致视频超出目标大小 5%。文档中的更多信息,您将在其中看到此警告:
注意:如果视频难以编码,限制比特率可能会导致输出质量低下。在大多数情况下(例如存储文件进行存档),让编码器选择合适的比特率是恒定质量或基于 CRF 的编码。
您可以使用以下代码来声明可重用的 bash 函数:
ffmpeg_resize () {
file=$1
target_size_mb=$2 # target size in MB
target_size=$(( $target_size_mb * 1000 * 1000 * 8 )) # target size in bits
length=`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file"`
length_round_up=$(( ${length%.*} + 1 ))
total_bitrate=$(( $target_size / $length_round_up ))
audio_bitrate=$(( 128 * 1000 )) # 128k bit rate
video_bitrate=$(( $total_bitrate - $audio_bitrate ))
ffmpeg -i "$file" -b:v $video_bitrate -maxrate:v $video_bitrate -bufsize:v $(( $target_size / 20 )) -b:a $audio_bitrate "${file}-${target_size_mb}mb.mp4"
}
ffmpeg_resize file1.mp4 25 # resize `file1.mp4` to 25 MB
ffmpeg_resize file2.mp4 64 # resize `file2.mp4` to 64 MB
答案2
声誉不够,无法发表评论。调整了@tobek 上面的代码以作为 windows/dos 批处理文件工作。
@echo off
setlocal
if "%~1" == "" (
echo Usage:
echo.
echo %0 filename.mp4
echo or
echo %0 filename.mp4 25
echo (25 = 25MB^)
goto end
)
set target_mb=%2
if "%~2" == "" (
rem # default
set target_mb=25
)
set file=%1
set /a target_size=%target_mb% * 1000 * 1000 * 8
for /f %%c in ('ffprobe -v error -show_entries format^=duration -of default^=noprint_wrappers^=1:nokey^=1 "%file%"') do set length=%%c
for /f "tokens=1 delims=." %%n in ('echo %length%') do set length_round_up=%%n
set /a length_round_up=%length_round_up% + 1
rem # 128k bit rate
set /a audio_bitrate=128 * 1000
set /a total_bitrate=%target_size% / %length_round_up%
set /a video_bitrate=%total_bitrate% - %audio_bitrate%
set /a bufsize=%target_size% / 20
for %%f in ("%file%") do (
set filedrive=%%~df
set filepath=%%~pf
set filename=%%~nf
set fileextension=%%~xf
)
set file_output=%filedrive%%filepath%%filename%-out%fileextension%
ffmpeg -i "%file%" -b:v %video_bitrate% -maxrate:v %video_bitrate% -bufsize:v %bufsize% -b:a %audio_bitrate% "%file_output%"
:end
答案3
免责声明:这似乎并不完美。它变得稍微大一点。
将其他两个答案放在一起相对简单:
假设您需要一个 10MB 文件(10485760 字节),您可以使用它ffprobe
来查找持续时间并让 shell 执行计算。
请小心,因为 ffprobe 会报告小数位,这会导致 shell 算术出错。我曾经${length%.*}
去掉小数位:
size=10485760
length=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4)
ffmpeg -i input.mp4 -b $(( $size / ${length%.*} )) output.mp4