sábado, julio 02, 2005

Renombrar canciones

o cómo se sigue aburriendo un informático...

Son las 5 de la mañana y acabo de culminar el proceso de automatización que empecé hace mucho tiempo para uniformar los nombres de los ficheros de mi colección de música.

Yo soy bastante quisquilloso con los nombres que tienen mis archivos, me gusta que todo esté ordenadito y consistente. Cuando ripeas tus discos para escucharlos en el ordenador, eres tú el que escoge el formato, pero cuando te bajas algún disco, normalmente cada uno tiene sus propias preferencias...

A mí personalmente me gusta tener una carpeta para cada grupo de música, y dentro una para cada disco, y dentro las canciones del siguiente modo: "'01 - Time To Relax.mp3". Lo más frecuente es que al descargar algo, el nombre del fichero sea algo así "the offspring-smash_1_time-to-relax_by-jaquer-juan.mp3" y uno tenga que ir canción por canción cambiando el nombre a mano.

Muy pronto me di cuenta de que había una forma mejor y más rápida (bueno, no siempre más rápida, pero al menos sí menos tediosa). Consistía en, estando en Güindous, abrir un intérprete de comandos, hacer un "dir /w", copiar el resultado en un editor de texto, abrir una hoja de cálculo en la que se ponía en la primera columna ren " en la segunda se pegaban los nombres de las canciones, en la tercera " ", en la cuarta se pegaba otra vez los nombres de las canciones y en la quinta se cerraban las comillas... aprovechando que la hoja de cálculo permite buscar y reemplazar sólo en una columna, se hacían los cambios pertinentes en la cuarta, después se copiaba todo al editor de texto, se eliminaban los tabuladores y se pegaba por fin en el intérprete de comandos, que obedientemente cambiaba los nombres de las canciones.

Eso era mucho pifostio para tan poca cosa. Tenía que haber una manera más sencilla. De hecho la había, y la encontré. Se trataba de emplear sabiamente la consola de Linux (o Cygwin en Güindous) y hacer unos scripts de AWK que facilitasen el trabajo.

El primer script, llamado "substs_delim.awk" se encargaba de reemplazar un texto por otro en el nombre de un fichero. Aquí está el código:

BEGIN{
 cmd = "mv"
}
{
 nombre=$1;
 for (i = 2; i<=NF;i++){
         nombre=nombre s $i;
 }
 print cmd" \""$0"\" \""nombre"\"";
}

La forma de ejecutarlo era la siguiente. Supongamos que te molesta el "ripped-by-juani-the-jaquer" que tienen los archivos... desde la carpeta donde están los emepetreses:
ls | awk -f substs_delim.awk -F "ripped-by-juani-the-jaquer" -v s="" | bash
Lo que hace es pasarle la lista de los archivos (ls) al programa de awk, que genera los comandos de cambiar los nombres de los archivos y finalmente pasárselo al intérprete de comandos para que los ejecute.

Esto es el pan de cada día de un usuario vago de Un*x. Pero requería cierto esfuerzo cuando había que realizar varios pasos para quitar o cambiar varias cosas y en ocasiones había que echar mano de otros scripts para insertar el " - " detrás del número de la canción...

Así que como no podía dormirme a las 3:30 de la mañana, arranqué el ordenador y me puse a hacer el script definitivo: "rename_titles.py"



#!/usr/bin/python
import sys

command = "mv"
c = "\""

def removeFirstEqual(lines):
        if len(lines) < 2:
                return []
        firstPart = lines[0].split()[0]
        for i in lines:
                if i.split()[0] != firstPart:
                        return []
        newLines=[]
        for i in lines:
                tmp = " ".join(i.split()[1:])
                newLines += [tmp]
        return newLines

def removeLastEqual(lines):
        if len(lines) < 2:
                return []
        lastPart = lines[0].split()[-1]
        for i in lines:
                if i.split()[-1] != lastPart:
                        return []
        newLines=[]
        for i in lines:
                tmp = " ".join(i.split()[:-1])
                newLines += [tmp]
        return newLines

def removeAllEquals(lines):
        res = removeFirstEqual(lines)
        while(res):
                lines = res
                res = removeFirstEqual(lines)
                res = removeLastEqual(lines)
        while(res):
                lines = res
                res = removeLastEqual(lines)
        return lines

def replaceAllRare(line):
        line = line.replace("_"," ")
        line = line.replace("."," ")
        line = line.replace("-"," ")
        line = " ".join(line.split())
        return line

class Song:
        def __init__(self, title):
                self.title = title
                self.findNumber()
                self.findExtension()
                self.title = " ".join(title.split()[1:-1])

        def findNumber(self):
                self.number = "0"
                for i in self.title.split():
                        if (i.isdigit()):
                                self.number = i
                                return

        def findExtension(self):
                self.extension = self.title.split()[-1]

        def toString(self):
                return self.number.zfill(2) + " - " + self.title + "." + self.extension
        def __repr__(self):
                return self.toString()

def printCommand(orig, dest):
        print command + " " + c + orig + c + " " + c + dest + c

lines = [i.strip() for i in sys.stdin]
result = [replaceAllRare(i) for i in lines]
songs = [Song(i) for i in result]
result = removeAllEquals(result)
result = [" ".join(i.split()[1:]) for i in result]
result = removeAllEquals(result)

for i,j in zip(songs, result):
        i.title = j.title()

for orig, dest in zip(lines, songs):
        printCommand(orig, str(dest))
 Esta pequeña maravilla de menos de 100 líneas de código en Python se encarga ella solita de, a partir de una lista de nombres, encontrar los cachos molestos, quitarlos y dejarlo todo como una patena.

De nuevo, requiere que le pasemos como entrada un "ls" y redirijamos la salida a un "bash", pero se recomienda echar un vistazo primero sin el "bash" para ver si el resultado es de nuestro agrado.

Espero que por lo menos le sirva a otra persona para limpiar su discografía, porque si no vaya hora y media más tirada... ¿dudas? ¿preguntas? ¿sugerencias?

3 comentarios:

  1. Anónimo3/7/05 12:44

    Yo tengo las mismas preferencias en cuanto a carpetas y nombres de ficheros, pero uso linux, y existe un programa muy comodo para estos menesteres, kid3 (que tiene binario descargable para windows).

    Revisas la informacion id3 de los fichero y a continuacion generas el nombre del fichero en base al id3, comodisimo.

    Si quieres solo renombrar ficheros usando expresiones regulares usa rename, ejemplo rename -s/" - ripped by juaker guay"// *.mp3 elimina la cadena de los mp3s, etctera. Como acepta expresiones regulares tambien puedes cambiar el orden de las cadenas y tal, bueno, te pego la salida de --help:


    ~ $ rename --help
    Usage: rename SOURCE DEST
    or: rename [OPTION] file ...
    Rename SOURCE to DEST, or substitute characters match the specified pattern
    in the filename.

    -l, --lowcase lowcase the file names
    -u, --upcase upcase the file names
    -s/PATTERN/STRING[/sw] replace matching PATTERN with STRING, [sw] is
    [g] replace all occurrences in the filename
    [i] ignore case when searching
    [b] backward searching and replacing
    [s] change file's suffix name
    [r] PATTERN is regular expression
    [e] PATTERN is extended regular expression
    -R, --recursive operate on files and directories recursively
    -o, --owner OWNER change file's owner (superuser only)
    -v, --verbose display verbose information
    -t, --test test only
    -h, --help display this help and exit
    -V, --version output version information and exit
    --yes --no force to choose YES or NO when target exists

    See man page regex(7) for detail information about extended regular expression.

    Report bugs to xuming@bigfoot.com.

    P.D. Siento chafarte la programacion, yo me pase dos horas haciendo una funcion de libreria en pascal en mis inicios de programacion.

    Jorge Nerin.

    ResponderEliminar
  2. De lo del kid3 no tenía ni idea... tiempo ha, conocía alguno similar para windows, pero tienen la desventaja de que no todo el mundo pone los tags id3 a los ficheros. Sin embargo sí que lo veo útil para poner los tags id3 a partir del nombre del fichero y la ruta una vez que ya se han formateado correctamente.

    Ya me había encontrado con el comando "rename" (al poner ren y darle al tabulador) y a uno se le queda cara un poco de tonto... eso sí, la ventaja que tiene mi programa es que no necesitas decirle qué es lo que quieres eliminar, él solo quita las partes comunes que hay en la lista de nombres, lo cual viene muy bien para el procesamiento por lotes... de hecho aquí tengo un script de BASH que lo ejecuta para todos los discos de un directorio (en un nivel de profundidad, la versión multinivel era un poco más compleja y la he dejado por ahora)


    #!/bin/bash
    for file in *
    do if test -d "$file"
    then
    echo "cd" "\"""$file""\""
    cd "$file"
    ls *.mp3 | rename_titles.py
    echo "cd .."
    cd ..
    fi
    done


    Lo interesante aquí es volcarlo a un fichero por si no estamos satisfechos con los resultados, pegarle un repaso y luego ejecutarlo con "bash nombre_fichero"

    Pero bueno, ya sabemos lo que pasa, al final tarda menos uno en hacérselo desde cero que en aprender a usar las cosas que ya existen (y así va el mundo)... yo he pasado un rato entretenido haciendo el programilla y le estoy dando bastante uso :-)

    ResponderEliminar