2009-04-14 15 views
45

Konwertuję plik .avi do pliku .flv za pomocą ffmpeg. Ponieważ konwersja pliku wymaga długiego czasu, chciałbym wyświetlić pasek postępu. Czy ktoś może mnie poprowadzić, jak postępować tak samo.Czy ffmpeg może wyświetlać pasek postępu?

Wiem, że ffmpeg musi w jakiś sposób wypisać postęp w pliku tekstowym i muszę go odczytać przy użyciu wywołań ajaxowych. Ale jak mogę uzyskać ffmpeg, aby wyprowadzić postęp do pliku tekstowego?

Dziękuję bardzo.

Odpowiedz

1

Wywołanie funkcji systemowej php blokuje ten wątek, więc musisz odrodzić 1 żądanie HTTP do wykonania konwersji, a inny moduł odpytujący do odczytu pliku txt, który jest generowany.

Albo, jeszcze lepiej, klienci przesyłają wideo do konwersji, a następnie inny proces jest odpowiedzialny za wykonanie konwersji. W ten sposób połączenie klienta nie będzie miało limitu czasu podczas oczekiwania na zakończenie wywołania systemowego. Odpytywanie odbywa się w taki sam sposób jak powyżej.

+3

Ale dlaczego użyć pliku tekstowego? musi być lepszy sposób. Czy nie musiałbyś tworzyć nowego pliku tekstowego dla każdego przesłania? – Scarface

19

FFmpeg używa standardowego wyjścia do wyprowadzania danych medialnych i stderr dla informacji rejestracyjnych/postępu. Musisz tylko przekierować stderr do pliku lub stdin procesu, który może go obsłużyć.

Z Unix shell to jest coś takiego:

ffmpeg {ffmpeg arguments} 2> logFile 

lub

ffmpeg {ffmpeg arguments} 2| processFFmpegLog 

W każdym razie, trzeba uruchomić ffmpeg jako oddzielnego wątku lub procesu.

+2

Gdybym miał zadać to pytanie, z radością przyjmuję twoją odpowiedź. – Riduidel

+0

@mouviciel, czy wiesz, jak mogę to osiągnąć w .NET? Próbowałem, co powiedziałeś i nie wydrukował pliku, także Jak mogę otrzymać powiadomienie, gdy plik zostanie zmieniony? – Shimmy

+2

Przepraszam, nie wiem .NET. – mouviciel

20

W języku rosyjskim jest article, który opisuje, jak rozwiązać problem.

Chodzi o to, aby złapać wartość Duration przed kodowaniem oraz aby przechwycić wartości time=... podczas kodowania.

--skipped-- 
Duration: 00:00:24.9, start: 0.000000, bitrate: 331 kb/s 
--skipped-- 
frame= 41 q=7.0 size=  116kB time=1.6 bitrate= 579.7kbits/s 
frame= 78 q=12.0 size=  189kB time=3.1 bitrate= 497.2kbits/s 
frame= 115 q=13.0 size=  254kB time=4.6 bitrate= 452.3kbits/s 
--skipped-- 
+3

Poszedłem do artykułu, spodziewając się trochę problemów z rosyjskim, ale było to zaskakująco łatwe do odczytania. –

+0

to powinno być zaakceptowane odpowiedź –

26

Bawiłem się z tym przez kilka dni. Pomogło mi to "ffmpegprogress", ale bardzo ciężko było pracować z moją konfiguracją i trudno było odczytać kod.

W celu wykazania postępu ffmpeg trzeba wykonać następujące czynności:

  1. uruchomić polecenie ffmpeg od PHP bez niego czekając na odpowiedź (dla mnie, to była najtrudniejsza część)
  2. powiedz ffmpeg, aby wysłać wynik do pliku
  3. z przedniego końca (AJAX, Flash, cokolwiek) uderzył bezpośrednio w ten plik lub plik php, który może wyciągnąć postęp z wyjścia ffmpeg.

Oto jak rozwiązać każdą część:

1. mam następujący pomysł z "ffmpegprogress". Oto, co zrobił: jeden plik PHP wywołuje inny przez gniazdo http. Drugi faktycznie uruchamia "exec", a pierwszy plik po prostu się rozłącza. Dla mnie jego wdrożenie było zbyt skomplikowane. Używał "fsockopen". Lubię CURL.Więc oto co zrobiłem:

$url = "http://".$_SERVER["HTTP_HOST"]."/path/to/exec/exec.php"; 
curl_setopt($curlH, CURLOPT_URL, $url); 
$postData = "&cmd=".urlencode($cmd); 
$postData .= "&outFile=".urlencode("path/to/output.txt"); 
curl_setopt($curlH, CURLOPT_POST, TRUE); 
curl_setopt($curlH, CURLOPT_POSTFIELDS, $postData); 

curl_setopt($curlH, CURLOPT_RETURNTRANSFER, TRUE); 

// # this is the key! 
curl_setopt($curlH, CURLOPT_TIMEOUT, 1); 
$result = curl_exec($curlH); 

Ustawianie CURLOPT_TIMEOUT do 1 oznacza, że ​​będzie czekać 1 sekundy na odpowiedź. Korzystnie byłoby to niższe. Istnieje również CURLOPT_TIMEOUT_MS, który trwa milisekundy, ale nie zadziałał on dla mnie.

Po 1 sekundzie CURL rozłącza się, ale polecenie exec nadal działa. Część 1 rozwiązana.

BTW - Kilka osób sugerowało użycie polecenia "nohup" w tym celu. Ale wydawało się, że to nie działa dla mnie.

* RÓWNIEŻ! Posiadanie pliku php na serwerze, który może wykonywać kod bezpośrednio w linii poleceń, jest oczywistym zagrożeniem bezpieczeństwa. Powinieneś mieć hasło lub kodować dane dotyczące postów w jakiś sposób.

2. Powyższy skrypt "exec.php" musi również informować ffmpeg o wyprowadzeniu do pliku. Oto kod, który:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1> path/to/output.txt 2>&1"); 

Uwaga "1> ścieżka/do/output.txt 2> & 1". Nie jestem ekspertem od linii poleceń, ale z tego co mogę powiedzieć, ta linia mówi: "wyślij normalne wyjście do tego pliku, I wyślij błędy w to samo miejsce". Sprawdź ten adres URL, aby uzyskać więcej informacji: http://tldp.org/LDP/abs/html/io-redirection.html

3. Z przodu wywołaj skrypt php, podając lokalizację pliku output.txt. Ten plik php następnie wyciągnie postęp z pliku tekstowego. Oto, jak to zrobiłem:

// # get duration of source 
preg_match("/Duration: (.*?), start:/", $content, $matches); 

$rawDuration = $matches[1]; 

// # rawDuration is in 00:00:00.00 format. This converts it to seconds. 
$ar = array_reverse(explode(":", $rawDuration)); 
$duration = floatval($ar[0]); 
if (!empty($ar[1])) $duration += intval($ar[1]) * 60; 
if (!empty($ar[2])) $duration += intval($ar[2]) * 60 * 60; 


// # get the current time 
preg_match_all("/time=(.*?) bitrate/", $content, $matches); 

$last = array_pop($matches); 
// # this is needed if there is more than one match 
if (is_array($last)) { 
    $last = array_pop($last); 
} 

$curTime = floatval($last); 


// # finally, progress is easy 
$progress = $curTime/$duration; 

Mam nadzieję, że to komuś pomaga.

+0

Pomogło mi to, dzięki Mike. – Scarface

+0

Działa świetnie! dzięki +1 –

+1

Wiem, że to stary wątek, ale chciałbym dodać coś, co odkryłem dzisiaj. Wydaje się, że wraz z ffmpeg pojawia się ffprobe, który może być użyty do uzyskania informacji w wyjściu standardowym ("ffmpeg -i {plik}" faktycznie kończy się wraz z kodem błędu, więc musisz wyprowadzić wynik za pomocą 2> 1, aby uzyskać informacje). więc, masz na to: 'ffprobe -v cichy -print_format json -show_format -i file.mp4 -show_streams' – thorne51

