Вопрос или проблема
Я использую EndeavourOS, и когда я воспроизвожу аудио с помощью команды ffmpeg -i test.flac -f alsa default
, терминал показывает такой вывод:
size=N/A time=00:00:21.31 bitrate=N/A speed=1.22x
Я хотел бы захватить параметр ‘time=’ и его значение. Я знаю, что ffmpeg выводит информацию в STDERR, поэтому я знаю, что нужно перенаправить 2>1, и поэтому я пробую:
ffmpeg -i test.flac -f alsa default 2>&1 | grep time
…и терминал остается совершенно пустым в этот момент (хотя музыка играет отлично).
Я также пробовал:
ffmpeg -i test.flac -f alsa default 2>test.txt
…а в другой сессии терминала:
tail -f test.txt | grep time
…и все равно пусто, хотя если я просто использую tail без grep, я вижу соответствующую строку с постоянно увеличивающимся значением time=.
Я знаю, что предложат использовать ffplay, но у меня много кода, использующего ffmpeg, и переключение не вариант. Я действительно больше заинтересован в ответе на вопрос, почему grep просто не работает с выводом STDERR ffmpeg, независимо от того, куда он перенаправлен! Почти второстепенно: есть ли способ получить значение ‘текущей позиции в потоке’ с помощью только ffmpeg?
В свете комментариев ниже:
ffmpeg -v
ffmpeg version n7.1 Copyright (c) 2000-2024 разработчики FFmpeg
собрано с использованием gcc 14.2.1 (GCC) 20240910
И обрезанный вывод от простого воспроизведения музыки, без перенаправлений:
TAGDATE : 1623718801
encoder : Lavf61.7.100
Stream #0:0: Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s
Metadata:
encoder : Lavc61.19.100 pcm_s16le
size=N/A time=00:00:15.36 bitrate=N/A speed=1.34x
И несколько примеров того, что я могу и не могу найти с помощью grep из этого вывода:
[zelenka Desktop]$ ffmpeg -i test.flac -f alsa default 2>&1 | grep Duration
Duration: 00:21:10.00, start: 0.000000, bitrate: 690 kb/s
^C^C^C
[zelenka Desktop]$ ffmpeg -i test.flac -f alsa default 2>&1 | grep Audio
Stream #0:0: Audio: flac, 44100 Hz, stereo, s16
Stream #0:0: Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s
^C^C^C
[zelenka Desktop]$ ffmpeg -i test.flac -f alsa default 2>&1 | grep time=
^C^C^C
(И в этот момент мне нужно Ctrl+C, чтобы прервать, так как ничего не появляется на экране). Поиск по ‘статическим’ данным работает; поиск по динамической части строки size=N/A time=00:00:15.36 bitrate=N/A speed=1.34x
не работает.
ffmpeg -i test.flac -f alsa default |& awk -v RS="\r" 'NR > 1 && /time/'
Если вы хотите увидеть только поле time
:
$ ffmpeg -i test.flac -f alsa default |& awk -v RS="\r" 'NR > 1 && /time/ {print $2}'
time=00:00:04.36
time=00:00:04.92
time=00:00:05.47
time=00:00:06.03
time=00:00:06.59
time=00:00:07.15
time=00:00:07.70
Если вы не хотите видеть часть time=
:
$ ffmpeg -i test.flac -f alsa default |& awk -v RS="\r" -v FS='[= ]' 'NR > 1 && /time/ {print $4}'
00:00:04.36
00:00:04.92
00:00:05.47
00:00:06.03
00:00:06.59
00:00:07.15
00:00:07.70
Если вы все еще хотите перенаправить вывод в другой файл, вы можете использовать
для сброса вывода в awk, но помните, что чем больше операций вы выполняете в
pipeline, тем медленнее он работает:
$ ffmpeg -i test.flac -f alsa default |& awk -v RS="\r" -v FS='[= ]' 'NR > 1 && /time/ {print $4; fflush("/dev/stdout")}' | tee /tmp/HERE
ffmpeg -i test.flac -f alsa default 2>&1 | grep time
не работает,
потому что grep основан на строках, но новая строка для него означает \n
(ASCII
0xa, вы можете увидеть это man ascii
), но ffmpeg использует \r
(ASCII 0xd) для
возврата в начало строки и очистки ее, чтобы информация о времени всегда
показывалась в одной и той же строке. Вы можете увидеть это с помощью strace:
$ strace -f -s 4096 ffmpeg -i test.flac -f alsa default |& grep time=
[pid 2666] write(2, "size=N/A time=00:00:05.47 bitrate=N/A speed=3.24x \r", 54 <unfinished ...>
Что означает часть RS=
Это разделитель записей в awk, вы можете узнать о нем больше в руководстве по GNU awk.
Ответ или решение
Чтобы захватить параметр "time=" из вывода FFmpeg, мы можем столкнуться с несколькими трудностями, связанными со спецификой его работы и форматом вывода. В данном случае важно понимать, что FFmpeg выводит информацию о состоянии на стандартный поток ошибок (STDERR), и, как вы уже правильно заметили, этот вывод может не сразу отображаться, когда мы пытаемся фильтровать его с помощью grep
. Давайте разберем возможные методы решения этой задачи.
Проблема с выводом и grep
FFmpeg периодически обновляет строку вывода с информацией о текущем времени, используя символ возврата каретки (\r
), что очищает текущую строку и перезаписывает её. Это означает, что вывод, который мы ищем, не имеет привычного завершения строки (\n
), что делает стандартные инструменты вроде grep
неэффективными для фильтрации такого вывода.
Использование awk
для обработки вывода
Вместо grep
можно воспользоваться awk
, который позволяет более гибко работать с границами записей. Мы можем использовать специальный символ для разделения записей, чтобы корректно обрабатывать вывод FFmpeg.
Вот команда, используя awk
, которая будет захватывать строки с параметром time
:
ffmpeg -i test.flac -f alsa default 2>&1 | awk -v RS="\r" '/time=/{print $0}'
Параметры команды
-i test.flac
: Указывает входной файл.-f alsa default
: Указывает выходное устройство.2>&1
: Перенаправляет вывод потока ошибок в стандартный поток вывода.awk -v RS="\r"
: Устанавливает символ возврата каретки в качестве разделителя записей./time=/
: Фильтрует только те записи, которые содержат "time=".
Извлечение только значения времени
Если вы хотите получить только значение параметра time
, вам необходимо дополнительно настроить awk
:
ffmpeg -i test.flac -f alsa default 2>&1 | awk -v RS="\r" -F '[= ]' '/time=/{print $4}'
Описание настроек
-F '[= ]'
: Устанавливает разделители для полей, что позволяет легко извлечь значение времени.print $4
: Печатает четвёртое поле, которое содержит фактическое значение времени.
Запись в файл
Если вам нужно записать вывод в файл, вы также можете использовать команду tee
:
ffmpeg -i test.flac -f alsa default 2>&1 | awk -v RS="\r" '/time=/{print $0; fflush();}' | tee output.txt
Заключение
Использование awk
в данном контексте является наиболее адекватным решением. Символ возврата каретки, используемый FFmpeg для обновления выводимых данных, требует специализированного подхода для эффективного парсинга. Убедитесь, что вы тестируете команды в своем терминале, чтобы адаптировать их под ваши конкретные сценарии использования.