我已经为此摸不着头脑有一段时间了;也许你们中的一个人可以帮助我。
我正在尝试从视频中的某些位置提取视频帧。为此,位置以“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
使用变量时用双引号引起来。例如,
cd "$OUTDIR"
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
../
如果您正在通过符号链接进行处理,则父目录构造可能会中断。因此,最好进入samples
目录而不是从目录中进行操作。一般来说,尽量避免变量名全部大写。它们可能与 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
在这里找到了这个解决方案: