Ver Mensaje Individual
  #11 (permalink)  
Antiguo 18/09/2011, 03:37
Avatar de Triby
Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 15 años, 8 meses
Puntos: 2237
Respuesta: [Aporte] Convertir videos con ffmpeg en background

1. Para poder probar tanto en local como en produccion, guardo en variables las rutas de los programas y archivos necesarios.

Código PHP:
Ver original
  1. if($_SERVER['SERVER_NAME'] == 'localhost') {
  2.     $ffmpeg = 'c:/wamp/bin/mplayer/ffmpeg.exe';
  3.     $flvtool = 'c:/wamp/bin/mplayer/flvtool2.exe';
  4.     $srcdir = '/servidor/pruebas/tests/original';
  5.     $cnvdir = '/servidor/pruebas/tests/converted';
  6.  
  7.     // Tratando de mostrar info conforme avanza el proceso
  8.     // Esto generalmente me causa errores en Ubuntu
  9.     // Por eso lo tengo solo para el servidor local
  10.     @apache_setenv('no-gzip', 1);
  11.     @ini_set('zlib.output_compression', 0);
  12.     @ini_set('implicit_flush', 1);
  13.     for ($i = 0; $i < ob_get_level(); $i++) { ob_end_flush(); }
  14. } else {
  15.     // En ubuntu no necesito la ruta absoluta para los programas
  16.     $ffmpeg = 'ffmpeg';
  17.     $flvtool = 'flvtool2';
  18.     $srcdir = '/ruta_del_sitio/htdocs/tests/original';
  19.     $cnvdir = '/ruta_del_sitio/htdocs/tests/converted';
  20. }
  21. // Por si vamos a agregar marca de agua (probado solo en Windows)
  22. $watermark = "$srcdir/watermark2.png";
  23.  
  24. // Directorio para generar imagenes de vista previa
  25. $preview_dir = "$cnvdir/preview";
  26.  
  27. // Sample rates soportados por FLV
  28. $samples = array(44100, 22050, 11025);

Bien, ya teniendo lista la configuracion, leemos los archivos, ya sea de base de datos o, en este caso, del directorio de pruebas:

Código PHP:
Ver original
  1. // Leer archivos en directorio original
  2. // No use glob porque esto ya lo tenia
  3. $files = array();
  4. $sdir = opendir($srcdir);
  5. while($file = readdir($sdir)) {
  6.     if($file != '.' && $file != '..' && end(explode('.', $file)) != 'png') {
  7.         $files[] = $file;
  8.     }
  9. }
  10.  
  11. echo 'Preparando para convertir: ' . count($files) . " videos\n------------\n";

Ok, iniciamos con lo bueno, solo pido de favor no criticar el enredo de codigo, porque iba parchando un script muy viejo sobre la marcha y... siendo solo pruebas... pues hay que hacerlo rapido.

Nota, para poder ver los avances, despues de cada echo hay que agregar flush();

