Detección automática del idioma hablado con cURL y código abierto
Detecta el idioma hablado directamente a partir del audio con un modelo multilingüe de Whisper, servido localmente por whisper.cpp. cURL envía la grabación a su endpoint HTTP, que devuelve un idioma candidato y una transcripción para contrastarla con la grabación. Una vez descargados el software y el modelo, la inferencia se ejecuta en tu computadora sin una cuenta ni una API en la nube.
Requisitos del sistema
Usa Linux con Bash, un shell POSIX (sh), un compilador C++17, Make,
CMake, cURL, FFmpeg, Python 3 y los comandos habituales tar,
gzip, dirname, grep,
mktemp y rm. Primero instala las herramientas
que falten mediante el gestor de paquetes de tu distribución. Python lee la respuesta JSON usando
solo su biblioteca estándar.
Esta guía para CPU fija la versión v1.8.7 de whisper.cpp y está dirigida a un
entorno Debian 13 con Bash 5.2, GCC 14.2, Make 4.4, CMake 3.31, cURL 8.14, FFmpeg 7.1 y Python 3.12.
Otras plataformas y cadenas de herramientas anteriores quedan fuera del alcance de esta guía.
El modelo multilingüe tiny ocupa unos 75 MiB en disco. Reserva espacio
adicional en disco para el código fuente y la compilación, y varios cientos de megabytes de memoria
libre para la inferencia.
Instalación
Desde un directorio de trabajo nuevo, descarga la versión fijada, compila su servidor y descarga el
modelo multilingüe. Elige tiny en lugar del modelo
tiny.en, que solo admite inglés. Los paréntesis mantienen las opciones del
shell y los cambios de directorio dentro de un subshell:
(
set -e
for tool in curl tar gzip cmake make c++ ffmpeg python3 sh dirname grep mktemp rm; do
command -v "$tool" >/dev/null || { printf 'Missing tool: %s\n' "$tool" >&2; exit 1; }
done
if [ -e whisper-v1.8.7.tar.gz ] || [ -e whisper.cpp-1.8.7 ]; then
printf 'Use a fresh directory; existing download or source found.\n' >&2
exit 1
fi
curl -q --fail --location --output whisper-v1.8.7.tar.gz \
https://github.com/ggml-org/whisper.cpp/archive/refs/tags/v1.8.7.tar.gz
tar -xzf whisper-v1.8.7.tar.gz
cd whisper.cpp-1.8.7
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DGGML_METAL=OFF -DWHISPER_BUILD_SERVER=ON
cmake --build build --config Release --target whisper-server -j 4
sh ./models/download-ggml-model.sh tiny
)
Configuración del servidor de transcripción
Una vez que la instalación se complete correctamente, inicia el servidor HTTP desde el mismo directorio de trabajo:
(
cd whisper.cpp-1.8.7 &&
./build/bin/whisper-server --model models/ggml-tiny.bin \
--host 127.0.0.1 --port 8080 --language auto --no-gpu
)
Espera el mensaje que indica que el servidor está escuchando y deja esta terminal abierta. El servidor de ejemplo no tiene autenticación, así que mantenlo vinculado a la interfaz de loopback y usa grabaciones locales de confianza. Detén el servidor con Ctrl+C cuando termines.
Transcripción de audio con cURL
En una segunda terminal, trabaja en un directorio que contenga una grabación de voz llamada
input.mp3. Usa un fragmento de confianza que comience con voz clara en un solo
idioma. Conviértelo a WAV PCM mono de 16 kHz y 16 bits. El bloque rechaza un
audio.wav existente antes de la conversión, y -n
también impide que FFmpeg lo sobrescriba. Elige un directorio nuevo para cada grabación;
FFmpeg 7.1 puede informar que se negó a sobrescribir el archivo y aun así devolver un estado cero,
por lo que la comprobación explícita es importante.
(
if [ -e audio.wav ]; then
printf 'Existing audio.wav; use a fresh directory.\n' >&2
exit 1
fi
ffmpeg -nostdin -n -i input.mp3 -vn -ar 16000 -ac 1 -c:a pcm_s16le audio.wav
)
Escucha audio.wav y comprueba que contiene la voz prevista antes de subirlo.
FFmpeg puede recuperar partes de grabaciones dañadas y aun así indicar que la operación se completó
correctamente; la conversión por sí sola no demuestra que el archivo de entrada estuviera intacto.
Detección del idioma
Guarda esto como read_language.py junto a audio.wav. Lee el idioma
inferido a partir del audio e imprime la transcripción para revisarla. Rechaza el texto vacío y el
texto compuesto únicamente por marcadores entre corchetes que indican sonidos no verbales, incluidos
los resultados repetidos de [BLANK_AUDIO]:
import json
import re
import sys
def read_language(path):
with open(path, encoding="utf-8") as handle:
data = json.load(handle)
if not isinstance(data, dict):
raise ValueError("Expected a JSON object")
language = data.get("language")
text = data.get("text")
if not isinstance(language, str) or not language.strip():
raise ValueError("Response has no detected language")
if not isinstance(text, str) or not re.sub(r"\[[^\]]*\]", "", text).strip():
raise ValueError("No speech transcript; language result is inconclusive")
return language.strip(), " ".join(text.split())
def main():
if len(sys.argv) != 2:
raise ValueError("Usage: python3 read_language.py response.json")
language, text = read_language(sys.argv[1])
print(f"Language candidate: {language}")
print(f"Transcript: {text}")
if __name__ == "__main__":
try:
main()
except (OSError, ValueError) as error:
print(f"Cannot read language result: {error}", file=sys.stderr)
sys.exit(1)
Una vez que la conversión se complete correctamente y hayas guardado el programa lector, pega este bloque de solicitud. Asigna a cada solicitud su propio archivo temporal de respuesta y lo lee solo si cURL se ejecuta correctamente. El archivo se elimina cuando termina el bloque, incluso si falla, por lo que una respuesta anterior no puede convertirse en el resultado de esta solicitud. La solicitud permite 60 segundos en total, incluidas la subida y la inferencia; una CPU ocupada o una grabación más larga pueden superar ese plazo. Auméntalo de forma deliberada si es necesario.
(
set -e
response=$(mktemp ./language-response.XXXXXX)
trap 'rm -f "$response"' EXIT
curl -q --fail --silent --show-error --max-time 60 \
http://127.0.0.1:8080/inference \
--form 'file=@audio.wav;type=audio/wav' \
--form 'language=auto' \
--form 'response_format=verbose_json' \
--form 'no_language_probabilities=true' \
--output "$response" &&
python3 read_language.py "$response"
)
language=auto solicita la detección del idioma. La
respuesta verbose_json del servidor incluye
language y text; json
sin más omite el idioma. La traducción está desactivada y no_language_probabilities=true omite una
segunda pasada opcional de detección.
Para una grabación de voz en inglés, espera Language candidate: english;
para español, el nombre que usa el servidor es spanish. Son nombres de idiomas,
no códigos ISO. Compara la transcripción impresa con lo que escuchas. La comprobación de marcadores
es solo una comprobación básica de coherencia:
Whisper puede inventar palabras,
y una transcripción no vacía no confirma la presencia de voz ni que el idioma sea correcto.
Optimización del rendimiento
Mantén el servidor en ejecución entre solicitudes para que pueda reutilizar el modelo cargado.
tiny mantiene pequeña la descarga para experimentos locales, pero su
transcripción puede contener errores considerables. Pruébalo con grabaciones conocidas antes de
usar el idioma candidato para dirigir otros trabajos.
En esta versión, la detección automática del idioma comienza en la posición cero y usa el intervalo inicial de la grabación, de aproximadamente 30 segundos, en lugar de volver a clasificar cada segmento posterior. Por tanto, un silencio prolongado al inicio puede producir un resultado de idioma engañoso incluso si hay voz más adelante. Usa un fragmento que comience con voz; si hay cambios de idioma, evalúa fragmentos separados. Un único resultado para todo el archivo no es una cronología de todos los idiomas hablados.
Manejo de errores
Si la instalación falla, inspecciona sus archivos parciales, soluciona la falta del requisito previo
y vuelve a intentarlo en un directorio de trabajo nuevo. Las descargas existentes y el código fuente
extraído se dejan intactos deliberadamente. Si la conversión falla, inspecciona cualquier archivo
audio.wav parcial antes de eliminarlo o elegir otro directorio.
cURL termina con un estado distinto de cero si hay errores HTTP, fallos de conexión o se agota el
tiempo de espera. En ese caso, el bloque de solicitud omite el programa lector y elimina su respuesta
temporal. Conserva el archivo audio.wav convertido correctamente y vuelve a
ejecutar solo el bloque de solicitud tras resolver el fallo. Un JSON no válido, campos ausentes,
transcripciones vacías o compuestas solo por marcadores y archivos que no se pueden leer hacen que
el programa lector termine con el estado 1.
Si el inicio falla, comprueba la ruta del modelo y si el puerto 8080 ya está ocupado. Si una subida falla, inspecciona la terminal del servidor local y confirma que la conversión a WAV se completó correctamente. No consideres una clasificación fiable el idioma que se haya estimado a partir de silencio, música o una grabación de voz muy breve.
Para transcripción alojada en un flujo de trabajo de procesamiento de archivos, consulta nuestro Speech Robot.
