fix(transcriptor): detectar formato de audio desde Content-Type antes de inferir por URL

El fallback a mp3 causaba que Whisper rechazara archivos mp4/m4a de
plataformas sociales cuya URL no tiene extensión reconocible. Ahora
se prioriza el header Content-Type de la respuesta HTTP, luego la
extensión de la URL, y el fallback final es mp4 en lugar de mp3.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 19:58:51 -05:00
parent 48978d1752
commit d9ba114b84

View File

@ -38,10 +38,32 @@ export async function transcribir(audioUrl, idioma = 'es') {
// En Vercel Serverless (Node < 20), Web API `File` no está disponible por defecto, // En Vercel Serverless (Node < 20), Web API `File` no está disponible por defecto,
// y `arrayBuffer` consume mucha RAM. `toFile` soluciona ambos. // y `arrayBuffer` consume mucha RAM. `toFile` soluciona ambos.
// Detectar extensión real del audio (TikTok→mp3, Instagram→m4a, etc.) const formatosSoportados = ['flac', 'm4a', 'mp3', 'mp4', 'mpeg', 'mpga', 'oga', 'ogg', 'wav', 'webm']
const ext = audioUrl.split('?')[0].split('.').pop()?.toLowerCase() || 'mp3' const mimeMap = { mp3: 'audio/mpeg', m4a: 'audio/mp4', mp4: 'audio/mp4', webm: 'audio/webm', ogg: 'audio/ogg', wav: 'audio/wav', flac: 'audio/flac', mpeg: 'audio/mpeg', mpga: 'audio/mpeg', oga: 'audio/ogg' }
const mimeMap = { mp3: 'audio/mpeg', m4a: 'audio/mp4', mp4: 'audio/mp4', webm: 'audio/webm', ogg: 'audio/ogg', wav: 'audio/wav' }
const mimeType = mimeMap[ext] || 'audio/mpeg' // 1. Intentar detectar formato desde el Content-Type de la respuesta HTTP
const contentType = audioResponse.headers.get('content-type') || ''
const contentTypeToExt = {
'audio/mpeg': 'mp3', 'audio/mp3': 'mp3', 'audio/mp4': 'm4a',
'video/mp4': 'mp4', 'audio/webm': 'webm', 'video/webm': 'webm',
'audio/ogg': 'ogg', 'audio/flac': 'flac', 'audio/wav': 'wav',
'audio/x-wav': 'wav', 'audio/x-m4a': 'm4a',
}
let ext = null
for (const [mime, e] of Object.entries(contentTypeToExt)) {
if (contentType.includes(mime)) { ext = e; break }
}
// 2. Si el Content-Type no fue útil, intentar con la extensión de la URL
if (!ext) {
const urlExt = audioUrl.split('?')[0].split('.').pop()?.toLowerCase()
ext = formatosSoportados.includes(urlExt) ? urlExt : null
}
// 3. Fallback a mp4 (más común en plataformas sociales y soportado por Whisper)
if (!ext) ext = 'mp4'
const mimeType = mimeMap[ext] || 'audio/mp4'
// toFile es async en el SDK de OpenAI — await es necesario aunque el IDE lo marque como hint // toFile es async en el SDK de OpenAI — await es necesario aunque el IDE lo marque como hint
const audioFile = await toFile(audioResponse, `audio.${ext}`, { type: mimeType }) const audioFile = await toFile(audioResponse, `audio.${ext}`, { type: mimeType })