Como criar um plugin de leitor de tela com o Robot /text/speak

Nota histórica sobre preços: os preços e as franquias abaixo descrevem a oferta vigente quando este artigo foi publicado. Consulte os preços atuais para ver os planos de hoje.
Recentemente falamos sobre uma atualização de um dos nossos queridos Robots de IA, e hoje aproveitamos esse impulso para adicionar mais um irmão à família de Robots de IA. Diga olá ao nosso novo Robot /text/speak! Assim como o último Robot de IA que abordamos, o Robot /speech/transcribe, a principal funcionalidade deste Robot gira em torno da fala. Mas, em vez de escrever texto a partir da fala, ele permite converter texto em fala (TTS).
Como esta é uma novidade tão empolgante, meu colega Joseph e eu decidimos que cada um criaria um projeto com este Robot no centro do design e depois escreveria um post no blog sobre o processo de design, para mostrar a versatilidade deste Robot. Então, se você gostar deste post, fique de olho no do Joseph, que sai em breve!
No meu projeto, vou criar um leitor de tela que pode ser facilmente integrado ao seu site para converter em fala todo o texto de uma página com um clique em um botão.
Este é um experimento de conversão de texto em fala, não um substituto para um leitor de tela assistivo com suporte a navegação e interação. Ao pressionar o botão, a demonstração envia o texto do tutorial abaixo para a Transloadit, excluindo os exemplos de código. O áudio segue o idioma exibido no menu de idiomas do site; use os controles de áudio para reproduzir o resultado. Já o exemplo independente abaixo escolhe a voz com base no atributo de idioma do primeiro bloco de texto marcado.

