Sintetizar voz en documentos usando Ruby
Convertir documentos de texto en voz puede mejorar la accesibilidad y ofrecer a los usuarios nuevas formas de interactuar con tu contenido. En este DevTip, exploramos cómo sintetizar voz en documentos usando Ruby y la biblioteca Google Cloud Text-to-Speech, con ejemplos prácticos y buenas prácticas.
Configurar Google Cloud Text-to-Speech
Google Cloud Text-to-Speech ofrece voces de alta calidad, amplio soporte de idiomas e integración fluida. Usa Ruby 3.2 o una versión más reciente, habilita la API de Text-to-Speech y la facturación en tu proyecto de Google Cloud, y luego agrega estas dependencias a tu Gemfile:
gem 'google-cloud-text_to_speech', '2.2.0'
gem 'nokogiri', '1.19.4'
Instala el bundle y haz commit de su lockfile para que el grafo de dependencias probado sea reproducible:
bundle install
Usa Application Default Credentials. Para el desarrollo local, la CLI de Google Cloud puede crear credenciales de desarrollo:
gcloud auth application-default login
En cargas de trabajo alojadas, prefiere una identidad de servicio adjunta en lugar de una clave descargada de larga duración. Nunca hagas commit de las credenciales ni incluyas el texto de los documentos en los registros de diagnóstico. Las solicitudes de síntesis reales usan la cuenta configurada y pueden generar cargos.
Script básico de narración de documentos
Guarda esta clase como document_narrator.rb. Admite documentos de texto plano y HTML en
UTF-8, limita el tamaño del archivo de origen, elimina los elementos HTML que no son contenido y
compara el texto extraído con el límite de contenido de 5.000 bytes de la API de síntesis
estándar. El límite es en bytes, no en caracteres.
require "google/cloud/text_to_speech"
require "nokogiri"
require "tempfile"
class DocumentNarrator
def initialize(client: Google::Cloud::TextToSpeech.text_to_speech)
@client = client
end
def narrate_text(text, output_path, language_code: "en-US")
unless text.is_a?(String) && text.encoding == Encoding::UTF_8 && text.valid_encoding?
raise ArgumentError, "Text must be valid UTF-8"
end
raise ArgumentError, "Text cannot be empty" if text.strip.empty?
raise ArgumentError, "Text exceeds 5,000 bytes" if text.bytesize > 5000
if File.exist?(output_path) || File.symlink?(output_path)
raise ArgumentError, "Output must not exist"
end
input = { text: text }
voice = { language_code: language_code, ssml_gender: :FEMALE }
audio_config = { audio_encoding: :MP3, speaking_rate: 1.0, pitch: 0.0 }
# Create the candidate on the destination filesystem before making a paid request.
Tempfile.create([".narration-", ".mp3"], File.dirname(output_path)) do |file|
file.binmode
response = @client.synthesize_speech(
input: input, voice: voice, audio_config: audio_config
)
raise IOError, "Synthesis returned empty audio" if response.audio_content.empty?
file.write(response.audio_content)
file.flush
# Publication must not replace a file created while synthesis was running.
File.link(file.path, output_path)
end
end
def narrate_file(input_path, output_path, language_code: "en-US")
text = extract_text(input_path)
narrate_text(text, output_path, language_code: language_code)
end
private
def extract_text(file_path)
raise ArgumentError, "Input must be a regular file" unless File.file?(file_path)
source = File.binread(file_path, 1024 * 1024 + 1)
raise ArgumentError, "Source exceeds 1 MiB" if source.bytesize > 1024 * 1024
source.force_encoding(Encoding::UTF_8)
raise ArgumentError, "Source must be valid UTF-8" unless source.valid_encoding?
case File.extname(file_path).downcase
when ".txt"
source
when ".html", ".htm"
doc = Nokogiri::HTML5(source)
doc.css("script, style, template, noscript").remove
doc.at_css("body").xpath(".//text()").map(&:text).join(" ").strip
else
raise ArgumentError, "Unsupported file format"
end
end
end
Funciones avanzadas y manejo de errores
Mantén una sola implementación de DocumentNarrator y crea una pequeña CLI a su
alrededor. Guarda lo siguiente como narrate.rb en el mismo directorio. Sale con
un estado de error ante entradas no válidas, fallos del proveedor o colisiones de salida, sin
imprimir errores del proveedor que contengan credenciales ni el texto del documento.
require_relative "document_narrator"
unless (2..3).cover?(ARGV.length)
abort "Usage: bundle exec ruby narrate.rb <input.txt|input.html> <new-output.mp3> [language-code]"
end
begin
DocumentNarrator.new.narrate_file(ARGV[0], ARGV[1], language_code: ARGV[2] || "en-US")
puts "Narration saved"
rescue StandardError
warn "Narration failed. Check the input, destination, credentials, and provider configuration."
exit 1
end
Ejecuta bundle exec ruby narrate.rb document.html narration.mp3 en-US. Selecciona un código de idioma que coincida con tu documento
y una configuración de voz compatible con el proveedor. El ajuste de voz no traduce la entrada. La
extracción de HTML aquí es deliberadamente simple; no reproduce el diseño del navegador, la
visibilidad de CSS ni el orden de lectura accesible. Revisa el contenido extraído antes de la
narración.
Buenas prácticas para la síntesis de voz
Ten en cuenta estas buenas prácticas al implementar la funcionalidad de texto a voz:
-
Preprocesamiento del texto:
- Divide los textos largos en los límites de las oraciones y verifica el tamaño en bytes UTF-8 de cada solicitud.
- Sigue los límites de contenido actuales.
- Elimina los espacios en blanco innecesarios y los caracteres especiales.
- Expande las abreviaturas y los números para mejorar la pronunciación.
-
Configuración de voz:
- Selecciona el idioma y la voz adecuados para tu contenido.
- Ajusta la velocidad de habla y el tono para obtener un audio de sonido natural.
- Aprovecha el marcado SSML para un control detallado, incluidas las pausas y el énfasis.
-
Optimización del rendimiento:
- Implementa una caché para reducir las llamadas redundantes a la API.
- Usa el procesamiento por lotes en documentos grandes para mejorar la eficiencia.
- Usa el flujo de trabajo de audio largo documentado por el proveedor para el material que supere los límites síncronos.
-
Consideraciones de seguridad:
- Almacena y gestiona tus credenciales de API de forma segura.
- Implementa limitación de tasa de solicitudes y validación de entradas para evitar abusos.
- Monitorea el uso de la API y configura alertas para controlar los costos.
Soluciones alternativas
Aunque Google Cloud Text-to-Speech es una solución robusta, puedes considerar otras opciones:
- El SDK de Ruby de Amazon Polly ofrece un cliente dedicado. Sus reglas de voz, motor y límite de contenido difieren de las de Google.
- La API REST de Text-to-Speech de Azure se puede llamar desde Ruby. No supongas que un SDK para otro lenguaje tiene un equivalente en Ruby; sigue los contratos de autenticación REST, SSML, formato de salida, tiempo de espera y errores HTTP.
- Los modelos de voz autoalojados permiten mantener el procesamiento de documentos en tu propia infraestructura. Verifica la licencia y el mantenimiento del modelo, asigna recursos de trabajo y usa la API HTTP documentada del servidor que elijas, en lugar de suponer que todos los servidores de TTS aceptan la misma carga útil JSON.
Conclusión
Las soluciones modernas de texto a voz te permiten crear contenido de audio atractivo y accesible a partir de documentos usando Ruby. Google Cloud Text-to-Speech, junto con otras alternativas, ofrece voces de alta calidad y soporte para varios idiomas. Para una síntesis de voz gestionada, consulta el Text to Speech Robot de Transloadit.
