如何在 for 循环中增加文件数量(ffmpeg)

如何在 for 循环中增加文件数量(ffmpeg)

我尝试使用 ffmpeg 找到一种解决方案,从视频中制作屏幕。大多数找到的示例都涉及解码整个视频以获取图像。对于较大的视频来说,这相当慢。
更好的尝试大致描述如下:使用 ffmpeg 为视频制作有意义的缩略图

那里的伪代码是:

for X in 1..N
T = integer( (X - 0.5) * D / N )  
run `ffmpeg -ss <T> -i <movie>
          -vf select="eq(pict_type\,I)" -vframes 1 image<X>.jpg`

在哪里:

  • D - 仅从 ffmpeg -i 或 ffprobe 读取视频时长,顺便说一下,ffprobe 有很好的 JSON 输出编写器
  • N - 您想要的缩略图总数
  • X - 缩略图编号,从 1 到 N
  • T - 缩略图的时间点

我已经根据这个“伪代码”想出了一个可行的解决方案,并将其与缩略图蒙太奇结合起来imagemagick

#!/bin/bash
# some of them not used here
MOVIE=$1
D=     # D -  video duration
N=30   # N -  wanted number of thumbnails
X=1    # X -  thumbnail number
T=     # T -  time point for thumbnail
Y=     # Y -  nth frame to take
Z=     # Z -  total number of frames
W=     # W -  fps of the video
SP=    # SP - starting point of extraction
OUTPUT=$2
# some of them from another approach - setting defaults
if [ -z "$N" ]; then N=30; fi
if [ -z "$COLS" ]; then COLS=3; fi
if [ -z "$ROWS" ]; then ROWS=10; fi
if [ -z "$HEIGHT" ]; then HEIGHT=360; fi
if [ -z "$SIZE" ]; then SIZE=3600; fi

# get video name without the path and extension
MOVIE_NAME=$(basename $MOVIE)
OUT_DIR=$(pwd)

if [ -z "$OUTPUT" ]; then OUTPUT=$(echo ${MOVIE_NAME%.*}_preview.jpg); fi

# get duration of input:
D=$(echo "$(ffprobe -hide_banner -i $MOVIE 2>&1 | sed  -n "s/.*: \(.*\), start:.*/\1/p")" | sed 's/:/*60+/g;s/*60/&&/' | bc)
D=$(echo "$D/1" | bc)    # get rid of the floating point part (make integer)

# get fps of input:
W=$(ffprobe $MOVIE 2>&1| grep ",* fps" | cut -d "," -f 5 | cut -d " " -f 2)

# get frame number of input:
Z=$(ffprobe -hide_banner -show_streams $MOVIE 2>&1 | grep nb_frames | head -n1 | sed 's/.*=//')
# as a fallback we'll calculate the frame count
# from duration and framerate, very unprecise, though
if [ "$Z" = "N/A" ]; then Z=$(echo "$D * $W / 1" | bc); fi

echo "Duration is: $D seconds / $Z frames @ $W fps"

# generate thumbnails in the /tmp folder
TMPDIR=/tmp/thumbnails-${RANDOM}/
mkdir $TMPDIR

for (( c=X; c<=N; c++ ))
do
Y=$(echo "($Z / $N)/1" | bc)
T=$(echo "(($c - 0.5) * $Y/1)" | bc)
SP=$(echo "$T / $W" | bc)
ffmpeg -hide_banner -ss $SP -i $MOVIE -an -sn -vf select="eq(pict_type\,I),scale=-1:360" -vframes 1 ${TMPDIR}thumb00$c.jpg
done

# mount the pics in one page neatly
montage ${TMPDIR}thumb*.jpg -background white -geometry +5+5 -tile ${COLS}x ${TMPDIR}output.jpg
rm -R $TMPDIR 
echo $OUT_FILEPATH

这有效,但我对创建的文件名感到困惑。
由于 ffmpeg 调用发生在“for 循环”中,因此明显的name%03d.jpg 模式不适用于输出文件。

最后一次迭代的输出:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test.mp4':
Metadata:
major_brand     : isom 
minor_version   : 512
compatible_brands: isomiso2avc1mp41
encoder         : Lavf54.59.106
Duration: 00:08:41.17, start: 0.023220, bitrate: 1866 kb/s
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1280x720, 1731 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc
(default)
Metadata:
handler_name    : VideoHandler
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 127 kb/s (default)
Metadata:
handler_name    : SoundHandler
[swscaler @ 0x15a85a0] deprecated pixel format used, make sure you did set range correctly
Output #0, image2, to '/tmp/thumbnails-13957/thumb0030.jpg':
Metadata:
major_brand     : isom
minor_version   : 512
compatible_brands: isomiso2avc1mp41
encoder         : Lavf56.40.101
Stream #0:0(und): Video: mjpeg, yuvj420p(pc), 640x360, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc (default)
Metadata:
handler_name    : VideoHandler
encoder         : Lavc56.60.100 mjpeg
Stream mapping:
Stream #0:0 -> #0:0 (h264 (native) -> mjpeg (native))
Press [q] to stop, [?] for help
frame=    1 fps=0.0 q=3.2 Lsize=N/A time=00:00:00.04 bitrate=N/A dup=1 drop=1
video:8kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown

我尝试将迭代变量放在输出文件名中,并以零开头thumb00$c.jpg。到目前为止,这种方法有效 - 但是:由于迭代次数超过 10,我的文件名不再按顺序排列,这意味着 imagemagick 的以下 montage 命令将它们按错误的顺序排列。这是我得到的结果:

-rw-rw-r-- 1 gpm  gpm    17303 Sep  8 19:32 thumb0010.jpg
-rw-rw-r-- 1 gpm  gpm    16474 Sep  8 19:32 thumb0011.jpg
-                                            - " -
-rw-rw-r-- 1 gpm  gpm     6323 Sep  8 19:32 thumb001.jpg
-rw-rw-r-- 1 gpm  gpm    14789 Sep  8 19:32 thumb0020.jpg
-rw-rw-r-- 1 gpm  gpm    18429 Sep  8 19:32 thumb0021.jpg
-                                            -  " -
-rw-rw-r-- 1 gpm  gpm    18870 Sep  8 19:32 thumb002.jpg
-rw-rw-r-- 1 gpm  gpm     7926 Sep  8 19:32 thumb0030.jpg
-rw-rw-r-- 1 gpm  gpm    18312 Sep  8 19:32 thumb003.jpg
-rw-rw-r-- 1 gpm  gpm    18274 Sep  8 19:32 thumb004.jpg

可以看到,前导零存在,但文件顺序不对。
我迷路了。
如何才能从中得到正确的递增文件名?

答案1

您可以使用该printf命令将数字填充为固定宽度。

printf [-v var] format [arguments]
       Write  the  formatted arguments to the standard output under the
       control of the format.  The -v option causes the  output  to  be
       assigned  to  the  variable var rather than being printed to the
       standard output.

       The format is a character string which contains three  types  of
       objects:  plain  characters, which are simply copied to standard
       output, character escape  sequences,  which  are  converted  and
       copied  to  the standard output, and format specifications, each
       of which causes printing of the next  successive  argument.

Bash 的实现将接受d带有宽度和0标志的说明符Cprintf()函数

例子:

$ for c in {8..11}; do
> printf -v result '%03d' $c
> echo $result
> done
008
009
010
011

因此,您无需像 那样对输出文件名中的零的数量进行硬编码thumb00$c.jpg,而是可以使用 printf 来找出使数字达到指定宽度所需的零的数量:

thumb$(printf '%03d' $c).jpg

解释:

  • $()是进程替换。它允许您使用一个命令的标准输出作为另一个命令的一部分。
  • printf运行printf命令。
  • '%03d' defines the format ofprintf`的输出。
    • %意味着我们要使用格式说明符。
    • 0意味着我们要进行零填充。
    • 3是我们填充的长度。
    • d是格式说明符,具体来说,它意味着我们将传递printf另一个参数,并且该参数应该用作有符号的十进制整数。
  • $c是我们要发送给的参数printf

答案2

看起来您想要做的是将文件分成 N 个间隔,每个缩略图一个间隔,并在每个间隔的中点之后选择第一个关键帧。只需运行一次 ffmpeg 即可快速完成此操作,只要给定每个间隔的持续时间( D/N;我们称之为 I )。

ffmpeg -discard nokey -skip_frame nokey -i input -vf select='eq(n,0)+gte(mod(t,I),I/2)*gte(t-prev_selected_t,I/2)',trim=1 -vsync 0 -vframes N image%d.jpg

-discard nokey告诉解复用器跳过非关键帧。-skip_frame nokey解码器执行此操作。对于 MP4 等,这将比省略这些选项快 15-20 倍。

相关内容