1

Miał problemy z drugą częścią php. Używam tego:

$log = @file_get_contents($txt); 
    preg_match("/Duration:([^,]+)/", $log, $matches); 
    list($hours,$minutes,$seconds,$mili) = split(":",$matches[1]); 
    $seconds = (($hours * 3600) + ($minutes * 60) + $seconds); 
    $seconds = round($seconds); 

    $page = join("",file("$txt")); 
    $kw = explode("time=", $page); 
    $last = array_pop($kw); 
    $values = explode(' ', $last); 
    $curTime = round($values[0]); 
    $percent_extracted = round((($curTime * 100)/($seconds))); 

Wyjściowo.

Chciałbyś zobaczyć coś dla wielu przesłanych plików na inny pasek postępu. To przejście dla bieżącego pliku na jeden procent. Następnie ogólny pasek postępu. Prawie na miejscu.

Ponadto, jeśli ludzie mają trudności z dostaniem:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1> path/to/output.txt 2>&1"); 

do pracy.

Spróbuj:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1>path/to/output.txt 2>&1"); 

"1> ścieżka" na "1> ścieżka" LUB "2> ścieżka" na "2> ścieżka"

Zajęło mi chwilę, aby to rozgryźć. FFMPEG ciągle kończyło się niepowodzeniem. Pracowałem, kiedy zmieniłem się na brak przestrzeni.

1

javascript powinien powiedzieć php, aby rozpocząć konwersję [1], a następnie wykonać [2] ...


[1]php: konwersja start i możliwość pisania do pliku (patrz wyżej):

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1>path/to/output.txt 2>&1"); 

Dla drugiej strony musimy tylko javascript czytać plik. Poniższy przykład używa dojo.request dla technologii AJAX, ale można użyć jQuery lub wanilii lub cokolwiek, a także:

[2]JS: chwycić postęp z pliku:

var _progress = function(i){ 
    i++; 
    // THIS MUST BE THE PATH OF THE .txt FILE SPECIFIED IN [1] : 
    var logfile = 'path/to/output.txt'; 

/* (example requires dojo) */ 

request.post(logfile).then(function(content){ 
// AJAX success 
    var duration = 0, time = 0, progress = 0; 
    var resArr = []; 

    // get duration of source 
    var matches = (content) ? content.match(/Duration: (.*?), start:/) : []; 
    if(matches.length>0){ 
     var rawDuration = matches[1]; 
     // convert rawDuration from 00:00:00.00 to seconds. 
     var ar = rawDuration.split(":").reverse(); 
     duration = parseFloat(ar[0]); 
     if (ar[1]) duration += parseInt(ar[1]) * 60; 
     if (ar[2]) duration += parseInt(ar[2]) * 60 * 60; 

     // get the time 
     matches = content.match(/time=(.*?) bitrate/g); 
     console.log(matches); 

     if(matches.length>0){ 
      var rawTime = matches.pop(); 
      // needed if there is more than one match 
      if (lang.isArray(rawTime)){ 
       rawTime = rawTime.pop().replace('time=','').replace(' bitrate',''); 
      } else { 
       rawTime = rawTime.replace('time=','').replace(' bitrate',''); 
      } 

      // convert rawTime from 00:00:00.00 to seconds. 
      ar = rawTime.split(":").reverse(); 
      time = parseFloat(ar[0]); 
      if (ar[1]) time += parseInt(ar[1]) * 60; 
      if (ar[2]) time += parseInt(ar[2]) * 60 * 60; 

      //calculate the progress 
      progress = Math.round((time/duration) * 100); 
     } 

     resArr['status'] = 200; 
     resArr['duration'] = duration; 
     resArr['current'] = time; 
     resArr['progress'] = progress; 

     console.log(resArr); 

     /* UPDATE YOUR PROGRESSBAR HERE with above values ... */ 

     if(progress==0 && i>20){ 
      // TODO err - giving up after 8 sec. no progress - handle progress errors here 
      console.log('{"status":-400, "error":"there is no progress while we tried to encode the video" }'); 
      return; 
     } else if(progress<100){ 
      setTimeout(function(){ _progress(i); }, 400); 
     } 
    } else if(content.indexOf('Permission denied') > -1) { 
     // TODO - err - ffmpeg is not executable ... 
     console.log('{"status":-400, "error":"ffmpeg : Permission denied, either for ffmpeg or upload location ..." }');  
    } 
}, 
function(err){ 
// AJAX error 
    if(i<20){ 
     // retry 
     setTimeout(function(){ _progress(0); }, 400); 
    } else { 
     console.log('{"status":-400, "error":"there is no progress while we tried to encode the video" }'); 
     console.log(err); 
    } 
    return; 
}); 

} 
setTimeout(function(){ _progress(0); }, 800); 
+0

