Organizando una colección de mp3 con ubuntu

Imagen de th3pr0ph3t
0 puntos

El problema:
Una colección de unos 2400 archivos mp3 con nombres mal puestos (001-artista-album-titulo sin tildes y con _, Unknown artist por doquier y mi favoritos: track01, track02...).
Quiero la colección organizada en carpetas por artista y los archivos al estilo Artista - Título.
Finalmente, corregir y agregar tags ID3 para que el reproductor los muestre bien.
Por supuesto no existe un programa adivino que lo arregle todo en un dos por tres, pero en ubuntu encontré las siguientes soluciones:

Solución 1:

Amarok

PROS: Es un muy buen reproductor que permite organizar la biblioteca del reproductor con facilidad. También renombra archivos, creo.
CONTRAS: Es pesado y tiene su propia base de datos, casi no toca los archivos mp3, así que si los llevas a otra parte, en mi caso, uso Rhythmbox así que me quedo con la solución 2.

Solución 2:

Usando solo Rhythmbox para reproducir música, no hay mucho que se pueda hacer con la biblioteca. Lo bueno es que Rhytmbox es rápido, responde a combinaciones de teclas para saltar canciones y la búsqueda de canciones es muy rápida.
Rhytmbox necesita archivos con tags ID3 bien puestos. Para producirla, habrá que usar otras herramientas.

El método que escogí es arreglar los nombres de archivo y escribir los tags ID3 a partir del nombre.

pyRenamer

Con este programa se puede renombrar archivos en masa, hace Casi* de todo.
Para completar el "casi", uso el explorador o gedit (ver más abajo).

Ex Falso

Ex Falso es el mejor editor de tags ID3 de los 3 que probé (Kid3 e EasyTAG fueron los otros dos).
Con Ex Falso simplemente voy a la carpeta donde están los archivos y usando la pestaña "Etiquetas de Nombre" corrijo los tags con dos clicks.

Otras soluciones

Existen otras maneras de hacerlo y quizá otros las pongan aquí. Así que sigue leyendo...

Imagen de th3pr0ph3t
+1
0
-1

Para completar el post, pongo aquí un par de scripts de python antes de que se me pierdan.

Lo que hace esto es agregar un menú contextual en el explorador (nautilus) que genera una lista de archivos en un archivo de texto que puedo editar con gedit o lo que más me sirva. Así puedo agregar una tilde que falta o algo que no se pueda hacer rápido desde el explorador ni desde pyRenamer, como por ejemplo, encontrar palabras mal escritas.

El script funcionará de la siguiente manera:
- Desde el explorador, selecciono los archivos que quiero renombrar.
- Clic derecho, Scripts, Renombrar archivos
- Se abre gedit con un archivo de texto y hago los cambios que quiera (menos eliminar lineas ni ordenar).
- Guardo el archivo, cierro gedit.
- En el explorador: clic derecho, Scripts, Renombrar archivos y listo.

Nautilus

No sé hacer extensiones de nautilus pero ya existe una manera más sencilla de hacerlo: nautilus-script-manager.
Con esto se agrega un menú contextual "scripts" donde puedes agregar scripts que hagan lo que quieras.
Instala el paquete nautilus-script-manager si es que no lo tienes instalado ya.
Después de Reiniciar deberías tener el menú contextual scripts con la opción Abrir la carpeta de scripts. Sino, aprovechemos para instalar un conjunto de scripts, por si acaso.

Haz esto es sólo si no está la opción "Scripts\Abrir la carpeta de scripts". Esto instalará los scripts para cambiar de formato a un archivo (por ejemplo, de mp3 a ogg). No tiene nada que ver pero al menos estaremos seguros de que tienes la extensión nautilus-script-manager funcionando como se debe.
desde un terminal:

sudo apt-get install nautilus-script-audio-convert
nautilus-script-manager enable ConvertAudioFile

Y reinicia.

Con esto podemos abrir la carpeta de scripts y agregar nuestro script para renombrar archivos.

El script para renombrar archivos es el siguiente:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