Código PHP:
Ver original
  1. foreach($files as $file) {
  2.     $newext = '.flv';
  3.     $preext = '.jpg';
  4.     $parts = explode('.', $file);
  5.     $curext = '.' . end($parts);
  6.  
  7.     // Las dos lineas siguientes son solo por "compatibilidad" con el codigo de produccion
  8.     unset($parts[count($parts) - 1]);
  9.     $curvid = implode('.', $parts);
  10.  
  11.     $file_orig = "$srcdir/$curvid$curext";
  12.     $file_final = "$cnvdir/$curvid$newext";
  13.     $file_pre = "$preview_dir/$curvid$preext"; // Vista previa
  14.     $file_th = "$preview_dir/{$curvid}.th$preext"; // Agregue esto para crear miniaturas
  15.  
  16.     echo "Preparando para convertir $file_orig to $file_final\n";
  17.  
  18.     //  Obtener información del video... recordando que php_ffmpeg me dio problemas
  19.     // Simplemente hacemos que ffmpeg nos de algunos datos
  20.     // para pasarlos por preg_match
  21.     exec("$ffmpeg -i $file_orig 2>&1", $output);
  22.     $output = implode("\n", $output);
  23.     $duration = $frameRate = $audioRate = $audioBitr = 0;
  24.     if(preg_match('/Duration: (.*?),/', $output, $matches)) {
  25.         $duration = $matches[1];
  26.         $duration_array = explode(':', $duration);
  27.         $duration = ($duration_array[0] * 3600) + ($duration_array[1] * 60) + $duration_array[2];
  28.     }
  29.     if (preg_match('/([0-9\.]+)\stbr/', $output, $matches)) {
  30.         $frameRate = $matches[1];
  31.     }
  32.     if(preg_match('/bitrate: ([0-9]+) kb\/s/', $output, $matches)) {
  33.         $audioBitr = $matches[1];
  34.     }
  35.     if(preg_match('/Audio:.+?([0-9]+) Hz/', $output, $matches)) {
  36.         $audioRate = $matches[1];
  37.     }
  38.  
  39.     if($duration == 0) {
  40.         // No obtuvimos duracion, el resto de datos podemos "calcularlos", pero este no
  41.         echo "----------------\nFormato de video no reconocido: $file_orig\nDuracion: $duration\nFrame Rate: $frameRate\nAudio Sample: $audioRate\nAudio Bit: $audioBitr\n\n";
  42.         continue;
  43.     }
  44.  
  45.     // Verificamos que todo sea soportado en FLV
  46.     $oldrate = '';
  47.     if(!in_array($audioRate, $samples)) {
  48.         $oldrate = "/$audioRate";
  49.         $audioRate = 22050;
  50.     }
  51.     if(!isset($audioBitr) || !$audioBitr || $audioBitr < 1) {
  52.         $audioBitr = $audioRate;
  53.     }
  54.     echo "---- Duración: $duration segundos\n---- Frame Rate: $frameRate fps";
  55.     echo "---- Audio Bit Rate: $audioBitr\n---- Audio Sample Rate: $audioRate$oldrate";
  56.  
  57.     // Ojo, esto tuve que hacerlo para probar, solo me funciono en Windows
  58.     // Hace mas complicado el codigo, pero necesitaba poder omitirlo para Linux
  59.     $newWater = "-vf \"movie=$watermark [img]; [in] [img] overlay=0:0:1 [movie]; [movie] setdar=3:2\"";
  60.  
  61.     // Los WMV son mas latosos, asi que por separado y conservando la misma calidad (?) ( -sameq )
  62.     if(strtolower($curext) == '.wmv') {
  63.         $ffmpeg_cmd = "$ffmpeg -i $file_orig -sameq -acodec libmp3lame -ar 22050 -ab 96000 -deinterlace -nr 500 -s 450x300 -aspect 4:3 -r 20 -g 500 -me_range 20 -b 270k -f flv -y $newWater $file_final";
  64.     } else {
  65.         // El resto de formatos con una calidad entre 65 y 70% ( -qscale 10 )
  66.         $ffmpeg_cmd = "$ffmpeg -i $file_orig -qscale 10 -acodec libmp3lame -ar  $audioRate -ab $audioBitr -f flv -s 450x300 $newWater $file_final";
  67.     }
  68.  
  69.     echo "-- Comando para convertir: $ffmpeg_cmd\n";
  70.     exec("$ffmpeg_cmd 2>&1", $output);
  71.  
  72.     // Verificamos que se haya generado el video
  73.     if(!file_exists($file_final)) {
  74.         echo "\n\n------ $fecha CONVERSION FALLO, archivo no creado\nOriginal: [$file_orig]\nDestino: [$file_final]\n" . implode("\n", $output) . "\n";
  75.         continue;
  76.     }
  77.     // Se genero el archivo, pero sin informacion?
  78.     if(file_exists($file_final) && filesize($file_final) < 1) {
  79.         unlink($file_final);
  80.         echo "\n\n------ $fecha CONVERSION FALLO, archivo creado con cero bytes\nOriginal: [$file_orig]\nDestino: [$file_final]\n" . implode("\n", $output) . "\n";
  81.         continue;
  82.     }
  83.  
  84.     // Parece que todo va bien... inyectamos meta datos FLV
  85.     $ffmpeg_cmd = "$flvtool -U $file_final";
  86.     echo "-- FLVtool injection: $ffmpeg_cmd\n";
  87.     exec("$ffmpeg_cmd 2>&1", $output);
  88.  
  89.     // Preparamos para crear miniatura e imagen de vista previa
  90.     // Para el primer metodo, con itoffset, se requiere la posicion en numero entero
  91.     $itoffset = ceil($duration / 2);
  92.     // Para el segundo metodo, lo necesitamos en formato hh:mm:ss
  93.     $thumb_position = sec2hms(ceil($duration / 2));
  94.  
  95.     // Primero la miniatura
  96.     $ffmpeg_cmd2 = "$ffmpeg  -itsoffset -$itoffset  -i $file_final -vcodec mjpeg -vframes 1 -an -f rawvideo -s 100x68 $file_th";
  97.     echo "-- Crear thumbnail (1): $ffmpeg_cmd2\n";
  98.     exec("$ffmpeg_cmd2 2>&1", $output);
  99.     if(file_exists($file_th) && filesize($file_th) < 1) {
  100.         // Si no se genero correctamente, lo eliminamos
  101.         unlink($file_th);
  102.     }
  103.     if(!file_exists($file_th)) {
  104.         // No se genero, probamos el segundo metodo
  105.         // Aclaro que esto lo tengo desde la primera version del aporte
  106.         // Y no ha llegado el caso de probarlo...
  107.         // Tal vez lo haga manualmente cuando tenga tiempo
  108.         $ffmpeg_cmd2 = "ffmpeg -i $file_final -ss $thumb_position -t 00:00:01 -s 100x68 -r 1 -f image2 $file_th";
  109.         echo "-- Crear thumbnail (2): $ffmpeg_cmd2\n";
  110.         exec("$ffmpeg_cmd2 2>&1", $output);
  111.     }
  112.     // Es exactamente el mismo proceso para la imagen de vista previa
  113.     // Solo cambiamos el tamaño para que sea igual que el video: -s 450x300
  114.     // Y reemplazamos la variable $file_th por $file_pre
  115.  
  116.     echo "$curvid fue convertido correctamente!!!\n\n";
  117. }
  118. echo "Proceso finalizado.\n";

Notas:
- La funcion sec2hms() es la misma que aparece en el primer mensaje, asi que no tiene caso repetirla.
- En Windows todo trabaja como debe ser, probando videos 3gp, mov, m4v, mp4, wmv, avi y flv.
- Si quieren cambiar la calidad del video generado, el parametro -qscale puede tener valores entre 1 (maxima calidad) y 31 (lo mas bajo)... dejandolo en 10 creo que tiene una excelente relacion tamaño de archivo/calidad.
- Lo de la marca de agua no pude hacerlo funcionar en linux porque mi version ya no tiene soporte (Ubuntu 6.06 LTS) y el unico codec/filtro que no pude instalar es el requerido para esto (avfilter-lavf)... si alguien puede instalar todo sin problemas le agradeceria cualquier comentario sobre el tema, para irme preparando... pronto cambiaremos el servidor.

Como decia un gran amigo mio: Capri, c'est fini
__________________
- León, Guanajuato
- GV-Foto