Powyższy kod podaje poniżej błąd. Jeśli to możliwe, odpowiedz. ** Błąd analizy składni: błąd składni, nieoczekiwany T_VAR ** – KRA

11

To bardzo proste, jeśli użyjesz polecenia pipeview. Aby to zrobić, należy przekształcić

ffmpeg -i input.avi {arguments} 

do

pv input.avi | ffmpeg -i pipe:0 -v warning {arguments} 

nie trzeba się do kodowania!

+0

Podoba mi się ta odpowiedź. Jest bardzo elegancki. Odpowiednim argumentem dla 'pv' jest' -n', aby uzyskać tylko numeryczny postęp w procentach. – Basti

+5

Pamiętaj, że 'ffmpeg' nie będzie kodował z' stdin' dla wideo, które nie są przesyłane strumieniowo, jak plik '* .mov', w którym metadane są na końcu. Powodem jest to, że 'ffmpeg' nie może szukać danych meta na rurze. Trzeba by "qt-faststart" każdy plik wejściowy, zanim można potokować go do 'ffmpeg'. – Basti

5

Można to zrobić z ffmpeg „s -progress argumentem i nc

WATCHER_PORT=9998 

DURATION= $(ffprobe -select_streams v:0 -show_entries "stream=duration" \ 
    -of compact $INPUT_FILE | sed 's!.*=\(.*\)!\1!g') 

nc -l $WATCHER_PORT | while read; do 
    sed -n 's/out_time=\(.*\)/\1 of $DURATION/p') 
done & 

ffmpeg -y -i $INPUT_FILE -progress localhost:$WATCHER_PORT $OUTPUT_ARGS 
+2

Wygląda na to, że prowadzi do najlepszego rozwiązania. Możesz przesyłać informacje o postępie za pomocą 'ffmpeg -v warning -progress/dev/stdout -i in.mp4 out.mp4' na' stdout', aby otrzymywać aktualizacje postępu raz na sekundę. Jeśli potrzebujesz tylko wartości procentowej, możesz 'ffprobe -v quiet -print_format json -show_format in.mp4' dla' duration' w sekundach przed rozpoczęciem kodowania, a następnie '| awk -F "=" '/ out_time_ms/{print $ 2}' ', aby uzyskać tylko bieżący czas trwania w ms, gdzie' progress = total_duration * 100 * 1000/current_duration'. – Basti

+1

Nie wiedziałem, że możesz skierować postęp do pliku; jeśli tak jest, możesz po prostu zrobić pięć z "sed" i być na dobrej drodze. – iluvcapra

+0

Dobrym pomysłem może być utworzenie nazwanego potoku i wywołanie 'ffmpeg' i' sed | grep | awk | cokolwiek "oddzielnie", aby uzyskać kod powrotu 'ffmpeg'. Ponadto wysyłam standardowe wyjście 'ffmpeg' do pliku logu, tak aby w razie błędu (kod> 0) miałeś na co popatrzeć. Moje polecenie wip wygląda obecnie na przykład 'ffmpeg -v ostrzeżenie -progress/dev/stdout -y -i $ source $ encodingOptions $ target >> $ logFile | grep --line-buffered out_time_ms'. – Basti