Módulo 5: Procesamiento de Audio
3. Alternativas a Whisper
Descripción
Whisper no es el único proveedor de STT. Google Speech-to-Text, AssemblyAI y Deepgram ofrecen capacidades que Whisper no tiene: diarización de speakers, detección de PII, streaming en tiempo real, y modelos optimizados para dominios específicos. En esta cápsula compararás los 4 proveedores principales con código funcional, tablas comparativas y un árbol de decisión para elegir el correcto según tu caso de uso.
Por qué importa: Elegir el proveedor incorrecto puede significar pagar más por menos features, o perder funcionalidad crítica como diarización. Un developer que solo conoce Whisper está limitado. Con esta cápsula, tendrás criterio para elegir entre 4 proveedores según costo, precisión, features y caso de uso.
Conexión con el proyecto: El Audio Pipeline (cápsula 08) usa Whisper por defecto, pero puedes extenderlo con fallback a otros proveedores. La función transcribe() unificada que construirás aquí permite cambiar de proveedor con un solo parámetro.
Comparación General
Tabla comparativa
| Característica | Whisper (OpenAI) | Google STT | AssemblyAI | Deepgram |
|---|---|---|---|---|
| Precio | $0.006/min | $0.004-0.016/min | $0.0065/min | $0.0043/min |
| Idiomas | 50+ | 125+ | 100+ | 30+ |
| Precisión (inglés) | Alta | Alta | Muy alta | Alta |
| Precisión (español) | Alta | Alta | Alta | Buena |
| Diarización | No | Sí | Sí | Sí |
| Detección de PII | No | No | Sí | Sí |
| Streaming | No | Sí | Sí | Sí |
| Word timestamps | Sí | Sí | Sí | Sí |
| Traducción integrada | Sí (→ inglés) | No | No | No |
| SDK Python | openai | google-cloud-speech | assemblyai | deepgram-sdk |
| Free tier | No | 60 min/mes | Créditos iniciales | $200 créditos |
| Latencia | Media | Baja | Media | Muy baja |
¿Cuándo cada proveedor es la mejor opción?
| Necesitas... | Elige |
|---|---|
| Transcripción simple y rápida | Whisper |
| Diarización (quién dijo qué) | AssemblyAI o Deepgram |
| 125+ idiomas | Google STT |
| Menor costo por minuto | Deepgram |
| Traducción integrada | Whisper |
| Streaming en tiempo real | Google STT o Deepgram |
| Detección de PII | AssemblyAI |
| Integración con GCP | Google STT |
| Menor latencia | Deepgram |
| Ya usas OpenAI | Whisper |
Google Speech-to-Text
Setup
pip install google-cloud-speech
# Configura credenciales de GCP
export GOOGLE_APPLICATION_CREDENTIALS="path/to/service-account.json"
Transcripción básica
from google.cloud import speech
def transcribe_google(audio_path: str, language: str = "es-ES") -> str:
client = speech.SpeechClient()
with open(audio_path, "rb") as f:
content = f.read()
audio = speech.RecognitionAudio(content=content)
config = speech.RecognitionConfig(
encoding=speech.RecognitionConfig.AudioEncoding.LINEAR16,
sample_rate_hertz=16000,
language_code=language,
enable_automatic_punctuation=True,
)
response = client.recognize(config=config, audio=audio)
return " ".join(
result.alternatives[0].transcript
for result in response.results
)
Transcripción con diarización
def transcribe_google_with_diarization(
audio_path: str,
language: str = "es-ES",
num_speakers: int = 2
) -> list[dict]:
client = speech.SpeechClient()
with open(audio_path, "rb") as f:
content = f.read()
audio = speech.RecognitionAudio(content=content)
diarization_config = speech.SpeakerDiarizationConfig(
enable_speaker_diarization=True,
min_speaker_count=num_speakers,
max_speaker_count=num_speakers,
)
config = speech.RecognitionConfig(
encoding=speech.RecognitionConfig.AudioEncoding.LINEAR16,
sample_rate_hertz=16000,
language_code=language,
diarization_config=diarization_config,
)
response = client.recognize(config=config, audio=audio)
result = response.results[-1]
utterances = []
current_speaker = None
current_text = []
for word in result.alternatives[0].words:
if word.speaker_tag != current_speaker:
if current_text:
utterances.append({
"speaker": f"Speaker {current_speaker}",
"text": " ".join(current_text)
})
current_speaker = word.speaker_tag
current_text = [word.word]
else:
current_text.append(word.word)
if current_text:
utterances.append({
"speaker": f"Speaker {current_speaker}",
"text": " ".join(current_text)
})
return utterances
Audio largo con Google (>1 min)
Para audio mayor a 1 minuto, Google requiere subir el archivo a GCS:
from google.cloud import speech, storage
def transcribe_google_long(
audio_path: str,
gcs_bucket: str,
language: str = "es-ES"
) -> str:
storage_client = storage.Client()
bucket = storage_client.bucket(gcs_bucket)
blob_name = f"audio/{Path(audio_path).name}"
blob = bucket.blob(blob_name)
blob.upload_from_filename(audio_path)
gcs_uri = f"gs://{gcs_bucket}/{blob_name}"
client = speech.SpeechClient()
audio = speech.RecognitionAudio(uri=gcs_uri)
config = speech.RecognitionConfig(
encoding=speech.RecognitionConfig.AudioEncoding.MP3,
sample_rate_hertz=16000,
language_code=language,
enable_automatic_punctuation=True,
)
operation = client.long_running_recognize(config=config, audio=audio)
response = operation.result(timeout=600)
blob.delete()
return " ".join(
result.alternatives[0].transcript
for result in response.results
)
Costos de Google STT
| Modelo | Costo | Notas |
|---|---|---|
| Standard | $0.004/15 seg | Económico |
| Enhanced (phone/video) | $0.009/15 seg | Mayor precisión |
| Medical | $0.016/15 seg | Terminología médica |
| Chirp (latest) | $0.016/15 seg | Mejor calidad general |
AssemblyAI
Setup
pip install assemblyai
export ASSEMBLYAI_API_KEY="your_key"
Transcripción básica
import assemblyai as aai
aai.settings.api_key = os.getenv("ASSEMBLYAI_API_KEY")
def transcribe_assemblyai(audio_path: str, language: str = "es") -> str:
config = aai.TranscriptionConfig(language_code=language)
transcriber = aai.Transcriber()
transcript = transcriber.transcribe(audio_path, config=config)
if transcript.status == aai.TranscriptStatus.error:
raise RuntimeError(f"Error de transcripción: {transcript.error}")
return transcript.text
Diarización de speakers
def transcribe_assemblyai_diarization(audio_path: str) -> list[dict]:
config = aai.TranscriptionConfig(
speaker_labels=True,
language_code="es"
)
transcriber = aai.Transcriber()
transcript = transcriber.transcribe(audio_path, config=config)
return [
{
"speaker": utterance.speaker,
"text": utterance.text,
"start": utterance.start,
"end": utterance.end
}
for utterance in transcript.utterances
]
Detección de PII (Información Personal)
def transcribe_assemblyai_redact_pii(audio_path: str) -> dict:
config = aai.TranscriptionConfig(
redact_pii=True,
redact_pii_policies=[
aai.PIIRedactionPolicy.person_name,
aai.PIIRedactionPolicy.phone_number,
aai.PIIRedactionPolicy.email_address,
aai.PIIRedactionPolicy.credit_card_number,
],
redact_pii_sub=aai.PIISubstitutionPolicy.hash,
)
transcriber = aai.Transcriber()
transcript = transcriber.transcribe(audio_path, config=config)
return {
"text": transcript.text,
"redacted": True
}
Resumen y análisis automático
def transcribe_assemblyai_with_analysis(audio_path: str) -> dict:
config = aai.TranscriptionConfig(
summarization=True,
summary_model=aai.SummarizationModel.informative,
summary_type=aai.SummarizationType.bullets,
sentiment_analysis=True,
auto_chapters=True,
)
transcriber = aai.Transcriber()
transcript = transcriber.transcribe(audio_path, config=config)
return {
"text": transcript.text,
"summary": transcript.summary,
"sentiment": [
{
"text": s.text,
"sentiment": s.sentiment.value,
"confidence": s.confidence
}
for s in (transcript.sentiment_analysis or [])
],
"chapters": [
{
"headline": ch.headline,
"summary": ch.summary,
"start": ch.start,
"end": ch.end
}
for ch in (transcript.chapters or [])
]
}
Costos de AssemblyAI
| Feature | Costo |
|---|---|
| Transcripción base | $0.0065/min (async), $0.015/min (streaming) |
| Speaker diarization | Incluido |
| PII redaction | Incluido |
| Summarization | Incluido |
| Sentiment analysis | Incluido |
Deepgram
Setup
pip install deepgram-sdk
export DEEPGRAM_API_KEY="your_key"
Transcripción básica
from deepgram import DeepgramClient, PrerecordedOptions, FileSource
def transcribe_deepgram(audio_path: str, language: str = "es") -> str:
deepgram = DeepgramClient(os.getenv("DEEPGRAM_API_KEY"))
with open(audio_path, "rb") as f:
buffer_data = f.read()
payload: FileSource = {"buffer": buffer_data}
options = PrerecordedOptions(
model="nova-2",
language=language,
smart_format=True,
punctuate=True,
)
response = deepgram.listen.rest.v("1").transcribe_file(payload, options)
return response.results.channels[0].alternatives[0].transcript
Diarización con Deepgram
def transcribe_deepgram_diarization(audio_path: str) -> list[dict]:
deepgram = DeepgramClient(os.getenv("DEEPGRAM_API_KEY"))
with open(audio_path, "rb") as f:
buffer_data = f.read()
payload: FileSource = {"buffer": buffer_data}
options = PrerecordedOptions(
model="nova-2",
language="es",
smart_format=True,
diarize=True,
utterances=True,
)
response = deepgram.listen.rest.v("1").transcribe_file(payload, options)
return [
{
"speaker": utt.speaker,
"text": utt.transcript,
"start": utt.start,
"end": utt.end
}
for utt in response.results.utterances
]
Costos de Deepgram
| Modelo | Costo | Notas |
|---|---|---|
| Nova-2 (pre-recorded) | $0.0043/min | Mejor relación costo-precisión |
| Nova-2 (streaming) | $0.0059/min | Baja latencia |
| Whisper Cloud | $0.0048/min | Whisper hosted por Deepgram |
Árbol de Decisión
¿Necesitas STT?
│
├── ¿Es transcripción simple sin features extra?
│ ├── Sí → ¿Ya usas OpenAI? → Sí → Whisper
│ │ → No → Deepgram (más barato)
│ └── No → sigue ↓
│
├── ¿Necesitas saber QUIÉN dijo QUÉ (diarización)?
│ ├── Sí → ¿Necesitas también análisis de sentimiento?
│ │ ├── Sí → AssemblyAI
│ │ └── No → Deepgram (más rápido y barato)
│ └── No → sigue ↓
│
├── ¿Necesitas redactar información personal (PII)?
│ ├── Sí → AssemblyAI
│ └── No → sigue ↓
│
├── ¿Necesitas 100+ idiomas?
│ ├── Sí → Google STT (125+) o AssemblyAI (100+)
│ └── No → sigue ↓
│
├── ¿Necesitas streaming en tiempo real?
│ ├── Sí → Deepgram (menor latencia) o Google STT
│ └── No → sigue ↓
│
├── ¿Necesitas traducción automática al inglés?
│ ├── Sí → Whisper (único con traducción integrada)
│ └── No → sigue ↓
│
└── ¿Prioridad es menor costo?
├── Sí → Deepgram ($0.0043/min)
└── No → Whisper (ecosistema OpenAI integrado)
Función Unificada Multi-Proveedor
from enum import Enum
class STTProvider(Enum):
WHISPER = "whisper"
GOOGLE = "google"
ASSEMBLYAI = "assemblyai"
DEEPGRAM = "deepgram"
def transcribe_unified(
audio_path: str,
provider: STTProvider = STTProvider.WHISPER,
language: str = "es",
diarize: bool = False
) -> dict:
if provider == STTProvider.WHISPER:
client = OpenAI()
with open(audio_path, "rb") as f:
response = client.audio.transcriptions.create(
model="whisper-1",
file=f,
language=language
)
return {"text": response.text, "provider": "whisper"}
elif provider == STTProvider.ASSEMBLYAI:
config = aai.TranscriptionConfig(
language_code=language,
speaker_labels=diarize
)
transcript = aai.Transcriber().transcribe(audio_path, config=config)
result = {"text": transcript.text, "provider": "assemblyai"}
if diarize and transcript.utterances:
result["utterances"] = [
{"speaker": u.speaker, "text": u.text}
for u in transcript.utterances
]
return result
elif provider == STTProvider.DEEPGRAM:
dg = DeepgramClient(os.getenv("DEEPGRAM_API_KEY"))
with open(audio_path, "rb") as f:
payload = {"buffer": f.read()}
options = PrerecordedOptions(
model="nova-2",
language=language,
diarize=diarize
)
response = dg.listen.rest.v("1").transcribe_file(payload, options)
return {
"text": response.results.channels[0].alternatives[0].transcript,
"provider": "deepgram"
}
elif provider == STTProvider.GOOGLE:
return {
"text": transcribe_google(audio_path, language=f"{language}-ES"),
"provider": "google"
}
raise ValueError(f"Proveedor no soportado: {provider}")
Transcripción con Fallback Multi-Proveedor
def transcribe_with_fallback(
audio_path: str,
providers: list[STTProvider] = None,
language: str = "es"
) -> dict:
if providers is None:
providers = [STTProvider.WHISPER, STTProvider.DEEPGRAM, STTProvider.ASSEMBLYAI]
errors = []
for provider in providers:
try:
result = transcribe_unified(audio_path, provider=provider, language=language)
result["fallback_used"] = provider != providers[0]
result["attempts"] = len(errors) + 1
return result
except Exception as e:
errors.append({"provider": provider.value, "error": str(e)})
return {
"error": "Todos los proveedores fallaron",
"details": errors
}
Troubleshooting
Problema 1: Google STT requiere formato LINEAR16
Síntoma: Invalid audio format con Google.
Solución: Convertir a WAV 16kHz mono:
def prepare_for_google(input_path: str) -> str:
output_path = Path(input_path).with_suffix(".wav")
audio = AudioSegment.from_file(input_path)
audio = audio.set_frame_rate(16000).set_channels(1).set_sample_width(2)
audio.export(str(output_path), format="wav")
return str(output_path)
Problema 2: AssemblyAI timeout en archivos largos
Síntoma: La transcripción tarda más de lo esperado.
Solución: AssemblyAI procesa asincrónicamente. El SDK espera por defecto, pero puedes configurar polling:
config = aai.TranscriptionConfig(language_code="es")
transcriber = aai.Transcriber()
transcript = transcriber.transcribe(
audio_path,
config=config,
)
Problema 3: Deepgram no reconoce idioma
Síntoma: Transcripción en idioma incorrecto.
Solución: Especificar idioma explícitamente con el código correcto:
options = PrerecordedOptions(
model="nova-2",
language="es",
detect_language=False
)
Ejercicios
Ejercicio 1: Benchmark de proveedores
Crea una función que transcriba el mismo audio con Whisper y un segundo proveedor, y compare los resultados.
Ver solución
import time
def benchmark_providers(
audio_path: str,
providers: list[STTProvider] = None
) -> list[dict]:
if providers is None:
providers = [STTProvider.WHISPER, STTProvider.DEEPGRAM]
results = []
for provider in providers:
start = time.time()
try:
result = transcribe_unified(audio_path, provider=provider)
elapsed = time.time() - start
results.append({
"provider": provider.value,
"text": result["text"],
"latency_s": round(elapsed, 2),
"word_count": len(result["text"].split()),
"char_count": len(result["text"]),
"status": "success"
})
except Exception as e:
results.append({
"provider": provider.value,
"status": "error",
"error": str(e),
"latency_s": round(time.time() - start, 2)
})
return results
Ejercicio 2: Transcripción con diarización formateada
Usa AssemblyAI o Deepgram para transcribir una conversación y formatear el output como un script de diálogo.
Ver solución
def transcribe_as_dialogue(audio_path: str) -> str:
config = aai.TranscriptionConfig(
speaker_labels=True,
language_code="es"
)
transcript = aai.Transcriber().transcribe(audio_path, config=config)
if not transcript.utterances:
return transcript.text
dialogue_lines = []
for utterance in transcript.utterances:
start_min = utterance.start // 60000
start_sec = (utterance.start % 60000) // 1000
timestamp = f"[{start_min:02d}:{start_sec:02d}]"
dialogue_lines.append(
f"{timestamp} Speaker {utterance.speaker}: {utterance.text}"
)
return "\n\n".join(dialogue_lines)
Ejercicio 3: Selector automático de proveedor
Crea una función que analice los requisitos del usuario y recomiende el mejor proveedor.
Ver solución
def recommend_provider(
needs_diarization: bool = False,
needs_pii_redaction: bool = False,
needs_streaming: bool = False,
needs_translation: bool = False,
budget_priority: bool = False,
language: str = "es"
) -> dict:
scores = {
"whisper": 0,
"google": 0,
"assemblyai": 0,
"deepgram": 0
}
if needs_translation:
scores["whisper"] += 10
if needs_diarization:
scores["assemblyai"] += 5
scores["deepgram"] += 5
scores["google"] += 3
if needs_pii_redaction:
scores["assemblyai"] += 10
if needs_streaming:
scores["deepgram"] += 5
scores["google"] += 5
if budget_priority:
scores["deepgram"] += 5
scores["google"] += 2
if not any([needs_diarization, needs_pii_redaction, needs_streaming, needs_translation]):
scores["whisper"] += 5
recommended = max(scores, key=scores.get)
reasons = {
"whisper": "Mejor opción general y única con traducción integrada",
"google": "Mejor para streaming y mayor cobertura de idiomas",
"assemblyai": "Mejor para diarización, PII, y análisis avanzado",
"deepgram": "Mejor relación costo-rendimiento y menor latencia"
}
return {
"recommended": recommended,
"reason": reasons[recommended],
"scores": scores
}
Resumen
- 4 proveedores principales de STT: Whisper, Google STT, AssemblyAI, Deepgram.
- Whisper es la opción por defecto: simple, buena calidad, ecosistema OpenAI, única con traducción integrada.
- Google STT destaca en idiomas (125+), streaming, e integración GCP.
- AssemblyAI es el líder en features avanzados: diarización, PII redaction, análisis de sentimiento, resúmenes automáticos.
- Deepgram tiene la menor latencia y el mejor precio ($0.0043/min).
- Usa la función unificada para cambiar de proveedor con un parámetro.
- Implementa fallback multi-proveedor para resiliencia en producción.
Recursos Adicionales
- Google Speech-to-Text — Documentación oficial de Google
- AssemblyAI Docs — Documentación completa de AssemblyAI
- Deepgram Docs — Documentación de Deepgram
- AssemblyAI Python SDK — SDK oficial
- Deepgram Python SDK — SDK oficial