"""
Renombra archivos masivamente usando dos archivos de texto con los nombres
origen y destino.

Uso:
    $THISFILE [--help] [--nogedit] [--re {regexp}|{archivo}[{archivo}...]]

Parámetros:
    -n, --nogedit 
        No lanzar gedit después de generar la lista.
    -r o --re seguido de una expresión regular
        Generará la lista de archivos cuyo nombre coincida. 
        Nótese que es una expresión regular y no un filtro con comodines.
              Ej: "-re .mp3$" listará sólo los archivos que terminan en ".mp3"
    {archivo} es un nombre de archivo. Esta forma permite usar la extensión
        nautilus-script-manager para hacer clic derecho a un grupo de archivos
        y generar la lista con ellos. Se puede especificar tantos archivos como
        sea necesario. Después del primer archivo, no se aceptan más opciones.
    Ejecutar este script sin archivos ni expresión regular generará una lista 
    con todos los archivos del directorio actual.

Modo de uso:
 1. Ejecutar el programa. Se generarán dos archivos: $ORI y $DES.
 2. Abrir $DES con un editor de texto y reemplazar los nombres.
    No eliminar líneas!
 3. Ejecutar el programa una vez más. Se renombrarán los archivos en 
    $ORI por el nombre que tengan en $DES.
El programa eliminará $ORI y $DES al terminar.

Notas: 

- Si existe $ORI pero no $DES, se crea una copia de $ORI a $DES
- Si existen ambos archivos, se procede a renombrar, 
  sin importar los parámetros.
- Tras la primera ejecución $ORI y $DES son iguales al contenido 
  generado por los siguientes comandos (linux):
    ls -1 > $ORI
    cp $ORI $DES
  (windows):
    dir /b > $ORI
    copy $ORI $DES
  Usa éstos comandos si deseas una lista filtrada.
- Después de renombrarse, los archivos $ORI, $DES, 
  $ORI~y $DES~ son eliminados.
"""

import sys, os, re, subprocess, traceback
import distutils.file_util


#constantes para legibilidad

FILE_NAME_SRC = "ren-ori.txt"
FILE_NAME_DES = "ren-des.txt"

MSG_ARGS_FILE_LIST = "Se recibió una lista de archivos por parámetro."
MSG_FILE_LISTS_GENERATING = "Generando lista de archivos... "
MSG_FILE_LISTS_GENERATED = """\
Los archivos $ORI y $DES han sido generados con exito. 
Para más información, ejecuta este programa con el parámetro -h"""
MSG_FILE_LISTS_COPY_DES = """\
Faltaba el archivo $DES. Se ha creado una copia de $ORI ."""
MSG_RENAMED = "$REN de $TOT archivos renombrados."

ERR_ONE_FILE_LONGER = "Error: $ORI y $DES no tienen el mismo número de líneas."
ERR_READING_FILE = "Error leyendo archivo $FNAME :"
ERR_RENAME = "Error al renombrar (de,a)\n\t$ORI\n\t$DES"


def read_file(fname) :
    """Lee las líneas de un archivo de texto."""
    f = open(fname, "r")
    result = list()
    try :
        tmp = f.readlines()
        #readlines carga con el salto de linea, quitarlo
        for line in tmp :
            result.append(line.strip())
        return result
    except :
        print ERR_READING_FILE.replace("$FNAME", fname)
    finally :
        f.close()


def generate_file_list(fnsrc, fndes, regexp) :
    """Genera la lista de archivos a un archivo de texto con nombre fnsrc
    y una copia a fndes. regexp puede ser None para incluir todos los archivos.
    """
    print MSG_FILE_LISTS_GENERATING,
    lst = os.listdir(".")
    if not regexp :
        lst.sort()
    else :
        lst2 = list()
        for fname in lst :
            if regexp.search(fname) :
                lst2.append(fname)
        lst2.sort()
        lst = lst2
    distutils.file_util.write_file(fnsrc, lst)
    distutils.file_util.write_file(fndes, lst)
    print "ok"
    print MSG_FILE_LISTS_GENERATED.replace("$ORI",fnsrc).replace("$DES",fndes)

def generate_file_list_from_params(fnsrc, fndes, file_names) :
    """Escribir la lista de archivos tomada de los parámetros, como cuando se
    usa nautilus-script-manager y se ha seleccionado un grupo de archivos."""
    file_names.sort()
    distutils.file_util.write_file(fnsrc, file_names)
    distutils.file_util.write_file(fndes, file_names)
    print MSG_ARGS_FILE_LIST


def rename_files(fnlstori, fnlstdes) :
    """Renombrar los archivos en el archivo de texto con nombre fnlstori a los
    nombres que correspondan en el archivo fnlstdes"""
    #verificar que los archivos tengan la misma cantidad de líneas
    oris = read_file(fnlstori)
    dess = read_file(fnlstdes)
    if len(oris) != len(dess) :
        print ERR_ONE_FILE_LONGER
        return
    total = len(oris)
    renamed = 0
    #renombrar archivos
    for i in range(0, total) :
        try :
            ori = oris[i].strip()
            des = dess[i].strip()
            if (ori!=des) :
                os.rename(oris[i], dess[i])
                renamed += 1
        except :
            print ERR_RENAME.replace("$ORI", oris[i]).replace("$DES", dess[i])
    print MSG_RENAMED.replace("$REN", str(renamed)).replace("$TOT", str(total))



def main() :
    """Punto de entrada del programa. Verifica que exista ren-ori.txt y decide
    si generar la lista o proceder a renombrar."""

    #atender al parámetro -h
    regexp = None
    gedit = True
    args = sys.argv[1:]
    for args_index in range(0, len(args)) :
        arg = args[args_index]
        if arg in ["-h", "--help", "/?"] :
            print __doc__.replace("$ORI", FILE_NAME_SRC)\
                    .replace("$DES", FILE_NAME_DES)\
                    .replace("$THISFILE", os.path.basename(sys.argv[0]))
            return
        elif arg in ["-n","--nogedit"] :
            gedit = False
        elif sys.argv[1] in ["-r", "--re"] :
            regexp = re.compile(args[args_index+1])
            args = args[ (args_index+2) : ]
            break
    
    if os.path.exists(FILE_NAME_SRC) :
        if os.path.exists(FILE_NAME_DES) :
            #los dos archivos existen, procesarlos
            rename_files(FILE_NAME_SRC, FILE_NAME_DES)
            os.remove(FILE_NAME_SRC)
            os.remove(FILE_NAME_DES)
            try : #borrar copias de seguridad (linux)
                os.remove(FILE_NAME_SRC+"~")
            except :
                pass
            try :
                os.remove(FILE_NAME_DES+"~")
            except :
                pass
        else :
            #no existe la lista de archivos de destino, hacer una copia
            distutils.file_util.copy_file(FILE_NAME_SRC, FILE_NAME_DES)
            print MSG_FILE_LISTS_COPY_DES\
                    .replace("$ORI",FILE_NAME_SRC)\
                    .replace("$DES",FILE_NAME_DES)
    else :
        #no hay lista de origen, generar la lista de archivos
        if regexp or len(args) == 0 :
            #parámetro -r o sin parámetros
            generate_file_list(FILE_NAME_SRC, FILE_NAME_DES, regexp)
        else :
            #parametros es una lista de archivos
            generate_file_list_from_params(FILE_NAME_SRC, FILE_NAME_DES, args)
        #lanzar gedit?
        if gedit :
            subprocess.Popen( ("gedit", FILE_NAME_DES) )



if __name__ == "__main__" :
    main()

Digamos que guardo este archivo en ~/scripts/renombrar.py
Sería una buena idea darle permiso de ejecución haciéndole clic derecho, propiedades, y en la pestaña Permisos, "Permitir ejecutar el archivo como un programa" (desde la consola: chmod +x ~/scripts/renombrar.py)

Para agregarlo al menú de scripts, desde el explorador:
Clic derecho, Crear un enlace. Se crea un archivo "Enlace hacia renombrar.py", cortar este archivo para pegarlo en la carpeta de scripts.
(desde la consola, se hace con ln pero no me acuerdo los parámetros)
Una vez más, en el explorador: Clic derecho, Scripts, "Abrir la carpeta de scripts"
Se abre una carpeta donde se puede pegar el archivo. También podemos ponerle un nombre más amigable como "Renombrar archivos".

La gracia de esto es que podemos agregar más scripts para hacer lo que se nos antoje.

También se puede agregar scripts en gedit. Con eso agrego un post más a este tema...

+1
0
-1

100% Ubuntu! :D

Imagen de th3pr0ph3t
+1
0
-1

Corrección ortográfica

Es necesario instalar el paquete aspell-es para tener un diccionario en español. Desde la consola: sudo apt-get install aspell-es
aspell-en también podría ser útil.
Desde gedit, ir a Editar/Preferencias y activar el complemento Corrector ortográfico. Cambiar capitalización también puede sernos útil.
No olvidar escoger idioma (sin el paquete aspell-es no habrá diccionario).

Ajustes por script

Este script puede usarse desde gedit para arreglar las mayúsculas y quitar espacios sobrantes.

Para usar scripts, activar el complemento Herramientas Externas:
Editar/Preferencias/Complementos/Herramientas Externas

Agregar una herramienta externa:
Herramientas/Herramientas externas...

Crear una nueva herramienta externa a la que llamaremos "Transformaciones". El comando supone que el script lo hemos guardado en /home/yo/scripts/transf.py:
Comando: python /home/yo/scripts/transf.py
Entrada: Selección actual
Salida: Reemplazar selección actual
Esto hará que el texto seleccionado (o todo el texto si no hay selección) sea entrada para el script desde stdin, y la salida que el script ponga en stdout será el texto "transformado".

El script usará zenity (un programa que presenta cuadros de diálogo) para presentar una ventana con una lista de transformaciones disponibles (la de normalizar nombres de mp3 será una).
Este script también hace un par de cosas más pero igual lo copio con todo:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

"""\
Script de transformaciones de texto para usarse como complemento en gedit.
Uso:
    $THISFILE [-zen|-gedit|{transformacion} [archivo]]
Ejemplo:
    $THISFILE xu prueba.txt

Parametros:
    transformacion
        Indica qué transformación se aplicará a los datos.
    archivo
        Nombre del archivo a transformar. Si se especifica, abre el archivo, 
        aplica los cambios y guarda al mismo archivo (sin backup).
        Si no se incluye este parametro, se usará STDIN y la salida se mostrará
        en STDOUT.
    -zenity Usa zenity para preguntar qué transformación usar. Sólo linux.
    -h  Muestra esta ayuda.
    -gedit Muestra el cómo configurar gedit para usar este script.
    -list Muestra una lista de transformaciones fácil de leer.

Nota: Este script carga todo el documento y luego aplica las transformaciones;
para archivos muy largos use el comando sed, que es más complicado y potente.\
"""

import os, sys, re, subprocess


HELP_GEDIT = """\
Para usar este script como un extensión en gedit:
- Activa la extensión Herramientas Externas.
- Crea una herramienta externa llamada Transformaciones.
- Asigna como Entrada la Selección actual
- Asigna como Salida Reemplazar la selección actual.
- Asigne como comando lo siguiente:
python $THISFILE -zenity
"""


#transformaciones: para agregar una transformación, agrega una tupla a TRANS
#con el comando y la función. La funcion debe tener documentación.
TRANS = list()


def xml_escape(s) :
    "Escape XML: Reemplaza < > y \" por entities."
    s = s.replace("&","&")
    s = s.replace("\"",""")
    s = s.replace("<","<")
    s = s.replace(">",">")
    return s
TRANS.append( ("xmle", xml_escape) )

    
def xml_unescape(s) :
    "Desescape XML: reemplazar entities por caracteres."
    s = s.replace(">",">",)
    s = s.replace("<","<")
    s = s.replace(""","\"")
    s = s.replace("&","&")
    return s
TRANS.append( ("xmlu", xml_unescape) )


