FFmpeg视频压缩/特定文件大小

36

我有一些80MB大小的电影文件,想用ffmpeg将它们转换成10MB或15MB大小。我知道这样会导致画质下降,但需要保证声音。是否可以指定文件大小或采用比之前更高的压缩率来实现呢?

ffmpeg -i movie.mp4 -b 2255k -s 1280x720 movie.hd.ogv

它们目前每个大约有25MB

6个回答

46

如果您的目标是特定的输出文件大小,最好的方法是使用H.264双通道编码

这里有一个很好的例子,但太大了无法复制粘贴:https://trac.ffmpeg.org/wiki/Encode/H.264#twopass

您可以使用 bitrate = target size / duration 计算目标比特率,然后启动ffmpeg两次:第一遍分析媒体,第二遍进行实际编码:

ffmpeg -y -i input -c:v libx264 -preset medium -b:v 555k -pass 1 -c:a libfdk_aac -b:a 128k -f mp4 /dev/null && \
ffmpeg -i input -c:v libx264 -preset medium -b:v 555k -pass 2 -c:a libfdk_aac -b:a 128k output.mp4

编辑:H.265(HEVC)在压缩方面表现得更好(有时可以达到H.264大小的50%),但支持尚不普及,因此目前请继续使用H.264。


8
在你的计算中,文件大小和持续时间的单位是什么? - Drew Chapin
5
千比特/秒 - aergistal
1
我们在Windows命令终端(又名MS-DOS)中如何做到这一点,因为它使用了奇怪的两行语法? - raccoon
1
@rac 请查看 https://dev59.com/5V4b5IYBdhLWcg3wdxjG#69304825 - somebadhat
1
@raccoon 使用NUL && ^代替dev null && \。或者使用filenamehere && ^来创建一个中间文件。 - undefined
显示剩余2条评论

39

受Hashbrown答案的启发。此版本保持原始音频质量,并调整大小以适应目标大小。

新版

  1. 为了可读性和一致性,重命名变量。
  2. 删除对grep的依赖(在OSX grep中未实现-P开关)
  3. 如果目标大小太小,则脚本现在会退出并显示有用的消息。

脚本

#!/bin/bash
#
# Re-encode a video to a target size in MB.
# Example:
#    ./this_script.sh video.mp4 15

T_SIZE="$2" # target size in MB
T_FILE="${1%.*}-$2MB.mp4" # filename out

# Original duration in seconds
O_DUR=$(\
    ffprobe \
    -v error \
    -show_entries format=duration \
    -of csv=p=0 "$1")

# Original audio rate
O_ARATE=$(\
    ffprobe \
    -v error \
    -select_streams a:0 \
    -show_entries stream=bit_rate \
    -of csv=p=0 "$1")

# Original audio rate in KiB/s
O_ARATE=$(\
    awk \
    -v arate="$O_ARATE" \
    'BEGIN { printf "%.0f", (arate / 1024) }')

# Target size is required to be less than the size of the original audio stream
T_MINSIZE=$(\
    awk \
    -v arate="$O_ARATE" \
    -v duration="$O_DUR" \
    'BEGIN { printf "%.2f", ( (arate * duration) / 8192 ) }')

# Equals 1 if target size is ok, 0 otherwise
IS_MINSIZE=$(\
    awk \
    -v size="$T_SIZE" \
    -v minsize="$T_MINSIZE" \
    'BEGIN { print (minsize < size) }')

# Give useful information if size is too small
if [[ $IS_MINSIZE -eq 0 ]]; then
    printf "%s\n" "Target size ${T_SIZE}MB is too small!" >&2
    printf "%s %s\n" "Try values larger than" "${T_MINSIZE}MB" >&2
    exit 1
fi

# Set target audio bitrate
T_ARATE=$O_ARATE


# Calculate target video rate - MB -> KiB/s
T_VRATE=$(\
    awk \
    -v size="$T_SIZE" \
    -v duration="$O_DUR" \
    -v audio_rate="$O_ARATE" \
    'BEGIN { print  ( ( size * 8192.0 ) / ( 1.048576 * duration ) - audio_rate) }')

# Perform the conversion
ffmpeg \
    -y \
    -i "$1" \
    -c:v libx264 \
    -b:v "$T_VRATE"k \
    -pass 1 \
    -an \
    -f mp4 \
    /dev/null \
&& \
ffmpeg \
    -i "$1" \
    -c:v libx264 \
    -b:v "$T_VRATE"k \
    -pass 2 \
    -c:a aac \
    -b:a "$T_ARATE"k \
    "$T_FILE"

注意事项

  • 有可能的话,可以查看评论以获取Windows版本。

来源

Hashbrown在这个帖子中的回复

双重验证方法


5
如果文件路径包含空格,应在第4和第5行上加引号引用$1。另外,“编辑必须至少为6个字符”这条规则对于一个编程网站来说是愚蠢的。 - 9072997
2
它不需要是整数,我没有运行你的代码,但我的原始代码可以接受“7.5”,所以你的代码可能也可以。这很方便,因为算法似乎会超出范围(8会生成一个8.5MB的文件,Discord不会喜欢,但7.8非常准确,而不必降到7平)。 - Hashbrown
非常好。并不像你说的那样直接转换为Windows CMD使用,这是我的实现方式,感谢你和这个答案:https://gist.github.com/svArtist/e1b0c8526abaffc9f6ffc63d232b53d5 - Ben Philipp
@BenPhilipp 哈哈,没错,你说得很对 - 感谢CMD版本。我在我的回答中添加了一条注释 - 我很想把那部分都删掉。 - Marian Minar
4
使用origin_duration_s=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$1")替换grep。请注意,对于某些格式,例如Matroska (mkv)输入,origin_audio_bitrate_kbit_s将无法正常工作。但是你也可以从中删除grep: origin_audio_bitrate_kbit_s=$(ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate -of csv=p=0 "$1") - llogan
显示剩余8条评论

11
这里有一个使用bash脚本自动实现的方法
只需这样调用:./script.sh file.mp4 15表示15兆字节
bitrate="$(awk "BEGIN {print int($2 * 1024 * 1024 * 8 / $(ffprobe \
    -v error \
    -show_entries format=duration \
    -of default=noprint_wrappers=1:nokey=1 \
    "$1" \
) / 1000)}")k"
ffmpeg \
    -y \
    -i "$1" \
    -c:v libx264 \
    -preset medium \
    -b:v $bitrate \
    -pass 1 \
    -an \
    -f mp4 \
    /dev/null \
&& \
ffmpeg \
    -i "$1" \
    -c:v libx264 \
    -preset medium \
    -b:v $bitrate \
    -pass 2 \
    -an \
    "${1%.*}-$2mB.mp4"

我正在剪辑音频


1
如何不剪切音频? - Samie Bencherif
2
好的。解决方案是删除带有“-an”的行。 - Samie Bencherif
嗯,我没有尝试过,因为它将不再匹配您想要制作的大小。要包含音频并具有文件大小限制,您需要决定比特率比,并因此降低音频质量,而不仅仅是 -b:v,还要确保这两个值正确相加。 - Hashbrown
如果您想保留音频“原样”(通常我都这么做),那么您当然可以使用类似ffprobe的工具来确定音轨的大小。从目标大小中减去该大小,以获得可用于视频轨道的剩余大小。我发现您需要从中减去2Mb以适应MP4容器。最近的一个例子是将200Mb、720p的视频缩小到640x360版本,目标大小为64Mb(得到了63.9Mb):ffmpeg -i in.mp4 -filter:v scale=640:-1 -c:a copy -b:v 630k out.mp4-c:a copy使我在输出视频中获得原始音频。 - RJVB
AWK?认真的吗?$(( $2 * 1024 * 1024 * 8 / $( ... ) ))。这甚至不是bash,这是POSIX shell,甚至可以在dash、ash等中工作。 - Mecki
显示剩余2条评论

2

使用Windows 10,cmd和ffmpeg将视频压缩到预定文件大小。

使用H.264和双通道编码。

计算比特率:比特率=目标文件大小/持续时间

目标文件大小以千位为单位。持续时间以秒为单位。1 MB = 8192 kb

将比特率分配给视频和音频,大约3/4的视频,1/4的音频。音频和视频比特率之和不能超过计算出的比特率。

-c:a libfdk_aac 对我无效。

ffmpeg -y -i input -c:v libx264 -b:v CALCULATED BITRATE HERE -pass 1 -an -f null nul && ^ffmpeg -y -i input -c:v libx264 -b:v CALCULATED BITRATE HERE -pass 2 -c:a aac -b:a CALCULATED BITRATE HERE output.mp4

ffmpeg -y -i 1.mp4 -c:v libx264 -b:v 555k -pass 1 -an -f null nul && ^ffmpeg -y -i 1.mp4 -c:v libx264 -b:v 555k -pass 2 -c:a aac -b:a 128k output.mp4

请查看https://trac.ffmpeg.org/wiki/Encode/H.264#twopass


3
为什么你的代码里看起来有4个ffmpeg调用? - Adrian

0

如果优先考虑音频,通过降低分辨率可以将视频大小大幅缩小。

-filter:v "scale=-1:ih*1/2:force_original_aspect_ratio=decrease" -max_muxing_queue_size 1024


减少每秒帧数并不会真正起到作用,因为视频压缩的方式是只编码帧与帧之间的差异。如果你大幅降低帧率,那么每一帧基本上都变成了一个大的关键帧。你最终并没有节省太多空间。但是降低分辨率非常有效。 - fieldlab

-4

如果您不需要高帧率,比如在教程中,只需降低帧率,就可以显著减小视频文件的大小。使用此过滤器,您不会失去音频同步。

ffmpeg -i input.mp4 -filter:v fps=fps=10 output.mp4


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接