5

对于上下文,我正在尝试创建一个 shell 脚本来简化 ffmpeg 的实时控制台输出,只显示正在编码的当前帧。我的最终目标是在批处理的某种进度指示器中使用这些信息。

对于那些不熟悉 ffmpeg 输出的人,它将编码的视频信息输出到 stdout,将控制台信息输出到 stderr。此外,当它实际显示编码信息时,它使用回车来防止控制台屏幕填满。这使得不可能简单地使用 grep 和 awk 来捕获适当的行和帧信息。

我尝试的第一件事是使用 tr 替换回车:

$ ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | tr '\r' '\n'

这样做的原因是它向控制台显示实时输出。但是,如果我随后将该信息传送到 grep 或 awk 或其他任何东西,则 tr 的输出将被缓冲并且不再是实时的。例如:$ ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | tr '\r' '\n'>log.txt导致文件立即填充一些信息,然后 5-10 秒后,更多行被放入日志文件中。

起初我认为 sed 会很适合这个: $ # ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | sed 's/\\r/\\n/',但它会到达所有回车符的行,并等到处理完成后再尝试做任何事情。我认为这是因为 sed 是逐行工作的,并且需要在执行其他任何操作之前完成整行,然后无论如何它都不会替换回车符。我已经为回车和换行尝试了各种不同的正则表达式,但还没有找到替代回车的解决方案。我正在运行 OSX 10.6.8,所以我使用的是 BSD sed,这可能是造成这种情况的原因。

我还尝试将信息写入日志文件并用于tail -f将其读回,但我仍然遇到实时替换回车的问题。

我已经看到在 python 和 perl 中有解决方案,但是,我不愿意立即走这条路。首先,我不知道 python 或 perl。其次,我有一个功能齐全的批处理 shell 应用程序,我需要移植或弄清楚如何与 python/perl 集成。可能不难,但不是我想进入的,除非我绝对必须这样做。所以我正在寻找一个shell解决方案,最好是bash,但任何OSX shell都可以。

如果我想要的根本不可行,那么我想当我到达那里时我会越过那座桥。

4

3 回答 3

5

如果只是管道后接收应用程序的输出缓冲问题。然后你可以尝试使用gawk(和一些 BSD awk)或者mawk可以刷新缓冲区。例如,尝试:

... | gawk '1;{fflush()}' RS='\r\n' > log.txt

或者,如果您 awk 不支持这一点,您可以通过反复关闭输出文件并附加下一行来强制执行此操作...

... | awk '{sub(/\r$/,x); print>>f; close(f)}' f=log.out

或者您可以只使用 shell,例如bash

... | while IFS= read -r line; do printf "%s\n" "${line%$'\r'}"; done > log.out
于 2013-05-01T14:31:30.897 回答
4

当 stdout 和 stderr 连接到终端时,Libc 使用行缓冲,当连接到管道时使用全缓冲(使用 4KB 缓冲区)。这发生在生成输出的过程中,而不是在接收过程中——ffmpeg在你的情况下,这是错误的,而不是tr's.

unbuffer ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | tr '\r' '\n'
stdbuf -e0 -o0 ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | tr '\r' '\n'

尝试使用unbufferstdbuf禁用输出缓冲。

于 2013-05-01T13:46:00.753 回答
1

管道中进程之间的数据缓冲由一些系统限制控制,至少在我的系统(Fedora 17)上无法修改:

$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8
$ ulimit -p 1
bash: ulimit: pipe size: cannot modify limit: Invalid argument
$ 

虽然这种缓冲主要与如果消费者没有以相同的速度消费,生产者在停止之前允许生产多少多余数据有关,但它也可能影响交付少量数据的时间(不太确定这一点)。

那是管道数据的缓冲,我认为这里没有太多需要调整的地方。但是,读取/写入管道数据的程序也可能会缓冲标准输入/标准输出数据,这在您的情况下是您想要避免的。

这是一个 perl 脚本,它应该以最少的输入缓冲和无输出缓冲来进行翻译:

#!/usr/bin/perl
use strict;
use warnings;

use Term::ReadKey;
$ReadKeyTimeout = 10; # seconds

$| = 1; # OUTPUT_AUTOFLUSH

while( my $key = ReadKey($ReadKeyTimeout) ) {
        if ($key eq "\r") {
                print "\n";
                next;
        }
        print $key;
}

但是,正如已经指出的那样,如果您想要实时响应,您应该确保 ffmpeg 不会缓冲其输出。

于 2013-05-01T13:54:43.363 回答