Pré-requisitos
Antes de começar, você precisa configurar uma pasta padrão de site. Crie os seguintes arquivos:
index.html, index.js e, opcionalmente, style.css. Para não deixar este post longo demais,
vamos discutir apenas o conteúdo do arquivo js e onde conectá-lo ao nosso arquivo HTML. Ainda
assim, se você quiser copiar todo o conteúdo dos outros arquivos, junto com o GIF de animação de
carregamento usado mais adiante nesta demonstração, pode consultar o seguinte
repositório.
HTML
Vamos começar configurando o elemento HTML da nossa aplicação. Como dito acima, não vamos discutir a criação de uma página web, apenas os elementos necessários para a conexão com o nosso arquivo JavaScript. Então crie um arquivo HTML básico com os seguintes elementos dentro da tag 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>
Isso configura o elemento de UI da nossa aplicação. O botão que criamos dispara a nossa requisição de Assembly com /text/speak usando o texto contido na nossa tag div. Para referenciar essa tag div, atribuímos a ela um valor de dataset que o desenvolvedor de back-end também usa para selecionar o idioma de destino especificado do Robot, com base nas opções descritas na nossa documentação. Por fim, vinculamos o nosso arquivo JavaScript no final do body.
Para interagir com a API da Transloadit, vamos usar o Robodog, uma versão simplificada do nosso uploader de arquivos gratuito, o Uppy.
Observação: o Robodog está descontinuado. Para novas integrações, use o plugin Transloadit do Uppy (UI do Dashboard ou uma UI personalizada).
No cabeçalho das suas páginas HTML, coloque a seguinte linha:
<script src="https://releases.transloadit.com/uppy/robodog/v1.10.7/robodog.min.js"></script>
JavaScript
Combine os seguintes trechos de JavaScript em index.js, na ordem. Os trechos dividem um único
programa ao longo das explicações; não são módulos separados. O bundle histórico do Robodog está
fixado abaixo; a demonstração interativa acima usa o plugin Transloadit do Uppy, que continua
sendo mantido.
Use apenas texto público que os usuários concordaram em enviar. Em produção, crie parâmetros assinados de curta duração no seu servidor usando Signature Authentication, restrinja as Instructions permitidas e o tamanho da entrada, e nunca exponha um Auth Secret no código do navegador.
Obtendo o nosso texto
Voltando ao nosso arquivo JavaScript, agora podemos começar a escrever o programa. A primeira coisa que precisamos fazer é extrair o texto legível do nosso site e atribuí-lo a uma variável para uso posterior.
// …
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
// …
Neste código, usamos o método Document.querySelectorAll() para criar um array contendo todos os elementos de
informação associados ao dataset screenreaderlanguage. Usamos um dataset para que, se quisermos
excluir algum texto legível da saída do nosso leitor de tela (como o texto dentro de uma tag
<code>), possamos fechar a tag div antes desse texto indesejado e depois criar uma nova tag div
com o mesmo dataset onde quisermos que o leitor de tela continue processando o texto.
Com essas informações básicas reunidas, ainda precisamos extrair o texto legível dos dados
coletados. Então, primeiro inicializamos uma variável de array para armazenar o texto legível,
antes de usar o loop for...of para percorrer cada elemento selecionado e salvar o seu valor
innerText.
Para fechar esta seção, definimos uma variável de idioma para que o nosso
Robot /text/speak saiba qual é o idioma de destino
mais adiante no programa. Para isso, declaramos a variável de idioma com o valor screenreaderlanguage do
nosso dataset.
Convertendo para o Uppy
Com os dados prontos, precisamos deixá-los adequados para que a nossa instância do Robodog possa processá-los.
// …
const mytextfile = new File([textArray.join('\n\n')], 'mytextfile.txt', { type: 'text/plain' })
// …
Isso cria um arquivo de texto com quebras de parágrafo entre os blocos de texto coletados. Marque apenas conteúdo no mesmo idioma; o exemplo lê o idioma do primeiro bloco marcado.
Animação de carregamento
Para que os usuários saibam que há um processamento em andamento, queremos exibir uma animação de carregamento enquanto o nosso Robot /text/speak faz o processamento. Para isso, precisamos de algumas variáveis de espaço reservado para substituir o elemento do botão por um GIF. Depois, se o processamento do texto for bem-sucedido, substituímos esse elemento GIF por um player de áudio.
// …
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'
// …
Aqui declaramos três variáveis para armazenar elementos do nosso documento HTML; a primeira
variável, buttonEl, referencia um elemento existente, enquanto as outras duas criam novos
elementos. Abaixo disso, atribuímos vários atributos ao elemento GIF substituto. Por fim, definimos
um arquivo GIF de origem do nosso diretório de trabalho, definimos as dimensões e um ID para
referenciá-lo mais adiante no programa.
Script de disparo
Com esses Steps preliminares concluídos, agora podemos integrar a funcionalidade principal do programa. Vamos definir o handler de clique do botão Generate.
Para começar, declare uma função com o mesmo nome da função que referenciamos antes na nossa página HTML.
// …
function runScript() {
if (!buttonEl.isConnected) return
statusEl.textContent = 'Generating speech.'
buttonEl.parentNode.replaceChild(tmpGif, buttonEl)
// …
Com a função configurada, podemos usar o nosso primeiro método de manipulação do DOM,
parentNode.replaceChild(). Ele substitui o botão da página pelo novo elemento GIF quando a função é executada.
Agora que o GIF está no lugar, indicando que os dados estão sendo processados, podemos usar a API da Transloadit para sintetizar a fala do programa. Para isso, usamos o script do Robodog que importamos no nosso arquivo 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,
},
},
},
})
// …
Na nossa nova instância do Robodog, há dois parâmetros que precisamos configurar. O primeiro é o arquivo enviado, que declaramos como a variável do arquivo de texto criada antes no programa. O outro parâmetro é um objeto contendo todas as demais opções adicionais.
No parâmetro de objeto, precisamos configurar alguns Steps. Primeiro, waitForEncoding
precisa ser definido como true para que possamos acessar os resultados da
Assembly mais adiante no programa. Em seguida, também precisamos inserir a nossa
chave de autenticação no parâmetro auth. Essa chave pode ser encontrada na aba Credenciais do
Transloadit Console. Por fim, precisamos inserir os Steps do nosso
Template: um para o upload e outro para a síntese de fala.
Alguns parâmetros compõem o Step de fala. Primeiro, precisamos informar a esse
Step que queremos usar o arquivo de texto que enviamos; isso é feito com o
parâmetro "use". Em seguida, precisamos especificar, com o parâmetro "robot", o uso do nosso
Robot /text/speak. O parâmetro seguinte, "provider",
permite escolher a API de backend para a síntese de fala. Os valores disponíveis são "gcp" e
"aws". Cada provedor oferece uma variedade de vozes diferentes, mas vamos ficar com a padrão. O
último parâmetro que usaremos, "target_language", informa à API em qual idioma queremos que o texto seja
pronunciado. Esse valor vem da variável language que configuramos no início do programa.
Com essa funcionalidade pronta, usamos o método then() do JavaScript para retornar uma promise
contendo os dados de fala, para que possamos consultá-los depois.
Usando o resultado
Com o Robodog todo configurado, agora podemos armazenar a fala resultante para reproduzi-la depois no 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 do método then(), usando a promise fornecida pelo Robodog, podemos analisar os
resultados JSON da Assembly e salvar a URL do resultado em uma nova variável.
Com essa nova variável, audio_url, podemos usar o mesmo método de substituição de elementos que
usamos antes nesta demonstração. Depois, para colocar o player de áudio em funcionamento, definimos
alguns atributos que fornecem a origem do áudio e exibem os controles de reprodução. A reprodução é
iniciada pelo usuário, então não depende de permissão de reprodução automática. Com tudo isso
pronto, um player de conversão de texto em fala foi gerado e integrado ao seu site!
Mas não podemos esquecer de ter uma opção de segurança contra falhas. É aqui que usamos o método
catch() do JavaScript para tratar quaisquer erros.
// …
.catch(() => {
statusEl.textContent = 'Could not generate speech. Please try again.'
tmpGif.parentNode.replaceChild(buttonEl, tmpGif)
})
}
// …
Os erros geram um status legível, e o GIF de processamento é trocado de volta pelo elemento do botão original. Isso significa que, se você perdesse momentaneamente a conexão de rede e depois a recuperasse, poderia clicar no botão Generate para gerar a fala novamente.
E com isso, esta demonstração chega ao fim! Esperamos que você concorde que ela mostrou ainda mais a enorme versatilidade da nossa API. Claro, você pode simplesmente adicionar o programa que criamos a qualquer site para ter um leitor de tela rápido e fácil, mas não pare por aí! Em vez disso, use este post como um conjunto de blocos de construção para expandir os seus projetos, e não deixe de nos contar sobre os resultados :) O nosso Robot /text/speak está disponível para todos os nossos clientes pagantes, então talvez valha a pena considerar fazer o upgrade da sua conta se este post despertou o seu interesse. O nosso primeiro plano pago custa $49/mês com 10 GB de dados de codificação!