def mp3caps(s) :
    "Normalizar el nombre de un archivo mp3 en formato %t - %a.ext"
    results = list()
    file_names = s.splitlines()
    rs = re.compile("[ ]+")
    for file_name in file_names :
        try :
            #solo un espacio entre palabras
            file_name = " ".join(rs.split(file_name))
            name, ext = os.path.splitext(file_name)
            #extension siempre en minusculas
            ext = ext.lower()
            #" - " separa artista de titulo, solo se espera dos partes
            artist, title = name.split(" - ")
            #titulo con la primera letra en mayúsculas solamente
            title = title.strip().capitalize()
            #artista conserva su capitalización, pero por lo menos la primera
            #sera mayúscula.
            artist = artist.strip()
            artist = artist[:1].upper() + artist[1:]
            file_name = artist + " - " + title + ext
        except :
            file_name = "****" + file_name
        finally :
            results.append(file_name)
    return "\n".join(results)
TRANS.append( ("mp3", mp3caps) )

#------------------------------------------------------------------------------


def get_zenity_args() :
    """Genera args para zenity: el primer argumento es zenity, los demás son
    los parámetros necesarios para mostrar un dialogo que pregunte qué 
    transformación realizar sobre el texto.
    Esto puede usarse en subprocess.Popen"""
    args = list()
    args.append("zenity")
    args.append("--list")
    args.append("--width=500")
    args.append("--height=300")
    args.append("--title=\"Transformar Selección\"")
    args.append("--column=Param")
    args.append("--column=Descripción")
    for item in TRANS :
        args.append(item[0])
        args.append(item[1].__doc__)
    return args


def do_zenity() :
    """Ejecuta zenity y devuelve la selección del usuario."""
    sp = subprocess.Popen(get_zenity_args(), stdout=subprocess.PIPE)
    if sp.wait()==0 :
        return sp.stdout.readline()
    
def do_gedit() :
    """Genera instrucciones para configurar gedit."""
    p = os.path.abspath(sys.argv[0])
    print HELP_GEDIT.replace("$THISFILE", p)
    

def do_list() :
    """Imprime una lista de transformaciones."""
    print "Transformaciones disponibles:\n"
    for param, func in TRANS :
        print param, ":"
        print func.__doc__
        print


def get_text(file_name) :
    """Obtiene el texto a modificar, si no hay file_name, se toma stdin."""
    if not file_name :
        return sys.stdin.read()
    else :
        f = open(file_name, "r")
        try :
            result = f.read()
        except :
            result = "?file:" + file_name + "?"
        finally :
            f.close()
        return result

def set_text(file_name, text) :
    """Devuelve o guarda el texto."""
    if not file_name :
        sys.stdout.write(text)
    else :
        f = open(file_name, "w")
        try :
            f.write(text)
        finally :
            f.close()


def main() : 
    "Punto de entrada del programa."
    param = ""
    file_name = None
    if len(sys.argv) == 1 :
        param = "-h"
    elif len(sys.argv) > 1 :
        param = sys.argv[1]
    if len(sys.argv) > 2 :
        file_name = sys.argv[2]
    
    #procesar parámetros -*
    if param.startswith("-") :
        if param == "-gedit" :
            do_gedit()
            return
        elif param == "-zenity" :
            param = do_zenity().strip()
        elif param == "-list" :
            do_list()
            return
        else: #-h, cualquier otro
            print __doc__.replace("$THISFILE", sys.argv[0])
            return
        
    text = get_text(file_name)

    #buscar y ejecutar el comando
    for item in TRANS :
        if item[0] == param :
            #llamar a la funcion guardada en el array
            text = item[1](text)
            break
    set_text(file_name, text)
    


#ejecutar si este es el programa principal (sino será simplemente una librería)
if __name__ == "__main__" :
    main()

Con esto puedes agregar tus propias transformaciones según sea necesario.

+1
0
-1

100% Ubuntu! :D

Imagen de thebabyxtremeboy
+1
0
-1

Interesante , lo probare ya que tengo mis mp3 desordenados xD!!!!
------------------------------------------------------------------------------------------------------------------------
El software libre es de todos y para todos.... :D

+1
0
-1

------------------------------------------------------------------------------------------------------------------------
El software libre es de todos y para todos.... :D