Bash - 当 ffmpeg 在 while 主体中执行时,while 读取行失败

Bash - 当 ffmpeg 在 while 主体中执行时,while 读取行失败

我已经为此摸不着头脑有一段时间了;也许你们中的一个人可以帮助我。

我正在尝试从视频中的某些位置提取视频帧。为此,位置以“hh:mm:ss”格式存储在文本文件中,每行一个条目。 bash 脚本从文件中读取这些行并为每一行执行 ffmpeg。

问题是从文本文件读取的输入会根据 ffmpeg 是否“正常”运行而变化。如果不执行 ffmpeg、运行不同的命令(例如 ls)、使用无效设置运行 ffmpeg(以便它立即退出),一切都会按缩进进行。使用ffmpeg,从时间戳文件中读取的文件是无效的。

我最好的猜测是 ffmpeg 以某种方式扰乱了 bash 脚本运行的环境,但是,操作系统设计告诉我们这应该是不可能的。

这是一个最小的示例及其输出:

脚本extract.sh

OUTDIR=samples

for f in test.txt; do    # in reality a proper filter such as "f in video-*.txt"
  BASE=${f%%.txt}
  VIDEO=${BASE}.mkv

  cd $OUTDIR

  while read ss; do
    echo "ffmpeg -i ../${VIDEO} -vsync 0 -ss ${ss} -t 00:00:01 '${BASE}-${ss}-%02d.png'"
    (ffmpeg -i ../invalid.mkv -vsync 0 -ss ${ss} -t 00:00:01 "${BASE}-${ss}-%02d.png" 2>&1) >> ${BASE}.log
    #(ffmpeg -i ../${VIDEO} -vsync 0 -ss ${ss} -t 00:00:01 "${BASE}-${ss}-%02d.png" 2>&1) >> ${BASE}.log
  done < ../${f}

  cd ..
done

时间戳文件test.txt

00:00:42
00:01:20
00:02:20
00:04:20
00:05:56
00:06:40

脚本中的第一个 ffmpeg 命令是不正确的命令(无效的视频文件)。如果激活,将从以下位置正确读取输入test.txt

$ ./extract.sh 
ffmpeg -i ../test.mkv -vsync 0 -ss 00:00:30 -t 00:00:01 'test-00:00:30-%02d.png'
ffmpeg -i ../test.mkv -vsync 0 -ss 00:00:42 -t 00:00:01 'test-00:00:42-%02d.png'
ffmpeg -i ../test.mkv -vsync 0 -ss 00:01:20 -t 00:00:01 'test-00:01:20-%02d.png'
ffmpeg -i ../test.mkv -vsync 0 -ss 00:02:20 -t 00:00:01 'test-00:02:20-%02d.png'
ffmpeg -i ../test.mkv -vsync 0 -ss 00:04:20 -t 00:00:01 'test-00:04:20-%02d.png'
ffmpeg -i ../test.mkv -vsync 0 -ss 00:05:56 -t 00:00:01 'test-00:05:56-%02d.png'
ffmpeg -i ../test.mkv -vsync 0 -ss 00:06:40 -t 00:00:01 'test-00:06:40-%02d.png'

禁用第一个(不正确的)ffmpeg 命令并启用第二个(正确的)命令后,读取的时间戳test.txt不正确:

$ ./extract.sh 
ffmpeg -i ../test.mkv -vsync 0 -ss 00:00:30 -t 00:00:01 'test-00:00:30-%02d.png'
ffmpeg -i ../test.mkv -vsync 0 -ss  -t 00:00:01 'test--%02d.png'
ffmpeg -i ../test.mkv -vsync 0 -ss 00:01:20 -t 00:00:01 'test-00:01:20-%02d.png'
ffmpeg -i ../test.mkv -vsync 0 -ss 05:56 -t 00:00:01 'test-05:56-%02d.png'

有任何想法吗?

答案1

  1. 使用变量时用双引号引起来。例如,

    cd "$OUTDIR"
    
  2. ffmpeg不喜欢:输出文件名。因此,将它们从$ss( ${ss//:/}) 中删除或将其更改为其他内容 ( ${ss//:/_}):

    ffmpeg -i "../$VIDEO" -vsync 0 -ss "$ss" -t 00:00:01 "$BASE-${ss//:/}-%02d.png" >> "$BASE.log" 2>&1 </dev/null
    
  3. ../如果您正在通过符号链接进行处理,则父目录构造可能会中断。因此,最好进入samples目录而不是从目录中进行操作。

  4. 一般来说,尽量避免变量名全部大写。它们可能与 shell 管理的变量名冲突($PATH$SECONDS等)

把这一切放在一起,

#!/bin/bash
outdir=samples

for vt in video-*.txt
do
    base=${vt%.txt}
    video=$base.mkv

    while read ss
    do
        echo "ffmpeg -i '$video' -vsync 0 -ss '$ss' -t 00:00:01 '$outdir/$base-${ss//:/}-%02d.png'" >&2
        ffmpeg -i "$video" -vsync 0 -ss "$ss" -t 00:00:01 "$outdir/$base-${ss//:/}-%02d.png" >> "$outdir/$base.log" 2>&1
    done <"$vt"
done

在我自己的示例文件中,这会为每个样本点生成 50 个 PNG 文件。

答案2

也许有点晚了,但问题是ffmpeg它也从标准输入读取,消耗了在 while-read-loop 中读取的数据。

我遇到了一些奇怪的时间戳混乱。要解决此问题,您可以ffmpeg通过两种方式重定向命令中的标准输入:

选项1:在命令行中将 -nostdin 放在 ffmpeg 后面

例子:

while read f; do ffmpeg -nostdin  -i "$f" -codec copy "${f%.*}.mp4" ; done

选项2:将 < /dev/null 放在 ffmpeg 命令行末尾

例子:

while read f; do ffmpeg -i "$f" -codec copy "${f%.*}.mp4" < /dev/null; done 

在这里找到了这个解决方案:

bash 循环中的 ffmpeg 问题

相关内容