Extraer el audio de un vídeo para escucharlo como podcast o repasar una clase es un caso de uso muy típico. El problema viene cuando quieres hacerlo:
- Sin instalar nada raro en el ordenador
- Desde el móvil
En este post cuento cómo está montado por dentro el extractor de audio que usamos en
👉 convertiraudioamp3.com
y en la herramienta de
👉 vídeo a MP3
No explico el código entero, pero sí las ideas clave: FastAPI, yt-dlp, ffmpeg, reCAPTCHA y algunos trucos para que funcione razonablemente bien con YouTube (incluidos Shorts).
Flujo general: de URL a MP3 descargable
El flujo del endpoint “URL → MP3” es algo así:
- El usuario envía un POST con:
- source_url: enlace de YouTube (u otra plataforma compatible)
- calidad_mp3: 192k, 128k o 64k
recaptcha_token: para evitar abuso/robots
Validamos el reCAPTCHA y la URL.
Usamos yt-dlp para inspeccionar los formatos disponibles y elegir el mejor stream de audio.
Descargamos solo el audio (o un stream progresivo de vídeo+audio si no queda otra).
Pasamos ese archivo a ffmpeg para convertirlo a MP3 con el bitrate solicitado.
Devolvemos un JSON con:
download_url para un endpoint de descarga tipo /descargar/{filename}
Limpiamos archivos temporales.
A nivel de usuario, todo eso es un formulario muy simple en
👉 convertiraudioamp3.com/convertir_video_a_mp3
Capa HTTP: FastAPI + reCAPTCHA
La parte web es un endpoint clásico de FastAPI con formulario:
@router.post("/convertir_video_a_mp3")
async def convertir_enlace_a_mp3(
request: Request,
source_url: str = Form(...),
calidad_mp3: str = Form("128k"),
):
# 1) Log de request (request_id, IP, path, etc.)
# 2) Verificar reCAPTCHA con el token del formulario
# 3) Validar/normalizar la URL
# 4) Lanzar la lógica de descarga + conversión
...
Cosas a destacar:
- reCAPTCHA: la verificación se hace antes de gastar CPU/red con yt-dlp.
- Normalización de URL: usamos una función normalize_youtube_url(...) que:
- fuerza https://
- limpia parámetros prescindibles en URLs de YouTube
- request id (rid): cada petición lleva un UUID en logs, lo que ayuda muchísimo a depurar problemas con vídeos concretos.
yt-dlp: elegir bien el stream de audio
yt-dlp da mucha información de formatos. En lugar de usar un simple "format": "bestaudio/best", nos interesaba:
- Evitar formatos sin URL directa (storyboards, miniaturas, etc.)
- Preferir audio-only (ej. m4a/140) si está disponible
- Ignorar formatos pseudo-vacíos
- Tener un fallback progresivo (vídeo+audio) para casos raros
La idea de la función de selección de formato es algo así (pseudocódigo simplificado):
def pick_best_audio_format(info_dict):
fmts = info_dict.get("formats") or []
def is_valid_audio(f):
return (
f.get("vcodec") == "none"
and (f.get("acodec") or "").lower() not in {"", "none"}
and has_url(f)
and not is_storyboard(f)
)
audio_only = [f for f in fmts if is_valid_audio(f)]
# Preferimos m4a / id 140 si existe (muy típico en YouTube)
m4a = [f for f in audio_only if f.get("ext") == "m4a" or f.get("format_id") == "140"]
if m4a:
return best_by_abr(m4a)
# Luego cualquier audio-only razonable
if audio_only:
return best_by_abr(audio_only)
# Fallback: stream progresivo vídeo+audio (extraeremos audio con ffmpeg)
progressive = [f for f in fmts if is_progressive_with_audio(f)]
if progressive:
return best_by_abr_or_tbr(progressive)
# Último recurso
return "bestaudio/best"
En la implementación real se añaden filtros extra:
- has_url(f): revisa url, manifest_url o fragment_base_url.
- is_storyboard(f): descarta formatos con format_id tipo sb* o extensiones mhtml, imágenes, etc.
Todo esto reduce bastante los casos de “no hay formatos válidos” y ahorra ancho de banda.
Jugar con player_client: Android, iOS y Web
Un detalle interesante de yt-dlp es el argumento:
"extractor_args": {"youtube": {"player_client": [player_client]}}
YouTube no se comporta igual si finges ser:
- un cliente Android
- un cliente iOS
- el reproductor web
En la práctica, para cierto contenido (y sobre todo Shorts) algunos clientes funcionan mejor que otros. El endpoint hace algo como:
clients = ["android", "ios", "web"]
downloaded = False
for client in clients:
try:
opts = ytdlp_opts_base(client)
info = extract_info_only(opts, source_url)
fmt = pick_best_audio_format(info)
download_with_format(opts, source_url, fmt)
downloaded = True
break
except Exception as e:
log_error(e, client)
continue
# Fallback sin player_client (a veces da formatos que los otros bloquean)
if not downloaded:
opts = ytdlp_opts_base("web")
opts.pop("extractor_args", None) # sin client
...
En ytdlp_opts_base(...) también se ajustan cosas como:
- User-Agent y Accept-Language
- force_ipv4
- retries, fragment_retries, socket_timeout
- cookiefile si existe (para sortear algunas restricciones)
Conversión a MP3 con ffmpeg
Una vez descargado el archivo (audio o vídeo+audio), lo pasamos por ffmpeg:
cmd = [
FFMPEG_BIN, "-y",
"-i", str(downloaded_path),
"-vn", # sin vídeo
"-c:a", "libmp3lame",
"-b:a", calidad_mp3, # "192k", "128k" o "64k"
"-ar", "44100",
"-ac", "2",
str(final_mp3),
]
safe_run_ffmpeg(cmd)```
{% endraw %}
Algunos detalles:
- safe_run_ffmpeg es un wrapper que limita tiempo de ejecución y captura errores.
- Usamos siempre 44100 Hz y 2 canales por compatibilidad, aunque para voz pura se podría optimizar a mono.
- Guardamos tamaño final en MB y bitrate para poder hacer estadísticas agregadas (sin datos personales).
## Descarga por streaming y limpieza
El endpoint de descarga es un GET /descargar/{filename} que envía el archivo como StreamingResponse en chunks, algo como:
{% raw %}
```python
def iterfile(path: Path, chunk_size: int = 1024 * 1024):
with path.open("rb") as f:
while chunk := f.read(chunk_size):
yield chunk
return StreamingResponse(
iterfile(file_path),
media_type="audio/mpeg",
headers={
"Content-Disposition": f'attachment; filename="{filename}"',
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0, private",
},
)
Y, muy importante:
- El archivo descargado original se borra en un finally.
- Los MP3 generados viven en disco, pero hay un proceso de limpieza que va eliminando todo de forma automática cada 24 h.
Ese mismo patrón lo usamos no solo para la parte de YouTube, sino para el resto de herramientas en la web, incluído el conversor de audio general:
👉 https://convertiraudioamp3.com/
Seguridad, límites y aspectos legales
Al ser un servicio público, hay varios puntos a cuidar:
- reCAPTCHA en la ruta de URL → MP3 para frenar abuso automatizado.
- Validación estricta de URLs (http(s):// y normalización) para no dejar que esto se convierta en un proxy abierto.
- Logs mínimos pero útiles: request_id, IP, user-agent, bitrate, tamaño; nada de contenido privado.
- Privacidad: archivos temporales y finales se eliminan de forma periódica.
- Derechos de autor: el formulario y los textos dejan claro que la opción de enlace (YouTube u otros) es solo para contenido propio o con permiso.
Si solo quieres trastear con el flujo técnico, puedes ignorar la parte de YouTube y probar con vídeo local en la misma herramienta de
👉 vídeo a MP3
subiendo un MP4/MOV/MKV desde tu disco.
Top comments (0)