2009-04-14 19 views
45

Estoy convirtiendo un archivo .avi a un archivo .flv usando ffmpeg. Como lleva mucho tiempo convertir un archivo, me gustaría mostrar una barra de progreso. ¿Puede alguien por favor guiarme sobre cómo proceder de la misma manera?¿Puede ffmpeg mostrar una barra de progreso?

Sé que ffmpeg de alguna manera tiene que generar el progreso en un archivo de texto y tengo que leerlo usando ajax calls. Pero, ¿cómo obtengo ffmpeg para enviar el progreso al archivo de texto?

Muchas gracias.

Respuesta

1

Llamar a la función de sistema de php bloquea esa cadena, por lo que deberá generar 1 solicitud HTTP para realizar la conversión, y otra encuesta para leer el archivo txt, que se está generando.

O, mejor aún, los clientes envían el video para la conversión y luego otro proceso se hace responsable de realizar la conversión. De esta forma, la conexión del cliente no se agotará mientras espera que finalice la llamada al sistema. El sondeo se hace de la misma manera que arriba.

+3

Pero ¿por qué utilizar un archivo de texto? Tiene que haber una mejor manera. ¿No tendrías que hacer un nuevo archivo de texto por cada carga? – Scarface

19

FFmpeg usa stdout para generar datos multimedia y stderr para la información de registro/progreso. Solo tiene que redirigir stderr a un archivo o al stdin de un proceso capaz de manejarlo.

Con un shell de Unix esto es algo como:

ffmpeg {ffmpeg arguments} 2> logFile 

o

ffmpeg {ffmpeg arguments} 2| processFFmpegLog 

De todos modos, hay que correr FFmpeg como un proceso o subproceso separado.

+2

Si tuviera que hacer la pregunta, con gusto aceptaría su respuesta. – Riduidel

+0

@mouviciel, ¿sabes cómo puedo lograr esto en .NET? Intenté lo que dijiste y no imprime un archivo, también ¿Cómo puedo recibir una notificación cuando se cambie el archivo? – Shimmy

+2

Lo siento, no sé .NET. – mouviciel

20

Hay un article en ruso que describe cómo resolver su problema.

El punto es capturar Duration valor antes de codificar y capturar time=... valores durante la codificación.

--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

Fui al artículo esperando luchar un poco con el ruso, pero fue sorprendentemente fácil de leer. –

+0

esto debe aceptarse respuesta –

26

He estado jugando con esto durante unos días. Esa cosa "ffmpegprogress" ayudó, pero fue muy difícil llegar a trabajar con mi configuración, y es difícil leer el código.

Con el fin de mostrar el progreso de FFmpeg que tiene que hacer lo siguiente:

  1. ejecutar el comando ffmpeg desde PHP sin ella esperando una respuesta (para mí, esta fue la parte más difícil)
  2. dile a ffmpeg que envíe su salida a un archivo
  3. desde el extremo frontal (AJAX, Flash, lo que sea), golpea ese archivo directamente o un archivo php que puede extraer el progreso de la salida de ffmpeg.

Así es como he resuelto cada parte:

1. me dio la siguiente idea de "ffmpegprogress". Esto es lo que hizo: un archivo PHP llama a otro a través de un socket http. El segundo en realidad ejecuta el "ejecutivo" y el primer archivo simplemente cuelga en él. Para mí, su implementación fue demasiado compleja. Él estaba usando "fsockopen". Me gusta CURL.Así que aquí está lo que hice:

$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); 

Configuración CURLOPT_TIMEOUT a 1 significa que se va a esperar 1 segundo para una respuesta. Preferiblemente eso sería más bajo. También está el CURLOPT_TIMEOUT_MS que demora milisegundos, pero no funcionó para mí.

Después de 1 segundo, CURL cuelga, pero el comando exec aún se ejecuta. Parte 1 resuelta.

BTW - Algunas personas sugirieron usar el comando "nohup" para esto. Pero eso no pareció funcionar para mí.

* TAMBIÉN! Tener un archivo php en su servidor que pueda ejecutar código directamente en la línea de comandos es un riesgo obvio de seguridad. Debe tener una contraseña o codificar los datos de la publicación de alguna manera.

2. El script "exec.php" de arriba también debe decirle a ffmpeg que muestre un archivo. Aquí está el código para eso:

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

