Thursday 9 April 2009

LAME + pipe + Vorbis


¿Qué es un pipe?

No tengo idea cómo definirlo. Pero puedo intentar explicarlo, más o menos...
Es algo así como un tubo (tubería, como su nombre indica) que permite la transferencia directa de streams, desde un proceso a otro. O sea, podemos agarrar un archivo, y pasarlo por 10 programas, hasta finalmente terminar con otro (convertido, comprimido, encriptado, codificado, lo que sea)... pero sin crear ninguno intermedio. Asa! Roughly speaking.

De una forma más normal... si nosotros somos procesos/programas, y las cosas son archivos, es algo así como pasarnos las cosas, sin apoyarlas en ningún lado. Para darme un billete, no es necesario que alguien lo apoye en la mesa, para yo poder agarrarlo: la persona me lo puede dar directamente!
Este es más o menos el concepto.

Razones para su uso? Velocidad. Espacio. La escritura y lectura en disco es varias veces más lenta que en memoria, y a veces los archivos pueden llegar a tamaños importantes... ¿no? Evitar la creación de monstruosidades temporarias es una solución elegante.

¿Por qué pensé en esto? Resulta que no me llevo demasiado bien con los MP3s. Es tecnología del pasado, tiene casi 20 años, y ya es hora de usar cosas mejores... ¿O acaso alguien sigue usando AVIs? Por favor... El problema es que tengo varios archivos en ese formato, y los quería convertir (si los paso a Vorbis, me puedo ahorrar un poco de espacio para la misma calidad, además de las otras ventajas de OGG.)

El chiste era algo así: bajar el AVR (Average BitRate = BitRate Promedio) en un 10-15% al convertirlos. ¿El problema? Eran muchos. Convertir archivo por archivo es una locura.
Hay otra manera: bash scripting. OK, en realidad, zsh scripting. (Acabo de cambiar de shell, por recomendación de... miles de personas. No tengo nada en contra de Bash, con él aprendí que los shells pueden ser mucho más flexibles y rápidos que los GUIs, pero realmente el zsh parece impresionante, NO podría NO probarlo.)

Necesidades: LAME (para decodificar los MP3s), vorbis-tools (para codificar a Vorbis) y algún shell copado (zsh/bash/csh/kornsh/etc.) que se banque un RENAME.

El comando debería:
1. Recorrer cíclicamente los archivos: ciclo for.
2. Decodificar mp3 al stdout (stream de salida, en lugar de un archivo.)
3. Pasar el stdout de LAME al stdin (stream de entrada) del OggEnc.
4. Codificar a un archivo Vorbis (direccionando el stdout del OggEnc.)

Comando:
for x in *.mp3; do lame --decode "$x" - | oggenc - > "${x}.ogg"; done
Listo. Una papa.

OK, explico un poco.
x es una variable (la declaración en bash es innecesaria), que agarrará el nombre de cada archivo que cumpla con *.mp3 (todos los mp3 en el directorio), secuencialmente (de a uno y no todos a la vez).
Al archivo cuyo nombre='valor de x', lo decodificamos a wav (lame --decode "$x"), pero sin escribirlo al disco, sino que lo mandamos al stdout (salida del shell, que marcamos con el '-').
El carácter del pipe '|' indica que enganchamos procesos, o sea que si bien le dijimos a LAME que mande el resultado al shell, no queremos que lo muestre en él (que resultaría en la impresión de caracteres raros), sino que otro proceso va a usar eso.
Y convertimos ese wav (que aún se está CREANDO en memoria) a un ogg vorbis (oggenc - > "${x}.ogg"). El '>' parece estar de más, pero hay que tener en cuenta que el oggenc, al recibir un archivo como stream de entrada (en lugar de agarrarlo de disco) sin nombre, lo larga como stream de salida. Eso nos sirve para enganchar OTRO proceso, pero en este caso queremos escribirlo al disco, por eso marcamos la salida a un tal 'valor de x'.ogg.

Esto resulta en una conversión de cada archivo.mp3 a un archivo.mp3.ogg dentro del directorio en el que corremos tal comando.

Pero esto se quedaba corto, porque yo quería mantener los bitrates como expliqué anteriormente, bajandolos un poco...
Con los shells todo se puede...

Pasos a seguir:
1. Creamos directorios cuyos nombres son el bitrate promedio que queremos en los archivos resultantes (vorbis).
2. Movemos los mp3 a los directorios correspondientes (si quiero que uno quede con bitrate-promedio de 192 lo muevo al directorio 192/, si lo quiero con 256 lo muevo a 256/, etc.)
3. Nos paramos afuera de los directorios con los bitrates deseados... Es decir que al tirar un 'ls' nos tiene que salir una lista de directorios tipo: 128/ 192/ 256/...
4. Corremos el siguiente comando:

for ABR in *; do cd "$ABR"; for FILE in *.mp3; do lame --decode "$FILE" - | oggenc -b "$ABR" - > "${FILE}.ogg"; rm "$FILE"; done; rename "mp3." "" *; cd ..; done

Listo.
Para explicarlo un poco, mejor lo pongo indentado (aunque creo que acá no sale indentado, pero al menos sale separado por líneas, sino es un bardo):

for ABR in *
do
cd "$ABR"
for FILE in *.mp3
do
lame --decode "$FILE" - | oggenc -b "$ABR" - > "${FILE}.ogg"
rm "$FILE"
done
rename "mp3." "" *
cd ..
done

Análisis línea por línea:
for ABR in *: tomamos el nombre de cada directorio acá y lo guardamos en la variable ABR
do: empezamos el ciclo marcado por el primer for (este corre UNA VEZ por CADA valor de ABR, es decir una vez por directorio)
cd "$ABR": entramos al directorio cuyo nombre=valor de ABR ("$x" significa 'valor de x')
for FILE in *.mp3: tomamos el nombre de cada archivo mp3 y lo guardamos en la variable FILE
do: empezamos el ciclo marcado por el segundo for (anidado), que corre una vez por archivo
lame --decode "$FILE" - | oggenc -b "$ABR" - > "${FILE}.ogg": pasamos de mp3 a un vorbis con el bitrate promedio (nominal) que indica la variable ABR (nombre del directorio=bitrate deseado), con nombre igual al archivo de la variable FILE más la extensión .ogg (ej: 'perrito caliente.mp3.ogg')
rm "$FILE": borramos el mp3 original (esto es opcional)
done: cerramos el segundo ciclo for
rename: ".mp3" "" *.ogg: le sacamos la extensión .mp3 a todos los vorbis (ahora: 'perrito caliente.ogg', también opcional)
cd ..: salimos del directorio corriente (volvemos al general, donde están los directorios-bitrates)
done: cerramos el primer ciclo for.

Y eso es todo amigos... los comandos los pueden copiar literalmente.
Sino los pueden copiar a un script (lame2ogg.sh), lo ponen como ejecutable (chmod a+x lame2ogg.sh) y lo corren desde el directorio general (./lame2ogg.sh).
Suerte!!!!!!!!!!

PS: Obviamente esto demuestra que usar pipes es más rápido que hacerlo en pasos (convertir a wav primero, después convertir los wavs guardados a vorbis) porque, debido a que hacemos una decodificación + codificación al vuelo, en hacer todo el proceso tardamos tiempo_total = decodificación_de_1er_mp3 + codificación_total_a_vorbis, mientras que hacerlo en pasos daría un tiempo_total = decodificación_total_de_mp3 + codificación_total_vorbis. Convirtiendo decenas de archivos son MUCHOS minutos de diferencia.

No comments: