Módulo 5: Procesamiento de Audio
7. Troubleshooting Audio
Descripción
Trabajar con audio en IA tiene problemas específicos: formatos incompatibles, archivos que exceden límites, transcripciones imprecisas, idiomas mal detectados, rate limits, calidad de audio deficiente, problemas con TTS, y costos fuera de control. En esta cápsula documentamos los 10 problemas más comunes con soluciones concretas, construimos una función de diagnóstico automatizado, un pipeline de preprocesamiento, y una tabla de referencia rápida.
Por qué importa: En producción, los errores de audio son los más frustrantes porque son silenciosos: un formato incorrecto no genera un stack trace claro, una transcripción imprecisa no lanza una excepción. Sin una estrategia de troubleshooting, pasas horas debuggeando problemas que tienen soluciones conocidas.
Conexión con el proyecto: El Audio Pipeline (cápsula 08) integra las funciones de diagnóstico y preprocesamiento de esta cápsula. Cada error documentado aquí tiene su handler en el pipeline final.
Problema 1: Formato de Audio No Soportado
Síntoma
Error: Invalid file format. Supported formats: flac, m4a, mp3, mp4, mpeg, mpga, oga, ogg, wav, webm
O bien: Could not process audio file.
Causa
El archivo tiene un formato no soportado por Whisper (e.g., .aac, .wma, .opus standalone, .amr).
Solución
from pydub import AudioSegment
from pathlib import Path
WHISPER_FORMATS = {".mp3", ".mp4", ".mpeg", ".mpga", ".m4a", ".wav", ".webm", ".flac", ".ogg", ".oga"}
def convert_to_supported_format(
input_path: str,
target_format: str = "mp3",
bitrate: str = "128k"
) -> str:
path = Path(input_path)
if path.suffix.lower() in WHISPER_FORMATS:
return input_path
output_path = str(path.with_suffix(f".{target_format}"))
audio = AudioSegment.from_file(input_path)
audio.export(output_path, format=target_format, bitrate=bitrate)
return output_path
Formatos comunes y cómo convertirlos
| Formato original | Extensión | Solución |
|---|---|---|
| AAC | .aac | AudioSegment.from_file("f.aac") → export mp3 |
| WMA | .wma | Requiere ffmpeg: mismo proceso |
| AMR | .amr | Común en grabaciones de teléfono |
| OPUS | .opus | AudioSegment.from_file("f.opus") |
| OGG Vorbis | .ogg | Soportado directamente por Whisper |
| AIFF | .aiff | AudioSegment.from_file("f.aiff") |
| PCM raw | .pcm | Necesita sample rate y channels explícitos |
Problema 2: Archivo Excede 25MB
Síntoma
Error: Maximum content size limit (26214400) exceeded
O HTTP 413: Request Entity Too Large.
Causa
El archivo supera el límite de 25MB de la API de Whisper. Común con archivos WAV (sin compresión) o grabaciones largas.
Solución: Comprimir primero, dividir si necesario
def reduce_file_size(
input_path: str,
target_size_mb: float = 24.0
) -> str:
path = Path(input_path)
current_size_mb = path.stat().st_size / (1024 * 1024)
if current_size_mb <= target_size_mb:
return input_path
audio = AudioSegment.from_file(input_path)
output_path = str(path.with_name(f"{path.stem}_compressed.mp3"))
audio = audio.set_channels(1).set_frame_rate(16000)
audio.export(output_path, format="mp3", bitrate="64k")
new_size_mb = Path(output_path).stat().st_size / (1024 * 1024)
if new_size_mb <= target_size_mb:
return output_path
return None
def split_large_audio(
input_path: str,
max_size_mb: float = 24.0,
chunk_duration_ms: int = 10 * 60 * 1000
) -> list[str]:
audio = AudioSegment.from_file(input_path)
chunks = []
output_dir = Path(input_path).parent / "chunks"
output_dir.mkdir(exist_ok=True)
for i in range(0, len(audio), chunk_duration_ms):
chunk = audio[i:i + chunk_duration_ms]
chunk_path = str(output_dir / f"chunk_{i // chunk_duration_ms:03d}.mp3")
chunk.export(chunk_path, format="mp3", bitrate="128k")
if Path(chunk_path).stat().st_size > max_size_mb * 1024 * 1024:
sub_chunks = split_large_audio(chunk_path, max_size_mb, chunk_duration_ms // 2)
chunks.extend(sub_chunks)
else:
chunks.append(chunk_path)
return chunks
Problema 3: Transcripción de Mala Calidad
Síntoma
Texto transcrito con errores frecuentes: palabras incorrectas, oraciones sin sentido, nombres mal escritos.
Causas y soluciones
| Causa | Diagnóstico | Solución |
|---|---|---|
| Ruido de fondo alto | Audio suena ruidoso al escucharlo | Preprocesar: normalizar + reducir ruido |
| Micrófono de baja calidad | Audio distorsionado, clipping | Mejorar hardware o preprocesar |
| Multiple speakers hablando a la vez | Crosstalk en la grabación | Usar diarización (AssemblyAI) |
| Términos técnicos no reconocidos | Jerga transcrita incorrectamente | Usar prompt con vocabulario esperado |
| Idioma no detectado | Texto en idioma incorrecto | Especificar language explícitamente |
| Audio muy silencioso | Volumen bajo, apenas audible | Normalizar volumen |
Preprocesamiento para mejorar calidad
def preprocess_audio_for_quality(
input_path: str,
output_path: str = None
) -> dict:
if output_path is None:
output_path = str(Path(input_path).with_name(
f"{Path(input_path).stem}_preprocessed.mp3"
))
audio = AudioSegment.from_file(input_path)
original_dbfs = audio.dBFS
audio = audio.set_frame_rate(16000)
audio = audio.set_channels(1)
audio = audio.normalize()
audio = audio.set_sample_width(2)
audio.export(output_path, format="mp3", bitrate="64k")
return {
"output_path": output_path,
"original_dbfs": round(original_dbfs, 2),
"normalized_dbfs": round(audio.dBFS, 2),
"duration_s": round(len(audio) / 1000, 2),
"output_size_mb": round(Path(output_path).stat().st_size / (1024 * 1024), 2)
}
Usar prompt para vocabulario técnico
def transcribe_with_vocabulary(
audio_path: str,
vocabulary: list[str],
language: str = "es"
) -> str:
prompt = ", ".join(vocabulary)
with open(audio_path, "rb") as f:
response = client.audio.transcriptions.create(
model="whisper-1",
file=f,
language=language,
prompt=prompt
)
return response.text
result = transcribe_with_vocabulary(
"tech_meeting.mp3",
vocabulary=["Kubernetes", "Docker", "CI/CD", "FastAPI", "PostgreSQL", "microservicios"]
)
Problema 4: Idioma Detectado Incorrecto
Síntoma
Whisper transcribe en un idioma diferente al del audio. Común cuando el audio tiene acento fuerte, palabras en otro idioma, o fragmentos multilingües.
Solución
def transcribe_with_language_verification(
audio_path: str,
expected_language: str = "es"
) -> dict:
with open(audio_path, "rb") as f:
response = client.audio.transcriptions.create(
model="whisper-1",
file=f,
response_format="verbose_json"
)
detected = response.language
text = response.text
if detected != expected_language:
with open(audio_path, "rb") as f:
forced = client.audio.transcriptions.create(
model="whisper-1",
file=f,
language=expected_language
)
return {
"text": forced.text,
"detected_language": detected,
"forced_language": expected_language,
"warning": f"Idioma detectado ({detected}) difiere del esperado ({expected_language}). Se forzó {expected_language}."
}
return {
"text": text,
"detected_language": detected,
"forced_language": None,
"warning": None
}
Problema 5: Rate Limits
Síntoma
Error 429: Rate limit reached for whisper-1 in organization ...
Causa
Demasiadas requests por minuto. Los límites dependen del tier de tu cuenta.
Solución: Retry con backoff exponencial
import time
from functools import wraps
def with_retry(max_retries: int = 5, base_delay: float = 1.0):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
error_str = str(e).lower()
is_rate_limit = "rate_limit" in error_str or "429" in error_str
if not is_rate_limit or attempt == max_retries - 1:
raise
delay = base_delay * (2 ** attempt)
time.sleep(delay)
raise RuntimeError(f"Falló después de {max_retries} intentos")
return wrapper
return decorator
@with_retry(max_retries=5, base_delay=2.0)
def transcribe_safe(audio_path: str, language: str = "es") -> str:
with open(audio_path, "rb") as f:
return client.audio.transcriptions.create(
model="whisper-1", file=f, language=language
).text
Rate limits por tier
| Tier | Whisper RPM | TTS RPM |
|---|---|---|
| Free | 3 | 3 |
| Tier 1 | 50 | 50 |
| Tier 2 | 100 | 100 |
| Tier 3 | 500 | 500 |
Problema 6: Calidad de Audio Deficiente
Síntoma
Audio distorsionado, con eco, volumen irregular, o clipping (saturación).
Diagnóstico
def diagnose_audio_quality(audio_path: str) -> dict:
audio = AudioSegment.from_file(audio_path)
issues = []
if audio.dBFS < -35:
issues.append({
"issue": "Volumen muy bajo",
"value": f"{audio.dBFS:.1f} dBFS",
"fix": "Normalizar audio"
})
if audio.dBFS > -3:
issues.append({
"issue": "Posible clipping (saturación)",
"value": f"{audio.dBFS:.1f} dBFS",
"fix": "Reducir ganancia del audio"
})
if audio.frame_rate < 16000:
issues.append({
"issue": "Sample rate bajo",
"value": f"{audio.frame_rate} Hz",
"fix": "Resamplear a 16000 Hz mínimo"
})
if audio.channels > 1:
issues.append({
"issue": "Audio estéreo (innecesario para STT)",
"value": f"{audio.channels} canales",
"fix": "Convertir a mono para reducir tamaño"
})
duration_s = len(audio) / 1000
size_mb = Path(audio_path).stat().st_size / (1024 * 1024)
bitrate_kbps = (size_mb * 1024 * 8) / duration_s if duration_s > 0 else 0
if bitrate_kbps < 32:
issues.append({
"issue": "Bitrate muy bajo",
"value": f"{bitrate_kbps:.0f} kbps",
"fix": "Audio sobre-comprimido, usar mayor bitrate"
})
return {
"path": audio_path,
"duration_s": round(duration_s, 2),
"size_mb": round(size_mb, 2),
"sample_rate": audio.frame_rate,
"channels": audio.channels,
"dbfs": round(audio.dBFS, 2),
"bitrate_kbps": round(bitrate_kbps, 0),
"issues": issues,
"quality": "good" if not issues else "needs_attention"
}
Problema 7: TTS — Voz Inadecuada o Artefactos
Síntoma
La voz generada suena robótica, tiene cortes, o la pronunciación es incorrecta.
Soluciones por caso
| Problema | Causa probable | Solución |
|---|---|---|
| Pronunciación incorrecta en español | Voz no optimizada para español | Usar nova o alloy (mejor español) |
| Audio con artefactos | Modelo tts-1 con texto largo | Cambiar a tts-1-hd |
| Entonación plana | Texto sin puntuación | Agregar puntuación correcta al texto |
| Cortes entre chunks | Texto dividido en medio de oración | Dividir por oraciones completas |
| Velocidad inadecuada | Speed no configurado | Ajustar speed (0.25-4.0) |
Función para mejorar texto antes de TTS
def prepare_text_for_tts(text: str) -> str:
text = text.strip()
if not text.endswith((".", "!", "?", "...")):
text += "."
text = text.replace(" - ", ", ")
text = text.replace("•", ".")
text = text.replace("\n\n", ". ")
text = text.replace("\n", " ")
import re
text = re.sub(r'\s+', ' ', text)
text = text.replace("$", " dólares ")
text = text.replace("%", " por ciento")
text = text.replace("&", " y ")
return text.strip()
Problema 8: Costos Fuera de Control
Síntoma
La factura de API es mucho mayor de lo esperado.
Diagnóstico
def estimate_pipeline_cost(
audio_duration_min: float,
transcript_chars: int = None,
include_tts: bool = False,
tts_model: str = "tts-1"
) -> dict:
if transcript_chars is None:
transcript_chars = int(audio_duration_min * 150 * 5)
costs = {
"whisper": round(audio_duration_min * 0.006, 4),
"gpt-4o-mini_input": round(transcript_chars * 0.00000015, 6),
"gpt-4o-mini_output": round(500 * 0.0000006, 6),
}
if include_tts:
summary_chars = min(transcript_chars // 5, 4000)
cost_per_char = 0.000015 if tts_model == "tts-1" else 0.00003
costs["tts"] = round(summary_chars * cost_per_char, 6)
costs["total"] = round(sum(costs.values()), 4)
return costs
Estrategias de reducción de costos
| Estrategia | Ahorro estimado | Implementación |
|---|---|---|
| Cachear transcripciones | 50-90% en Whisper | Hash del archivo → cache en disco |
Usar gpt-4o-mini en vez de gpt-4o | ~95% en LLM | Suficiente para resúmenes |
Usar tts-1 en vez de tts-1-hd | 50% en TTS | Suficiente para prototipo |
| Comprimir audio antes de enviar | Reduce tamaño (no costo directo) | Menor tiempo de upload |
| Truncar transcripts largos | Variable | Enviar solo primeros 10K chars al LLM |
| Batch processing off-peak | 0% (misma tarifa) | Mejor rate limits |
Problema 9: Timeout en Archivos Largos
Síntoma
La request a Whisper se queda colgada o retorna timeout.
Solución
import httpx
def transcribe_with_timeout(
audio_path: str,
timeout_seconds: int = 300,
language: str = "es"
) -> str:
client_with_timeout = OpenAI(
timeout=httpx.Timeout(timeout_seconds, connect=10.0)
)
with open(audio_path, "rb") as f:
response = client_with_timeout.audio.transcriptions.create(
model="whisper-1",
file=f,
language=language
)
return response.text
Problema 10: Audio con Múltiples Speakers sin Diarización
Síntoma
Whisper transcribe todo como un solo bloque de texto sin distinguir quién habla.
Solución
Whisper no soporta diarización nativa. Opciones:
- Usar AssemblyAI con
speaker_labels=True - Post-procesamiento con LLM para intentar separar speakers
def infer_speakers_with_llm(transcript: str) -> str:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": (
"Este es un transcript de una conversación entre múltiples personas. "
"Intenta identificar cambios de speaker basándote en el contexto "
"(cambios de tema, respuestas a preguntas, etc). "
"Formatea como:\n"
"Speaker A: ...\n"
"Speaker B: ...\n\n"
f"Transcript:\n{transcript[:8000]}"
)
}],
max_tokens=2000
)
return response.choices[0].message.content
Función de Diagnóstico Completo
def full_audio_diagnostic(audio_path: str) -> dict:
path = Path(audio_path)
if not path.exists():
return {"error": "Archivo no encontrado", "path": audio_path}
diagnostic = {
"file": path.name,
"format": path.suffix.lower(),
"size_mb": round(path.stat().st_size / (1024 * 1024), 2),
"issues": [],
"recommendations": [],
"ready_for_whisper": True
}
if path.suffix.lower() not in WHISPER_FORMATS:
diagnostic["issues"].append(f"Formato {path.suffix} no soportado")
diagnostic["recommendations"].append("Convertir a MP3 con convert_to_supported_format()")
diagnostic["ready_for_whisper"] = False
if diagnostic["size_mb"] > 25:
diagnostic["issues"].append(f"Archivo de {diagnostic['size_mb']}MB excede límite de 25MB")
diagnostic["recommendations"].append("Comprimir con reduce_file_size() o dividir con split_large_audio()")
diagnostic["ready_for_whisper"] = False
try:
audio = AudioSegment.from_file(audio_path)
quality = diagnose_audio_quality(audio_path)
diagnostic["audio_info"] = {
"duration_s": quality["duration_s"],
"sample_rate": quality["sample_rate"],
"channels": quality["channels"],
"dbfs": quality["dbfs"],
}
diagnostic["quality_issues"] = quality["issues"]
if quality["issues"]:
diagnostic["recommendations"].append("Preprocesar con preprocess_audio_for_quality()")
except Exception as e:
diagnostic["issues"].append(f"No se pudo leer el audio: {str(e)}")
diagnostic["ready_for_whisper"] = False
estimated_cost = None
if "duration_s" in diagnostic.get("audio_info", {}):
duration_min = diagnostic["audio_info"]["duration_s"] / 60
estimated_cost = round(duration_min * 0.006, 4)
diagnostic["estimated_whisper_cost"] = f"${estimated_cost}"
return diagnostic
Pipeline de Preprocesamiento Completo
def preprocess_pipeline(
input_path: str,
output_dir: str = None
) -> dict:
path = Path(input_path)
if output_dir is None:
output_dir = str(path.parent / "preprocessed")
Path(output_dir).mkdir(exist_ok=True)
result = {
"original": input_path,
"steps": [],
"final_path": input_path
}
current = input_path
if path.suffix.lower() not in WHISPER_FORMATS:
converted = str(Path(output_dir) / f"{path.stem}.mp3")
audio = AudioSegment.from_file(current)
audio.export(converted, format="mp3", bitrate="128k")
current = converted
result["steps"].append("format_conversion")
audio = AudioSegment.from_file(current)
needs_normalize = audio.dBFS < -30 or audio.channels > 1 or audio.frame_rate < 16000
if needs_normalize:
normalized = str(Path(output_dir) / f"{path.stem}_normalized.mp3")
audio = audio.set_frame_rate(16000).set_channels(1).normalize()
audio.export(normalized, format="mp3", bitrate="64k")
current = normalized
result["steps"].append("normalization")
current_size = Path(current).stat().st_size / (1024 * 1024)
if current_size > 25:
chunks = split_large_audio(current)
result["steps"].append("splitting")
result["chunks"] = chunks
result["final_path"] = chunks
else:
result["final_path"] = current
return result
Tabla de Referencia Rápida
| Problema | Síntoma | Solución rápida |
|---|---|---|
| Formato no soportado | Invalid file format | convert_to_supported_format() |
| Archivo > 25MB | Error 413 | reduce_file_size() o split_large_audio() |
| Transcripción imprecisa | Palabras incorrectas | Preprocesar audio + usar prompt |
| Idioma incorrecto | Texto en otro idioma | Especificar language="es" |
| Rate limit | Error 429 | @with_retry decorator |
| Audio muy silencioso | dBFS < -35 | audio.normalize() |
| TTS con artefactos | Sonidos extraños | Usar tts-1-hd |
| TTS texto largo | Error o truncamiento | Dividir en chunks de 4000 chars |
| Timeout | Request colgada | Aumentar timeout, dividir audio |
| Sin diarización | Todo como un speaker | Usar AssemblyAI o post-procesar con LLM |
| Costo alto | Factura inesperada | Cachear transcripciones, usar gpt-4o-mini |
| Clipping/saturación | dBFS > -3 | Reducir ganancia antes de normalizar |
Ejercicios
Ejercicio 1: Validador completo de audio
Crea una función que valide un archivo de audio y retorne un reporte detallado con todos los problemas encontrados y las funciones específicas para resolverlos.
Ver solución
def validate_and_report(audio_path: str) -> dict:
diagnostic = full_audio_diagnostic(audio_path)
if diagnostic.get("error"):
return diagnostic
fixes = {}
for issue in diagnostic.get("issues", []):
if "formato" in issue.lower() or "format" in issue.lower():
fixes["convert_format"] = {
"function": "convert_to_supported_format(audio_path)",
"description": "Convierte a MP3 compatible con Whisper"
}
if "25mb" in issue.lower() or "excede" in issue.lower():
fixes["reduce_size"] = {
"function": "reduce_file_size(audio_path)",
"description": "Comprime a mono 16kHz 64kbps"
}
fixes["split_audio"] = {
"function": "split_large_audio(audio_path)",
"description": "Divide en chunks de 10 min"
}
for qi in diagnostic.get("quality_issues", []):
issue_type = qi["issue"].lower()
if "volumen" in issue_type:
fixes["normalize"] = {
"function": "preprocess_audio_for_quality(audio_path)",
"description": "Normaliza volumen y sample rate"
}
if "sample rate" in issue_type:
fixes["resample"] = {
"function": "audio.set_frame_rate(16000)",
"description": "Resamplea a 16kHz"
}
if "estéreo" in issue_type:
fixes["mono"] = {
"function": "audio.set_channels(1)",
"description": "Convierte a mono"
}
diagnostic["fixes"] = fixes
diagnostic["auto_fix_available"] = len(fixes) > 0
diagnostic["auto_fix_command"] = "preprocess_pipeline(audio_path)" if fixes else None
return diagnostic
Ejercicio 2: Monitor de costos acumulados
Crea una clase que trackee los costos acumulados de todas las operaciones de audio y alerte cuando se acerque a un presupuesto definido.
Ver solución
from dataclasses import dataclass, field
@dataclass
class AudioCostMonitor:
budget_usd: float = 5.0
warning_threshold: float = 0.8
costs: list[dict] = field(default_factory=list)
@property
def total_spent(self) -> float:
return sum(c["cost"] for c in self.costs)
@property
def remaining(self) -> float:
return max(0, self.budget_usd - self.total_spent)
@property
def utilization(self) -> float:
return self.total_spent / self.budget_usd if self.budget_usd > 0 else 0
def log_cost(self, service: str, cost: float, description: str = ""):
self.costs.append({
"service": service,
"cost": round(cost, 6),
"description": description,
"timestamp": time.time()
})
if self.utilization >= self.warning_threshold:
print(f"ALERTA: {self.utilization:.0%} del presupuesto usado. "
f"Restante: ${self.remaining:.4f}")
def check_budget(self, estimated_cost: float) -> dict:
can_afford = estimated_cost <= self.remaining
return {
"can_afford": can_afford,
"estimated_cost": round(estimated_cost, 6),
"remaining_budget": round(self.remaining, 4),
"total_spent": round(self.total_spent, 4)
}
def get_report(self) -> dict:
by_service = {}
for c in self.costs:
svc = c["service"]
by_service[svc] = by_service.get(svc, 0) + c["cost"]
return {
"total_spent": round(self.total_spent, 4),
"budget": self.budget_usd,
"remaining": round(self.remaining, 4),
"utilization": f"{self.utilization:.1%}",
"by_service": {k: round(v, 4) for k, v in by_service.items()},
"operations": len(self.costs)
}
Ejercicio 3: Auto-fix pipeline
Crea una función que tome un archivo de audio problemático, ejecute el diagnóstico completo, y aplique automáticamente todas las correcciones necesarias.
Ver solución
def auto_fix_audio(
audio_path: str,
output_dir: str = "fixed_audio"
) -> dict:
Path(output_dir).mkdir(exist_ok=True)
diagnostic = full_audio_diagnostic(audio_path)
if diagnostic.get("error"):
return {"success": False, "error": diagnostic["error"]}
if diagnostic["ready_for_whisper"] and not diagnostic.get("quality_issues"):
return {
"success": True,
"output_path": audio_path,
"fixes_applied": [],
"message": "Audio ya está listo para Whisper"
}
fixes_applied = []
current_path = audio_path
if Path(audio_path).suffix.lower() not in WHISPER_FORMATS:
converted = str(Path(output_dir) / f"{Path(audio_path).stem}.mp3")
AudioSegment.from_file(audio_path).export(converted, format="mp3", bitrate="128k")
current_path = converted
fixes_applied.append("format_conversion")
quality = diagnose_audio_quality(current_path)
if quality["issues"]:
result = preprocess_audio_for_quality(
current_path,
str(Path(output_dir) / f"{Path(audio_path).stem}_normalized.mp3")
)
current_path = result["output_path"]
fixes_applied.append("quality_normalization")
size_mb = Path(current_path).stat().st_size / (1024 * 1024)
if size_mb > 25:
compressed = reduce_file_size(current_path)
if compressed:
current_path = compressed
fixes_applied.append("compression")
else:
chunks = split_large_audio(current_path)
return {
"success": True,
"output_paths": chunks,
"fixes_applied": fixes_applied + ["splitting"],
"message": f"Audio dividido en {len(chunks)} chunks"
}
return {
"success": True,
"output_path": current_path,
"fixes_applied": fixes_applied,
"message": f"Se aplicaron {len(fixes_applied)} correcciones"
}
Ejercicio 4: Comparador de calidad pre/post procesamiento
Crea una función que transcriba el mismo audio antes y después de preprocesamiento, y compare los resultados.
Ver solución
def compare_preprocessing_quality(
audio_path: str,
language: str = "es"
) -> dict:
t1 = time.time()
with open(audio_path, "rb") as f:
original_transcript = client.audio.transcriptions.create(
model="whisper-1", file=f, language=language,
response_format="verbose_json"
)
original_time = time.time() - t1
prepped = preprocess_audio_for_quality(audio_path)
t2 = time.time()
with open(prepped["output_path"], "rb") as f:
processed_transcript = client.audio.transcriptions.create(
model="whisper-1", file=f, language=language,
response_format="verbose_json"
)
processed_time = time.time() - t2
original_words = set(original_transcript.text.lower().split())
processed_words = set(processed_transcript.text.lower().split())
common = original_words & processed_words
similarity = len(common) / max(len(original_words | processed_words), 1)
return {
"original": {
"text": original_transcript.text,
"language": original_transcript.language,
"duration": original_transcript.duration,
"word_count": len(original_transcript.text.split()),
"transcription_time_s": round(original_time, 2),
"file_size_mb": round(Path(audio_path).stat().st_size / (1024 * 1024), 2)
},
"processed": {
"text": processed_transcript.text,
"language": processed_transcript.language,
"duration": processed_transcript.duration,
"word_count": len(processed_transcript.text.split()),
"transcription_time_s": round(processed_time, 2),
"file_size_mb": prepped["output_size_mb"]
},
"comparison": {
"word_similarity": round(similarity, 3),
"word_count_diff": len(processed_transcript.text.split()) - len(original_transcript.text.split()),
"size_reduction_pct": round(
(1 - prepped["output_size_mb"] / (Path(audio_path).stat().st_size / (1024 * 1024))) * 100, 1
)
}
}
Resumen
- 10 problemas comunes documentados con síntomas, causas y soluciones concretas.
- Función de diagnóstico (
full_audio_diagnostic()) analiza formato, tamaño, calidad y estima costos. - Pipeline de preprocesamiento (
preprocess_pipeline()) convierte formato, normaliza audio y divide archivos grandes automáticamente. - Retry con backoff para manejar rate limits de manera resiliente.
- Tabla de referencia rápida para resolver problemas en segundos.
- Siempre diagnosticar antes de transcribir — es más barato debuggear audio que re-intentar transcripciones fallidas.
Recursos Adicionales
- pydub Documentation — Manipulación de audio
- FFmpeg — Herramienta de conversión base
- Whisper Supported Formats — Formatos oficiales
- OpenAI Rate Limits — Límites por tier
- Audio Quality for STT — Best practices de Google (aplican a todos los proveedores)