2013-05-01 20 views
5

Dla kontekstu próbuję utworzyć skrypt powłoki, który upraszcza wyjście konsoli ffmpeg w czasie rzeczywistym, wyświetlając tylko bieżącą ramkę, która jest kodowana. Moim celem końcowym jest wykorzystanie tych informacji w pewnego rodzaju wskaźniku postępu dla przetwarzania wsadowego.Usuwanie w czasie rzeczywistym powrotu karetki w powłoce

Dla tych, którzy nie są zaznajomieni z wynikami ffmpeg, wyprowadza zakodowane informacje wideo na stdout i informacje konsolowe na stderr. Ponadto, gdy faktycznie wyświetla informacje o kodowaniu, wykorzystuje powrót karetki, aby nie wypełniać ekranu konsoli. Dzięki temu nie można po prostu użyć grep i awk do przechwycenia odpowiednich informacji o linii i ramie.

Pierwszą rzeczą Próbowałem jest zastąpienie powrotu karetki pomocą tr:

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

Działa to tym, że wyświetla obrazy w czasie rzeczywistym do konsoli. Jednakże, jeśli następnie potoku tej informacji do grep lub awk lub cokolwiek innego, dane wyjściowe tr są buforowane i nie jest już w czasie rzeczywistym. Na przykład: $ ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | tr '\r' '\n'>log.txt powoduje wyświetlenie pliku, który jest natychmiast wypełniony pewnymi informacjami, a następnie 5-10 sekund później, kolejne wiersze zostają upuszczone do pliku dziennika.

Na początku myślałem, że sed będzie świetny do tego: $ # ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | sed 's/\\r/\\n/', ale dotrze do linii z wszystkimi powrotami karetki i czeka, aż przetwarzanie zakończy się, zanim podejmie próbę zrobienia czegokolwiek. Zakładam, że dzieje się tak dlatego, że sed działa na zasadzie line-by-line i wymaga ukończenia całej linii, zanim zrobi cokolwiek innego, i wtedy i tak nie zastąpi powrotu karetki. Próbowałem różnych regex dla powrotu karetki i nowej linii, i jeszcze nie znalazłem rozwiązania, które zastąpiłoby powrót karetki. Używam OSX 10.6.8, więc używam BSD sed, co może to wyjaśniać.

Próbowałem również zapisać informacje w pliku dziennika i użyć tail -f, aby go odczytać, ale wciąż napotykam problem zastępowania zwrotów karetki w czasie rzeczywistym.

Widziałem, że istnieją rozwiązania dla tego w python i perl, jednak jestem niechętny, aby przejść tę trasę natychmiast. Po pierwsze, nie wiem Pythona lub Perl. Po drugie, mam w pełni funkcjonalną aplikację powłoki przetwarzania wsadowego, którą potrzebowałbym do przeniesienia lub wymyślenia jak zintegrować z pythonem/perlem. Prawdopodobnie nie jest to trudne, ale nie to, co chcę osiągnąć, chyba że absolutnie muszę. Tak więc szukam rozwiązania powłoki, najlepiej bash, ale każda z powłok OSX byłaby w porządku.

A jeśli tego, czego chcę, po prostu nie da się zrobić, to chyba przejdę przez ten most, kiedy tam dotrę.

Odpowiedz

4

Jeśli chodzi tylko o buforowanie wyjściowe przez aplikację odbierającą po potoku. Następnie możesz spróbować użyć gawk (i jakiegoś awk BSD) lub mawk, który może opróżnić bufory. Na przykład, spróbuj:

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

Ewentualnie jeśli awk nie obsługuje to można wymusić to poprzez wielokrotne zamykanie pliku wyjściowego i dołączanie do następnego wiersza ...

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

Lub może po prostu użyć powłoka, na przykład w bash:

... | while IFS= read -r line; do printf "%s\n" "${line%$'\r'}"; done > log.out 
+0

Dziękuję bardzo! Twoje pierwsze polecenie działa idealnie, chociaż oczywiście użyłem 'awk', ponieważ jestem na OSX. To oszczędza mi tyle bólu głowy! – Seth

+1

Jedną z rzeczy, które należy dodać do tej wspaniałej odpowiedzi: sprawdź, w jaki sposób zainstalowana jest dokumentacja awk/gawk/mawk, aby dowiedzieć się, jak interpretować RS. W moim lokalnym systemie OSX, awk miał niejawne OR dla separatora rekordów (albo powrót karetki, albo znak nowej linii). Na moim serwerze Ubuntu gawk 4.0.1 interpretuje RS jako wyrażenie regularne, jeśli ma więcej niż jeden znak. Musiałem więc użyć 'RS = '\ r | \ n'', aby osiągnąć to samo zachowanie, które widziałem na OSX. – ajmicek

4

Libc używa buforowania linii, gdy stdout i stderr są podłączone do terminala i pełnego buforowania (z buforem 4KB) po podłączeniu do rury. Dzieje się tak w procesie generowania danych wyjściowych, a nie w procesie odbiorczym, w twoim przypadku, a nie .

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' 

Spróbuj użyć unbuffer or stdbuf, aby wyłączyć buforowanie wyjściowe.

+0

Jestem trochę zdezorientowany tym. Jeśli po prostu wypakuję stderr do pliku, tj. '2> log.txt', otrzymam względnie aktualizację pliku w czasie rzeczywistym (jeśli użyję' tail -f' w pliku, widzę, co uważam za aktualizację buforowaną linią; znacznie szybszy niż robi to buforowanie "tr"). Pod względem 'unbuffer' i' stdbuf', nie wydaje mi się, aby któryś z nich był w moim systemie, chociaż mam 'spodziewać się', co sprawiło, że pomyślałem, że mam' unbuffer'. Chciałbym trochę przenieść do mojego skryptu, więc jeśli potrzebuję użyć niestandardowej aplikacji, chciałbym mieć statyczny plik wykonywalny, który mógłby podróżować z moimi skryptami. – Seth

+0

W porządku, próbowałem 'unbuffer', robiąc to sam z instrukcji [tutaj] (http://superuser.com/questions/59497/writing-tail-f-output-to-another-file). To wciąż tylko aktualizuje się w buforowanych blokach, a nie po linii. – Seth

1

Buforowanie danych pomiędzy procesami w rurę jest sterowany z pewnymi ograniczeniami układu, który jest co najmniej w systemie (Fedora 17) nie można zmieniać:

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

Chociaż buforujący jest związane głównie z ile nadmiaru danych producent może wyprodukować, zanim zostanie zatrzymany, jeśli konsument nie zużywa z tą samą prędkością, może również wpływać na czas dostarczania mniejszych ilości danych (nie do końca pewne).

To jest buforowanie danych w rurze i nie sądzę, że trzeba tu poprawić. Jednak programy odczytujące/zapisujące dane potokowe mogą również buforować dane stdin/stdout i tego uniknąć w swoim przypadku.

Oto skrypt Perl, który powinien zrobić tłumaczenie z minimalnym buforowaniem wejściowym i bez buforowania wyjścia:

#!/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; 
} 

Jednakże, jak już wyżeł się, należy upewnić się, że ffmpeg nie bufor swoje wyjście jeśli ciebie żądać odpowiedzi w czasie rzeczywistym.