Automatizando la conversión de una biblioteca musical con Bash y ffmpeg

Written by

in

Para aquellos melómanos / audiófilos que tienen amplias colecciones por clasificar y/o reducir una biblioteca musical completa de 320kbps o cualquier tasa de bits a 128kbps para ahorrar espacio en disco, sobretodo este año donde el precio del almacenamiento está en los cielos, tareas de limpiar los nombres de archivo, y hacerlo sobre un share de red (SMB), procesando cientos de álbumes automáticamente sin intervención manual álbum por álbum. Este es el proceso completo, incluyendo los problemas reales que fueron apareciendo y cómo se resolvieron.

Objetivo

Convertir MP3 en 320kbps a 128kbps, eliminando además el prefijo numérico típico de pista en el nombre de archivo (01. Artista - Canción.mp3 → Artista - Canción.mp3).

Comando base con ffmpeg

for f in *.mp3; do
  ffmpeg -i "$f" -codec:a libmp3lame -b:a 128k "converted/$f"
done

-codec:a libmp3lame especifica el códec de audio (el encoder MP3 estándar en ffmpeg), y -b:a 128k fija el bitrate de audio objetivo. El resultado se guarda en una subcarpeta converted/ para no sobrescribir los originales durante las pruebas.

Eliminando el prefijo numérico con regex

for f in *.mp3; do
  if [[ "$f" =~ ^[0-9]+\.[[:space:]]*(.+)$ ]]; then
    newname="${BASH_REMATCH[1]}"
  else
    newname="$f"
  fi
  ffmpeg -i "$f" -codec:a libmp3lame -b:a 128k "converted/$newname"
done

El regex ^[0-9]+\.[[:space:]]*(.+)$ se lee así: uno o más dígitos al inicio (^[0-9]+), seguido de un punto literal (\.), seguido de cero o más espacios ([[:space:]]*), y captura todo el resto del nombre en un grupo ((.+)$). Funciona igual para prefijos de una, dos o tres cifras — 1., 01., 078. — sin necesitar reglas distintas para cada longitud. ${BASH_REMATCH[1]} es la variable de Bash que guarda el contenido del primer grupo capturado por el regex.

Dry run: verificar antes de convertir

Antes de correr la conversión real (que toma tiempo y no es instantánea de revertir), conviene simular el renombrado con un echo para confirmar visualmente que el patrón detecta los nombres correctamente:

for f in *.mp3; do
  if [[ "$f" =~ ^[0-9]+\.[[:space:]]*(.+)$ ]]; then
    echo "'$f' -> '${BASH_REMATCH[1]}'"
  fi
done

Script final: procesar toda la biblioteca de forma recursiva

Con el patrón ya validado, el script completo recorre todas las subcarpetas del directorio base, maneja álbumes con un nivel extra de subdirectorio (algunos venían con una carpeta duplicada dentro), y evita reprocesar carpetas ya convertidas:

#!/bin/bash
BASE="/mnt/hd1/biblio/musica_por_clasificar"

for dir in "$BASE"/*/; do
  dir="${dir%/}"  # quitar slash final
  dirname=$(basename "$dir")

  # Carpetas ya procesadas o a saltar
  [[ "$dirname" == "converted" ]] && continue
  [[ "$dirname" == *"Nombre carpeta ya procesada 1"* ]] && continue
  [[ "$dirname" == *"Nombre carpeta ya procesada 2"* ]] && continue

  echo "=== Procesando: $dirname ==="
  mkdir -p "$dir/converted"

  for f in "$dir"/*.mp3 "$dir"/*/*.mp3; do
    [[ -f "$f" ]] || continue

    # Saltar archivos que ya estén dentro de converted/
    [[ "$f" == */converted/* ]] && continue

    fname=$(basename "$f")
    if [[ "$fname" =~ ^[0-9]+\.[[:space:]]*(.+)$ ]]; then
      newname="${BASH_REMATCH[1]}"
    else
      newname="$fname"
    fi
    echo "  -> $newname"
    ffmpeg -i "$f" -codec:a libmp3lame -b:a 128k "$dir/converted/$newname" -y -loglevel error
  done
done

echo "=== LISTO ==="

Problemas encontrados y sus soluciones

ProblemaCausaSolución
El script no hacía nadaMount de gvfs lento o colgadoMontar el share con mount.cifs en vez de gvfs
«Permiso denegado» al crear converted/Mount CIFS sin permisos de escrituraRemontar especificando uid, gid, dir_mode
Doble slash //converted/$dir del glob */ ya termina en /dir="${dir%/}" para quitar el slash final antes de concatenar
ffmpeg reportaba «same as Input #0»El glob */*.mp3 volvía a capturar archivos ya dentro de converted/Agregar la condición [[ "$f" == */converted/* ]] && continue
Archivo corrupto («No stream»)Un MP3 inválido dentro de la colecciónffmpeg reporta el error y continúa — no bloquea el resto del loop
Algunos álbumes quedaban sin convertirLos MP3 estaban dentro de una subcarpeta extra anidadaAgregar el glob adicional "$dir"/*/*.mp3 al loop

Montar un share SMB correctamente para scripting

Un detalle clave que causó varios de los problemas anteriores: gvfs (el montaje automático que hacen los gestores de archivos gráficos, visible en rutas como /run/user/1000/gvfs/smb-share:...) es inestable para scripts en lote — se cuelga o responde lento bajo cargas de I/O sostenidas. La alternativa confiable es montar el share manualmente con mount.cifs:

# Crear el punto de montaje
sudo mkdir -p /mnt/hd1

# Montar con usuario y contraseña
sudo mount -t cifs //192.168.100.50/hd1 /mnt/hd1 \
  -o username=TU_USUARIO,password=TU_PASSWORD,uid=1000,gid=1000,file_mode=0755,dir_mode=0755

# Montar sin contraseña (share abierto/guest)
sudo mount -t cifs //192.168.100.50/hd1 /mnt/hd1 \
  -o guest,uid=1000,gid=1000,file_mode=0755,dir_mode=0755

# Verificar que el montaje permite escritura
touch /mnt/hd1/test.txt && echo "OK escritura" && rm /mnt/hd1/test.txt

Los parámetros uid y gid son importantes: sin ellos, los archivos montados vía CIFS suelen aparecer con el propietario root por defecto, lo que puede bloquear la escritura para un usuario normal — especificarlos fuerza a que el montaje asigne la propiedad al usuario local correcto.

Notas técnicas adicionales

  • El regex ^[0-9]+\.[[:space:]]*(.+)$ funciona igual para prefijos de cualquier longitud: 1., 01., 078., etc.
  • -loglevel error en ffmpeg suprime toda la salida de metadata habitual, mostrando solo errores reales — indispensable al procesar miles de archivos, para no inundar la terminal.
  • -y sobrescribe el archivo destino sin preguntar si ya existe, necesario para que el loop corra sin intervención manual.
  • Convertir un MP3 de 128kbps a FLAC o de vuelta a 320kbps no mejora la calidad — la información descartada durante la compresión con pérdida original no se puede recuperar. Reconvertir un archivo ya comprimido a un bitrate mayor solo aumenta el tamaño del archivo, no la calidad de audio real.