Nota del "1> ruta/a/2 salida.txt> & 1". No soy un experto en línea de comandos, pero por lo que puedo decir, esta línea dice "envíe salida normal a este archivo Y envíe errores al mismo lugar". Echa un vistazo a la siguiente dirección URL para obtener más información: http://tldp.org/LDP/abs/html/io-redirection.html

3. desde el extremo frontal llamar a un script php que le da la ubicación del archivo salida.txt. Ese archivo php luego sacará el progreso del archivo de texto. He aquí cómo lo hice:

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

Espero que esto ayude a alguien.

+0

Esto me ayudó, gracias Mike. – Scarface

+0

¡Funciona muy bien! Gracias +1 –

+1

Sé que este es un hilo viejo pero me gustaría agregar algo que descubrí hoy. Parece que junto con ffmpeg viene ffprobe, que se puede usar para obtener información en salida estándar ("ffmpeg -i {file}" en realidad sale con un código de error, por lo que tendrás que canalizar la salida usando 2> 1 para obtener la información). por lo tanto, tenga en ello: 'ffprobe -v quiet -print_format json -show_format -i file.mp4 -show_streams' – thorne51

1

Tuve problemas con la segunda parte de php. Así que estoy usando esto en su lugar:

$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))); 

Salidas a la perfección.

Me gustaría ver algo para cargas múltiples para otra barra de progreso. Esto pasa para el archivo actual por un porcentaje. Luego, una barra de progreso general. Casi ahí.

Además, si la gente está teniendo dificultades para conseguir:

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

a trabajar.

Probar:

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

"1> ruta" a "1> ruta" O "2> ruta" a "2> ruta"

Me llevó un tiempo para resolverlo. FFMPEG siguió fallando. Funcionó cuando cambié a ningún espacio.

1

JavaScript debería indicar a php que comience a convertir [1] y luego haga [2] ...


[1]php: conversión de inicio y escribir de estado para presentar (véase más arriba):

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

Para la segunda parte que necesitamos simplemente Javascript para leer el archivo. el siguiente ejemplo se utiliza dojo.request para AJAX, pero se puede usar jQuery o vainilla o lo que sea así:

[2]JS: agarrar el progreso del archivo:

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

Su código anterior muestra el siguiente error. Por favor responde si es posible ** Error de análisis: error de sintaxis, inesperado T_VAR ** – KRA

11

Es muy simple si usa el comando pipeview. Para ello, transformar

ffmpeg -i input.avi {arguments} 

a

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

No hay necesidad de entrar en la codificación!

+0

Me gusta esta respuesta. Es muy elegante Un argumento relevante para 'pv' es' -n' para obtener solo el progreso numérico en porcentaje. – Basti

+5

Tenga en cuenta que 'ffmpeg' no codificará desde' stdin' para video que no se puede transmitir como un archivo '* .mov' donde los metadatos están al final. La razón es que 'ffmpeg' no puede buscar los metadatos en la tubería. Tendría que 'qt-faststart' cada archivo de entrada antes de poder canalizarlo a' ffmpeg'. – Basti

5

Puede hacerlo con ffmpeg 's -progress argumento y 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

Esto parece que conduce a la mejor solución. Puede canalizar la información de progreso a través de 'ffmpeg -v warning -progress/dev/stdout -i in.mp4 out.mp4' a' stdout' para obtener actualizaciones de progreso una vez por segundo. Si solo necesita un porcentaje, 'ffprobe -v quiet -print_format json -show_format in.mp4' para la' duración' en segundos antes de iniciar la codificación y luego '| awk -F "=" '/ out_time_ms/{print $ 2}' 'para obtener solo la duración actual en ms donde' progress = total_duration * 100 * 1000/current_duration'. – Basti

+1

No sabía que podría enrutar el progreso a un archivo; si ese es el caso, podrías hacer un fifo con 'sed' y estar en camino. – iluvcapra

+0

También podría ser una buena idea crear un conducto con nombre y llamar a 'ffmpeg' y' sed | grep | awk | lo que sea 'separado' para obtener el código de retorno de 'ffmpeg'. Además envío el stdout de 'ffmpeg' a un archivo de registro para que en caso de un error (código> 0) tenga algo que ver. Mi comando wip actualmente se ve como 'ffmpeg -v warning -progress/dev/stdout -y -i $ source $ encodingOptions $ target >> $ logFile | grep --line-buffer out_time_ms'. – Basti

Cuestiones relacionadas