Fundamentos del modelo
Fish Speech 1.5 es un sistema de síntesis de voz (TTS) impulsado por la arquitectura DualAR (transformer autorregresivo dual), que permite generar locuciones con una naturalidad cercana a la voz humana sin depender de reglas fonéticas tradicionales. Al procesar el texto directamente, el modelo mejora su capacidad de generalización y reduce los artefactos mecánicos típicos de los sintetizadores convencionales.
En el contexto de la producción de audiolibros, ofrece varias ventajas prácticas: salida de audio de alta fidelidad, imitación de timbres a partir de muestras de referencia, generación rápida (aproximadamente 18 tokens por segundo) y una interfaz web accesible que no requiere conocimientos avanzados de programación.
Requisitos e instalación
Para ejecutar el modelo de forma local o en un servidor se recomienda:
- Una GPU NVIDIA con al menos 8 GB de VRAM.
- Docker y docker-compose instalados, o un entorno Python 3.10+.
- Conexión a internet estable para la descarga inicial de pesos.
La forma más directa de desplegar el servicio es mediante la imagen oficial:
docker run --gpus all -p 7860:7860 -p 8080:8080 \
-v ./models:/app/models \
fishaudio/fish-speech:1.5
Tras el arranque, los endpoints disponibles serán:
- Interfaz web:
http://localhost:7860 - API REST:
http://localhost:8080/v1/tts
Síntesis básica desde la API
El siguiente cliente muestra cómo convertir un fragmento de texto en audio usando la API. Se utiliza httpx en lugar de requests para aprovechar el manejo asíncrono y se renombran las variables para mayor claridad:
import httpx
BASE_URL = "http://localhost:8080/v1/tts"
def sintetizar(fragmento, ruta_salida="salida.wav"):
carga = {
"text": fragmento,
"format": "wav",
"max_new_tokens": 1024,
"temperature": 0.7,
"top_p": 0.8
}
respuesta = httpx.post(BASE_URL, json=carga, timeout=120)
respuesta.raise_for_status()
with open(ruta_salida, "wb") as archivo:
archivo.write(respuesta.content)
return ruta_salida
sintetizar("El viento movía las hojas secas por el sendero.")
Clonación de voz con referencia
Para mantener una identidad sonora coherente a lo largo del audiolibro, se puede proporcionar una muestra de referencia. El modelo infiere el timbre y la entonación a partir del clip, siempre que su transcripción coincida exactamente con el audio.
import base64
import httpx
def codificar_audio(ruta):
with open(ruta, "rb") as archivo:
return base64.b64encode(archivo.read()).decode("utf-8")
def clonar_voz(texto, ref_audio, ref_texto, destino="narracion.wav"):
datos = {
"text": texto,
"references": [{
"audio_data": codificar_audio(ref_audio),
"text": ref_texto
}],
"max_new_tokens": 1024,
"temperature": 0.7
}
respuesta = httpx.post("http://localhost:8080/v1/tts", json=datos, timeout=120)
respuesta.raise_for_status()
with open(destino, "wb") as archivo:
archivo.write(respuesta.content)
clonar_voz(
"La montaña guardaba un secreto milenario.",
"muestras/narrador_01.wav",
"La montaña guardaba un secreto milenario."
)
Consejos para elegir la muestra de referencia:
- Duración entre 5 y 10 segundos, con buena calidad de grabación.
- Ausencia de ruido de fondo, música o reverberación.
- Correspondencia exacta entre el audio y la transcripción.
- Uso de la misma referencia en todos los capítulos para preservar la coherencia.
Procesamiento por lotes
Los audiolibros suelen dividirse en capítulos. El siguiente script recorre un directorio con archivos de texto y genera un archivo de audio por cada uno, nombrando el resultado con el mismo identificador del capítulo:
import glob
import httpx
SERVICIO = "http://localhost:8080/v1/tts"
def convertir_capitulo(ruta_texto):
nombre = ruta_texto.replace(".txt", "")
with open(ruta_texto, "r", encoding="utf-8") as f:
contenido = f.read().strip()
respuesta = httpx.post(
SERVICIO,
json={
"text": contenido,
"format": "mp3",
"max_new_tokens": 2048,
"temperature": 0.75,
"top_p": 0.85
},
timeout=180
)
respuesta.raise_for_status()
destino = f"{nombre}.mp3"
with open(destino, "wb") as f:
f.write(respuesta.content)
print(f"Generado: {destino}")
for archivo in sorted(glob.glob("capitulos/*.txt")):
convertir_capitulo(archivo)
Automatización con división de texto largo
Cuando un capítulo supera el límite de tokens del modelo, conviene dividir el texto en bloques manejables y luego concatenar los audios resultantes. La clase siguiente encapsula ese flujo:
import os
import httpx
from pathlib import Path
class FabricaAudiolibros:
def __init__(self, endpoint="http://localhost:8080/v1/tts"):
self.endpoint = endpoint
self.cliente = httpx.Client(timeout=180)
def dividir(self, texto, limite=800):
oraciones = [o.strip() for o in texto.replace("?", ".").replace("!", ".").split(".") if o.strip()]
bloques, bloque_actual = [], ""
for oracion in oraciones:
if len(bloque_actual) + len(oracion) > limite:
bloques.append(bloque_actual.strip())
bloque_actual = oracion + ". "
else:
bloque_actual += oracion + ". "
if bloque_actual.strip():
bloques.append(bloque_actual.strip())
return bloques
def generar_audio(self, texto, opciones=None):
configuracion = {
"text": texto,
"format": "mp3",
"max_new_tokens": 512,
"temperature": 0.7,
"top_p": 0.8
}
if opciones:
configuracion.update(opciones)
return self.cliente.post(self.endpoint, json=configuracion).content
def producir(self, ruta_entrada, carpeta_salida="audios"):
os.makedirs(carpeta_salida, exist_ok=True)
nombre = Path(ruta_entrada).stem
with open(ruta_entrada, "r", encoding="utf-8") as f:
texto_completo = f.read()
segmentos = self.dividir(texto_completo)
audios = [self.generar_audio(s) for s in segmentos]
ruta_final = os.path.join(carpeta_salida, f"{nombre}.mp3")
with open(ruta_final, "wb") as f:
for audio in audios:
f.write(audio)
print(f"Audiolibro listo: {ruta_final}")
# Uso
FabricaAudiolibros().producir("capitulos/capitulo_01.txt")
Ajuste de parámetros
La calidad de la narración depende en gran medida de tres valores principales:
| Tipo de contenido | Temperature | Top-P | Repetition penalty |
|---|---|---|---|
| Narrativa / novela | 0.70 - 0.85 | 0.80 - 0.95 | 1.0 - 1.1 |
| Documentación técnica | 0.55 - 0.70 | 0.70 - 0.80 | 1.2 - 1.4 |
| Literatura infantil | 0.80 - 0.95 | 0.85 - 0.98 | 1.0 - 1.1 |
Preparación de texto y postproducción
Un buen resultado no depende solo del modelo. La preparación previa del texto y el tratamiento posterior del audio son igual de importantes.
- Limpieza del texto: eliminar caracteres especiales innecesarios, normalizar espacios y revisar que los diálogos estén bien delimitados.
- Control de ritmo: usar puntuación de manera coherente; las comas generan pausas breves y los puntos, pausas más marcadas.
- Segmentación: evitar párrafos excesivamente largos para prevenir cortes o repeticiones.
- Masterización: normalizar el volumen entre capítulos, aplicar reducción de ruido ligera y, si se desea, añadir música de fondo con licencia adecuada.
Flujo de trabajo recomendado
- Preparación: dividir el libro en capítulos, limpiar el texto y definir el estilo narrativo.
- Prueba inicial: sintetizar un único párrafo con diferentes configuraciones hasta obtener el timbre y ritmo deseados.
- Generación masiva: procesar todos los capítulos de forma automática, conservando el mismo audio de referencia.
- Revisión: escuchar fragmentos de cada capítulo para detectar repeticiones, errores de pronunciación o cambios de tono.
- Publicación: exportar a formato final, añadir metadatos (título, autor, capítulos) y empaquetar para distribución.