Codifica audio con cURL y herramientas de código abierto
Usa cURL para descargar el audio y FFmpeg para codificarlo. Este tutorial genera un archivo M4A local a partir de una muestra MP3 real y luego convierte ese flujo de trabajo en un script Bash para generar archivos AAC, M4A, MP3 u Opus. Subir el resultado requiere un receptor con su propio contrato de API y queda fuera del alcance de este ejemplo.
Configura tu entorno
Usa Linux con Bash, cURL, FFmpeg y ffprobe en tu PATH. La compilación de FFmpeg necesita los codificadores aac,
libmp3lame y libopus. Instala paquetes con mantenimiento activo para tu distribución;
la página de descargas de FFmpeg enlaza a proveedores de paquetes.
Los ejemplos se probaron con Bash 5.3.15, cURL 8.22.0 y FFmpeg/ffprobe 9.0.1, con una prueba adicional
de compatibilidad en FFmpeg/ffprobe 6.1.1. Estas son las versiones probadas, no una recomendación de
instalar una versión de parche antigua.
Comprueba las herramientas y los codificadores instalados:
bash --version &&
curl --version &&
ffmpeg -version &&
ffprobe -version &&
ffmpeg -hide_banner -encoders
Empieza con una grabación completa, sin cifrar, mono o estéreo, a 44,1 o 48 kHz. Los ejemplos cubren entradas MP3 y PCM WAV, incluidas muestras de enteros y de punto flotante. No definen una asignación de canales de sonido envolvente ni fuerzan una frecuencia de muestreo; FFmpeg puede remuestrear cuando el códec de salida lo necesite. Usa audio que tengas permiso para procesar.
Codificación básica de audio con FFmpeg y cURL
Pega lo siguiente en Bash desde un directorio donde aún no exista audio-example. Descarga
viper.mp3 del ejemplo de Web Audio de MDN,
y luego codifica su primer flujo de audio como AAC en un contenedor M4A. La muestra fijada tiene
unos 41 segundos de audio estéreo a 44,1 kHz.
(
set -euo pipefail
mkdir -- audio-example || exit 1
trap 'status=$?; if [ "$status" -ne 0 ]; then
rm -f -- audio-example/input.mp3 audio-example/output.m4a audio-example/ffmpeg-errors.log
rmdir -- audio-example 2>/dev/null || true
fi' EXIT
curl -fsSL --retry 3 --connect-timeout 10 --max-time 60 \
--proto '=https' --proto-redir '=https' \
-o audio-example/input.mp3 \
https://raw.githubusercontent.com/mdn/webaudio-examples/5af6b7a1545ad572c2dd2ce0443eeb26ab7c3e61/audio-analyser/viper.mp3 || exit 1
if ! ffmpeg -nostdin -v error -n -xerror -i audio-example/input.mp3 -map 0:a:0 \
-c:a aac -b:a 192k -f ipod audio-example/output.m4a \
2>audio-example/ffmpeg-errors.log || [ -s audio-example/ffmpeg-errors.log ]; then
cat -- audio-example/ffmpeg-errors.log >&2
exit 1
fi
rm -f -- audio-example/ffmpeg-errors.log
)
Si se completa correctamente, audio-example contiene el MP3 descargado y output.m4a.
Si falla la descarga o la codificación, se eliminan los archivos de este intento. Si el directorio
ya existe, el bloque se detiene antes de escribir nada. Los paréntesis mantienen las opciones del
shell dentro de un subshell, por lo que pegar el bloque no modifica tu shell de trabajo. Ejecuta
este ejemplo de forma secuencial en un directorio que controles.
La opción -map 0:a:0 selecciona el primer flujo de audio,
mientras que -n impide reemplazar una salida existente y -nostdin desactiva la entrada interactiva.
La tasa de bits es un objetivo para el codificador, no una garantía del tamaño del archivo ni de
la calidad perceptual. Volver a codificar un MP3 no puede recuperar los detalles que ya se perdieron
en su compresión original.
Crea un script de codificación de audio
Guarda lo siguiente como encode_audio.sh en tu directorio actual. El argumento de formato
selecciona tanto un codificador como un contenedor; cambiar solo la extensión de un archivo no
convierte el audio. La documentación de formatos de FFmpeg
describe estos multiplexores.
| Argumento | Códec de audio | Contenedor | Archivo de salida |
|---|---|---|---|
aac | AAC | ADTS | output.aac |
m4a | AAC | Audio MPEG-4 | output.m4a |
mp3 | MP3 mediante libmp3lame | MP3 | output.mp3 |
opus | Opus mediante libopus | Ogg | output.opus |
#!/bin/bash
set -euo pipefail
if [ "$#" -ne 3 ]; then
echo "Usage: $0 <input_url> <output_format> <output_bitrate>" >&2
exit 1
fi
INPUT_URL=$1
OUTPUT_FORMAT=$2
BITRATE=$3
case "$OUTPUT_FORMAT" in
aac) CODEC=aac; CONTAINER=adts ;;
m4a) CODEC=aac; CONTAINER=ipod ;;
mp3) CODEC=libmp3lame; CONTAINER=mp3 ;;
opus) CODEC=libopus; CONTAINER=ogg ;;
*) echo "Unsupported output format: $OUTPUT_FORMAT" >&2; exit 1 ;;
esac
if [[ ! "$BITRATE" =~ ^[1-9][0-9]*k$ ]]; then
echo "Bitrate must be a positive integer followed by k, such as 192k" >&2
exit 1
fi
WORK_DIR=$(mktemp -d ./audio-encode.XXXXXX)
INPUT_FILE="$WORK_DIR/input.audio"
OUTPUT_FILE="$WORK_DIR/output.$OUTPUT_FORMAT"
ERROR_LOG="$WORK_DIR/ffmpeg-errors.log"
# Delete the download; retain the output only after successful encoding.
trap 'status=$?; rm -f -- "$INPUT_FILE" "$ERROR_LOG";
if [ "$status" -ne 0 ]; then rm -f -- "$OUTPUT_FILE"; fi
rmdir -- "$WORK_DIR" 2>/dev/null || true' EXIT
echo "Downloading input file…"
if ! curl -fsSL --retry 3 --connect-timeout 10 --max-time 60 \
--proto '=http,https' --proto-redir '=http,https' \
-o "$INPUT_FILE" -- "$INPUT_URL"; then
echo "Error: Failed to download input file" >&2
exit 1
fi
echo "Encoding to ${OUTPUT_FORMAT} format…"
if ! ffmpeg -nostdin -v error -n -xerror -i "$INPUT_FILE" -map 0:a:0 -vn \
-c:a "$CODEC" -b:a "$BITRATE" -f "$CONTAINER" "$OUTPUT_FILE" \
2>"$ERROR_LOG" || [ -s "$ERROR_LOG" ]; then
cat -- "$ERROR_LOG" >&2
echo "Error: Failed to encode audio" >&2
exit 1
fi
echo "Successfully encoded to: ${OUTPUT_FILE}"
Ejecuta el script guardado con Bash; no necesita permisos de ejecución:
bash ./encode_audio.sh \
https://raw.githubusercontent.com/mdn/webaudio-examples/5af6b7a1545ad572c2dd2ce0443eeb26ab7c3e61/audio-analyser/viper.mp3 \
opus 128k
Si se completa correctamente, imprime una ruta como ./audio-encode.A1b2C3/output.opus. Cada invocación crea
un nuevo directorio de trabajo, por lo que volver a ejecutar el script con la misma URL conserva
los resultados anteriores. El script elimina su descarga y, si falla la descarga o la codificación,
elimina cualquier salida parcial y el directorio de trabajo vacío. Los errores de argumentos se
producen antes de crear archivos. Esta limpieza cubre los fallos habituales de los comandos;
la terminación forzada del proceso o el apagado del equipo pueden dejar archivos temporales.
128k significa un objetivo de 128.000 bits por segundo. El script comprueba cómo
está escrito el argumento; el codificador seleccionado sigue siendo quien determina si admite esa
tasa de bits. La descarga termina antes de la codificación, por lo que FFmpeg puede desplazarse a
distintas posiciones dentro del archivo de entrada.
Procesamiento de archivos de audio por lotes
Guarda esto como batch_encode.sh junto a encode_audio.sh. Ejecútalo desde ese directorio.
Cada fila de la lista de entrada contiene una URL, un formato y una tasa de bits separados por
espacios. Se omiten las líneas en blanco; codifica con porcentajes los espacios dentro de las URL.
No se admiten comentarios ni campos adicionales.
#!/bin/bash
set -euo pipefail
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <input_file_list.txt>" >&2
echo "File list format: <input_url> <output_format> <bitrate>" >&2
exit 1
fi
INPUT_LIST=$1
failed=0
while IFS=' ' read -r url format bitrate extra || [[ -n "$url" ]]; do
[[ -z "$url" ]] && continue
echo "Processing: ${url}"
if [[ -n "$extra" || -z "$format" || -z "$bitrate" ]]; then
echo "Invalid list entry: $url" >&2
failed=1
elif bash ./encode_audio.sh "$url" "$format" "$bitrate"; then
echo "Success: ${url}"
else
echo "Failed: ${url}" >&2
failed=1
fi
done < "${INPUT_LIST}"
exit "$failed"
Por ejemplo, guarda estas dos filas como audio-list.txt:
https://raw.githubusercontent.com/mdn/webaudio-examples/5af6b7a1545ad572c2dd2ce0443eeb26ab7c3e61/audio-analyser/viper.mp3 m4a 192k
https://raw.githubusercontent.com/mdn/webaudio-examples/5af6b7a1545ad572c2dd2ce0443eeb26ab7c3e61/audio-analyser/viper.mp3 mp3 128k
bash ./batch_encode.sh audio-list.txt
El lote se ejecuta de forma secuencial y conserva las salidas correctas aunque falle otra fila. Continúa tras las entradas no válidas, las descargas fallidas y las codificaciones fallidas, y luego termina con el estado 1 si falló alguna tarea. El estado 0 significa que todas las filas procesadas se completaron correctamente; una lista vacía no realiza ningún trabajo.
Consideraciones de seguridad
Usa URL HTTPS de fuentes en las que confíes. cURL
verifica los certificados del servidor de forma predeterminada; no añadas -k
para omitir esa verificación. Las
restricciones --proto y --proto-redir del script solo permiten
HTTP y HTTPS, incluidas las redirecciones. HTTP sigue disponible para un servidor de pruebas local,
pero no proporciona cifrado durante el transporte.
Estos scripts son ejemplos de conversión local. No aíslan FFmpeg en un entorno restringido, no imponen límites de tamaño de descarga ni hacen que un servidor pueda recuperar de forma segura cualquier URL proporcionada por un usuario. Los tiempos de espera de cURL limitan cada intento de transferencia, y los reintentos pueden prolongar la duración total de la descarga. Deja suficiente espacio en disco para la entrada y la salida completas.
Gestión de errores y validación
Comprueba la salida real del ejemplo básico con este bloque desde el mismo directorio donde lo ejecutaste:
(
set -euo pipefail
VERIFY_LOG=$(mktemp ./audio-verify.XXXXXX)
trap 'rm -f -- "$VERIFY_LOG"' EXIT
ffprobe -v error -select_streams a:0 \
-show_entries stream=codec_name,sample_rate,channels:format=format_name,duration \
-of json audio-example/output.m4a || exit 1
if ! ffmpeg -nostdin -v error -xerror -i audio-example/output.m4a \
-map 0:a:0 -f null - 2>"$VERIFY_LOG" || [ -s "$VERIFY_LOG" ]; then
cat -- "$VERIFY_LOG" >&2
exit 1
fi
)
El valor de codec_name debería ser aac, con dos canales y una duración cercana a 41 segundos.
ffprobe identifica el contenedor M4A como parte de la familia mov,mp4,m4a,3gp,3g2,mj2. El segundo
comando decodifica toda la salida sin guardar otro archivo. Para un resultado del script, sustituye
la ruta en ambos comandos por la ruta exacta que imprimió esa invocación. El AAC ADTS sin procesar
incluye retardo del codificador y relleno, y ffprobe puede estimar su duración a partir de la tasa
de bits. Compara el audio decodificado con la línea de tiempo de la fuente en lugar de tratar esa
estimación como una duración exacta.
-xerror solicita que FFmpeg se detenga ante errores.
FFmpeg 6.1.1 puede informar de un error tardío del decodificador y aun así devolver el estado 0.
Por eso, los bloques también capturan stderr con el nivel de registro error y rechazan
un registro de errores que no esté vacío, imprimiendo su diagnóstico antes de la limpieza. Esto
detecta los errores notificados, pero no demuestra la integridad: una grabación truncada en un
punto que permita decodificarla todavía puede codificarse o decodificarse correctamente, y algunos
paquetes dañados pueden descartarse de forma silenciosa. Compara la duración con una fuente que
sepas que está completa y escucha hasta el final; usa una suma de verificación de un proveedor
confiable cuando esté disponible. La existencia del archivo, un encabezado legible y un estado de
salida de cero no demuestran que se haya recibido todo el audio esperado.
Un error HTTP de cURL, como el 404, detiene la descarga antes de la codificación. Un codificador no disponible, un archivo sin flujo de audio o una tasa de bits rechazada por el codificador detienen la conversión. Conserva el diagnóstico en stderr al investigar un fallo. Si el lote termina con el estado 1, usa sus mensajes por fila para identificar los fallos; las salidas ya completadas siguen disponibles.
Conclusión
Para un flujo de trabajo de codificación alojado, consulta la documentación del Robot 🤖 /audio/encode. Su contrato de subida y autenticación es independiente de esta descarga local con cURL y conversión con FFmpeg.
