Crear un plugin de lector de pantalla con el Robot /text/speak

Nota histórica sobre precios: los precios y las asignaciones que aparecen abajo describen la oferta vigente cuando se publicó este artículo. Consulta los precios actuales para ver los planes de hoy.
Hace poco hablamos de una actualización de uno de nuestros queridos Robots de IA, y hoy aprovechamos ese impulso para sumar otro hermano a la familia de Robots de IA. ¡Saluda a nuestro nuevo Robot /text/speak! Al igual que el último Robot de IA del que hablamos, el Robot /speech/transcribe, la funcionalidad clave de este Robot gira en torno al habla. Pero en lugar de escribir texto a partir del habla, te permite procesar texto a voz (TTS).
Como se trata de una incorporación muy emocionante, mi compañero Joseph y yo decidimos que ambos crearíamos un proyecto con este Robot en el centro del diseño y luego escribiríamos un blog sobre nuestro proceso de diseño para mostrar la versatilidad de este Robot. Así que si te gusta este blog, ¡no te pierdas el de Joseph, que llegará pronto!
Para mi proyecto, voy a crear un lector de pantalla que se puede integrar fácilmente en tu sitio web para convertir en voz todo el texto de una página con solo pulsar un botón.
Este es un experimento de texto a voz, no un sustituto de un lector de pantalla asistivo con soporte de navegación e interacción. Cuando pulsas el botón, la demo envía a Transloadit el texto del tutorial que aparece abajo, excluyendo los ejemplos de código. El audio sigue el idioma que se muestra en el menú de idiomas del sitio; usa los controles de audio para reproducir el resultado. En cambio, el ejemplo independiente de abajo selecciona su voz a partir del atributo de idioma de su primer bloque de texto marcado.

Requisitos previos
Antes de empezar, necesitas configurar una carpeta de sitio web estándar. Crea los siguientes
archivos: index.html, index.js y, opcionalmente, style.css. Para no recargar este blog,
solo hablaremos del contenido del archivo js y de dónde conectarlo con nuestro archivo HTML. Aun
así, si quieres copiar el contenido completo de nuestros otros archivos, junto con el GIF de
animación de carga que se usa más adelante en esta demostración, puedes consultar el siguiente
repositorio.
HTML
Empecemos configurando el elemento HTML de nuestra aplicación. Como se indicó arriba, no hablaremos de cómo crear una página web, solo de los elementos necesarios para conectar con nuestro archivo JavaScript. Así que crea un archivo HTML básico con los siguientes elementos dentro de la etiqueta body:
<button id="button_event" onclick="runScript()">Generate</button>
<p id="status" role="status"></p>
<div data-screenreaderlanguage="en-US">
<p>Sample Text</p>
</div>
<script src="index.js"></script>
Esto configura el elemento de interfaz de nuestra aplicación. El botón que hemos creado lanza nuestra solicitud de Assembly /text/speak usando el texto contenido dentro de nuestra etiqueta div. Para referenciar esta etiqueta div, le asignamos un valor de dataset que el desarrollador de back-end también usa para seleccionar el idioma de destino especificado del Robot según las opciones que se detallan en nuestra documentación. Por último, enlazamos nuestro archivo JavaScript al final del body.
Para interactuar con la API de Transloadit, usaremos Robodog, una versión reducida de nuestra herramienta gratuita de subida de archivos, Uppy.
Nota: Robodog está en desuso. Para nuevas integraciones, usa el plugin de Transloadit de Uppy (la interfaz Dashboard o una interfaz personalizada).
Dentro del header de tu página HTML, coloca la siguiente línea:
<script src="https://releases.transloadit.com/uppy/robodog/v1.10.7/robodog.min.js"></script>
JavaScript
Combina los siguientes fragmentos de JavaScript en index.js, en orden. Los fragmentos dividen un
mismo programa entre las explicaciones; no son módulos independientes. El bundle histórico de
Robodog está fijado abajo; la demo en vivo de arriba usa el plugin mantenido de Transloadit para
Uppy.
Usa únicamente texto público que los usuarios hayan aceptado enviar. Para producción, crea en tu servidor parámetros firmados de corta duración usando Signature Authentication, limita las Instructions permitidas y el tamaño de la entrada, y nunca expongas un Auth Secret en el código del navegador.
Obtener nuestro texto
Ya en nuestro archivo JavaScript, podemos empezar a escribir el programa. Lo primero que debemos hacer es analizar el texto legible de nuestro sitio web y asignarlo a una variable para usarlo más adelante.
// …
const result = document.querySelectorAll('[data-screenreaderlanguage]')
const textArray = []
for (const element of result) textArray.push(element.innerText)
const language = document.querySelector('[data-screenreaderlanguage]').dataset.screenreaderlanguage
// …
En este código, usamos el método Document.querySelectorAll() para crear un array que contiene todos los
elementos de información asociados al dataset screenreaderlanguage. Usamos un dataset para que, si
queremos excluir algún texto legible de la salida de nuestro lector de pantalla (como el texto
dentro de una etiqueta <code>), podamos cerrar nuestra etiqueta div antes de ese texto no
deseado y luego crear una nueva etiqueta div bajo el mismo dataset donde queremos que nuestro
lector de pantalla siga procesando texto.
Con esa información base recopilada, todavía necesitamos extraer el texto legible de los datos
recogidos. Así que primero inicializamos una variable de tipo array para almacenar nuestro texto
legible, antes de usar el bucle for...of para visitar cada elemento seleccionado y
guardar su valor innerText.
Para cerrar esta sección, definimos una variable de idioma para que nuestro
Robot /text/speak conozca el
idioma de destino más adelante en el programa. Para ello, declaramos nuestra variable de idioma con
el valor screenreaderlanguage de nuestro dataset.
Convertir para Uppy
Con nuestros datos listos, necesitamos adaptarlos para que nuestra instancia de Robodog pueda manejarlos.
// …
const mytextfile = new File([textArray.join('\n\n')], 'mytextfile.txt', { type: 'text/plain' })
// …
Esto crea un archivo de texto con saltos de párrafo entre los bloques de texto recopilados. Marca solo contenido en el mismo idioma; el ejemplo lee el idioma del primer bloque marcado.
Animación de carga
Para que los usuarios sepan que hay un procesamiento en curso, queremos que se reproduzca una animación de carga mientras nuestro Robot /text/speak procesa. Para ello, necesitamos tener algunas variables de marcador de posición listas para reemplazar nuestro elemento de botón por un GIF. Luego, si el procesamiento del texto tiene éxito, reemplazamos ese elemento GIF por un reproductor de audio.
// …
const buttonEl = document.getElementById('button_event')
const statusEl = document.getElementById('status')
const tmpGif = document.createElement('img')
const audioPlayer = document.createElement('AUDIO')
tmpGif.src = 'loader.gif'
tmpGif.width = 100
tmpGif.height = 100
tmpGif.id = 'tmpGif'
tmpGif.alt = 'Generating speech'
// …
Aquí hemos declarado tres variables para almacenar elementos de nuestro documento HTML; la primera
variable, buttonEl, hace referencia a un elemento existente, mientras que las otras dos variables
crean elementos nuevos. Debajo de eso, asignamos varios atributos a nuestro elemento GIF de
reemplazo. Por último, establecemos un archivo GIF de origen desde nuestro directorio de trabajo,
definimos las dimensiones y asignamos un ID al que referirnos más adelante en el programa.
Script disparador
Con esos Steps preliminares listos, ahora podemos integrar la funcionalidad principal de nuestro programa. Definamos el manejador de clic del botón Generate.
Para empezar, declara una función con el mismo nombre que la función que referenciamos antes en nuestra página HTML.
// …
function runScript() {
if (!buttonEl.isConnected) return
statusEl.textContent = 'Generating speech.'
buttonEl.parentNode.replaceChild(tmpGif, buttonEl)
// …
Con nuestra función configurada, podemos usar nuestro primer método de manipulación del DOM, parentNode.replaceChild().
Esto reemplaza el botón de nuestra página por el nuevo elemento GIF cuando se ejecuta la función.
Ahora que el GIF está en su sitio, indicando que se están procesando datos, podemos usar la API de Transloadit para sintetizar la voz de nuestro programa. Para ello, usamos el script de Robodog que importamos en nuestro archivo HTML.
// …
window.Robodog.upload([mytextfile], {
waitForEncoding: true,
params: {
auth: { key: 'TRANSLOADIT_AUTH_KEY' },
steps: {
':original': {
robot: '/upload/handle',
},
speech: {
use: ':original',
robot: '/text/speak',
provider: 'aws',
target_language: language,
},
},
},
})
// …
En nuestra nueva instancia de Robodog, hay dos parámetros que debemos configurar. El primero es para el archivo que subimos, que declaramos como la variable de archivo de texto que creamos antes en nuestro programa. El otro parámetro es un objeto que contiene todas las demás opciones adicionales.
En nuestro parámetro de objeto, necesitamos configurar unos cuantos Steps. Primero, waitForEncoding debe
establecerse en true para que podamos invocar los resultados de nuestra Assembly más adelante en el
programa. Después, también necesitamos insertar nuestra clave de autenticación en el parámetro auth.
Esta clave se encuentra en la pestaña Credenciales de tu Transloadit Console. Por último,
necesitamos insertar los Steps de nuestro Template:
uno para la subida y otro para la síntesis de voz.
El Step de voz se compone de unos cuantos parámetros. Primero, debemos indicar a este
Step que queremos usar el archivo de texto que subimos; esto se consigue con el parámetro
"use". Después, tenemos que especificar mediante el parámetro "robot" que se use nuestro
Robot /text/speak. El siguiente parámetro, "provider",
nos permite decidir la API de backend para la síntesis de voz. Los valores disponibles son «gcp» y
«aws». Cada proveedor tiene una variedad de voces diferentes, pero nosotros nos quedaremos con la
predeterminada. El último parámetro que usaremos, "target_language", indica a nuestra API en qué idioma
queremos que se pronuncie nuestro texto. Este valor proviene de la variable language que configuramos
al principio de nuestro programa.
Una vez que esta funcionalidad está lista, usamos el método then() de JavaScript para devolver
una promesa que contiene nuestros datos de voz, de modo que podamos consultarlos más adelante.
Usar el resultado
Con Robodog ya configurado, ahora podemos almacenar la voz resultante para reproducirla después desde el navegador.
// …
.then((bundle) => {
const audio_url = bundle.results?.find((result) => result.stepName === 'speech')?.ssl_url
if (!audio_url) throw new Error('No speech result returned')
audioPlayer.setAttribute('src', audio_url)
audioPlayer.setAttribute('controls', 'controls')
audioPlayer.setAttribute('aria-label', 'Generated speech')
audioPlayer.setAttribute('lang', language)
audioPlayer.setAttribute('style', 'width: 100%;')
tmpGif.parentNode.replaceChild(audioPlayer, tmpGif)
statusEl.textContent = 'Speech is ready. Use the audio controls to play it.'
})
// …
Dentro de nuestro método then(), usando la promesa que proporciona Robodog, podemos analizar los
resultados JSON de la Assembly y guardar la URL del resultado en una nueva variable. Con esta
nueva variable, audio_url, podemos usar el mismo método de reemplazo de elementos que usamos antes
en esta demostración. Después, para poner en marcha el reproductor de audio, establecemos algunos
atributos que proporcionan la fuente del audio y exponen los controles de reproducción. La
reproducción la inicia el usuario, así que no depende del permiso de reproducción automática. Con
todo eso listo, ¡ya se ha generado e integrado un reproductor de texto a voz en tu sitio web!
Eso sí, no podemos olvidarnos de tener una opción a prueba de fallos. Aquí es donde usamos el
método catch() de JavaScript para manejar cualquier error.
// …
.catch(() => {
statusEl.textContent = 'Could not generate speech. Please try again.'
tmpGif.parentNode.replaceChild(buttonEl, tmpGif)
})
}
// …
Los errores producen un estado legible y nuestro GIF de procesamiento se cambiará de nuevo por el elemento de botón original. Esto significa que si perdieras momentáneamente la conexión de red y luego la recuperaras, podrías hacer clic en el botón Generate para que la generación de voz se ponga en marcha de nuevo.
¡Y con esto llega a su fin esta demostración! Esperamos que coincidas en que ha demostrado aún más la enorme versatilidad de nuestra API. Por supuesto, puedes añadir sin más el programa que creamos a cualquier sitio web para tener un lector de pantalla rápido y sencillo, ¡pero no te quedes ahí! En lugar de eso, usa este blog como un conjunto de bloques de construcción para ampliar tus proyectos, y no dejes de contarnos los resultados :) Nuestro Robot /text/speak está disponible para todos nuestros clientes de pago, así que quizá quieras plantearte mejorar tu cuenta si este blog te ha resultado interesante. ¡Nuestro primer plan de pago cuesta 49 $/mes con 10 GB de datos de encoding!