Criando um aplicativo de Transcrição de Áudio em Texto com Quasar Framework

Olá pessoal, neste post iremos aprender como construir nosso próprio aplicativo de transcrição de áudio em texto, usando apenas Javascript e algumas API’s nativas dos navegadores.

Para deixar nosso aplicativo com um visual super bacana, e construí-lo rapidamente, utilizaremos também o Quasar Framework.

Se você é um desenvolvedor Front-end e ainda não ouviu falar sobre o Quasar, leia esse post sobre a versão 1.0 recém lançada.

Criando o projeto

O primeiro passo, é saber se nosso ambiente já está configurado. Os requisitos necessários são:

Agora vamos criar nosso projeto com Quasar CLI, executando o seguinte comando no seu cmd favorito:

quasar create quasar-speech-api

Após selecionar as opções desejadas no projeto e finalizar a instalação podemos levantar nosso ambiente de desenvolvimento usando o comando:

quasar dev

Conhecendo a Speech API

A Web Speech API permite incorporar dados de voz em aplicativos da web. A nossa aplicação vai utilizar 2 recursos da Web Speech API : SpeechSynthesisUterrance (Texto para Fala) e SpeechRecognition (Reconhecimento de Fala Assíncrona).

SpeechSynthesisUtterance

SpeechSynthesisUtterance  representa um pedido de fala e é compatível com os seguintes browsers

Compatibilidade Desktop
Compatibilidade em dispositivos móveis
Compatibilidade em dispositivos móveis

Para poder testar, abra o console de seu navegador(um que esteja na lista de compatibilidade) e execute:

speechSynthesis.speak(new SpeechSynthesisUtterance('Esse é um pedido de fala executado'))

Você também pode configurar algumas propriedades:

  • lang – defina o idioma (os valores usam uma tag de idioma BCP 47, como en-USou pt-BR);
  • pitch – obtém e define o tom na qual a expressão será dita aceita entre [0 e 2 ], o padrão é 1;
  • rate– definir a velocidade, aceita entre [0,1-10], o padrão é 1;
  • text – em vez de configurá-lo no construtor, você pode passá-lo como uma propriedade. O texto pode ter no máximo 32767 caracteres;
  • voice – define a voz (mais sobre isso abaixo);
  • volume – define o volume, aceita entre [0 – 1], o padrão é 1;

Faça o teste no console de seu navegador:

const utterance = new SpeechSynthesisUtterance('Esse é um pedido de fala executado')
utterance.pitch = 1.5
utterance.volume = 0.5
utterance.rate = 8
speechSynthesis.speak(utterance)

SpeechRecognition

A SpeechRecognition é a interface para o serviço de reconhecimento. Atualmente essa é uma tecnologia experimental, então sua compatibilidade ainda é bastante limitada com os navegadores do mercado.
Atualmente disponível no Chrome a partir da versão 33 e no WebView. Ou seja, se você também construir um aplicativo híbrido que utilize uma webview, também será compatível com essa API.

Aqui você pode conferir um exemplo interessante onde o fundo se modifica caso a cor falada esteja na lista pré definida. Basta clicar na tela e falar:

https://mdn.github.io/web-speech-api/speech-color-changer/

Criando Boot Files

Iremos criar um arquivo Boot em nosso projeto Quasar. Mas antes precisamos entender o que de fato ele faz.

Um arquivo de inicialização(boot) é um arquivo JavaScript simples que pode, opcionalmente, exportar uma função. O Quasar chamará a função exportada quando inicializar o aplicativo e, além disso, passará um objeto com as seguintes propriedades para a função:

  • app – Objeto com o qual o componente raiz e instanciado pelo Vue
  • router – instância do Vue Router de ‘src/router/index.js’
  • store – instância do aplicativo Vuex Store
  • Vue – É como se fizéssemos import Vue from ‘vue’ e está lá por conveniência
  • ssrContext – Disponível apenas no lado do servidor, se estiver construindo uma aplicação SSR

Bem, agora iremos criar o nosso próprio boot chamado de speech.js no diretório /src/boot.

import { Loading, QSpinnerAudio, QSpinnerBars } from 'quasar'
export default async ({ Vue }) => {
  Vue.prototype.$speechTalk = (lang = 'pt-BR', text) => {
    return new Promise((resolve, reject) => {
      let speech = new SpeechSynthesisUtterance()
      // Set the text and voice attributes.
      speech.lang = lang
      speech.text = text
      speech.volume = 1
      speech.rate = 1
      speech.pitch = 1
      setTimeout(() => {
        window.speechSynthesis.speak(speech)
      }, 300)

      speech.addEventListener('start', () => {
        Loading.show({
          delay: 0,
          spinner: QSpinnerAudio, // ms,
          backgroundColor: 'primary'
        })
      })

      speech.addEventListener('end', () => {
        Loading.hide()
        resolve(true)
      })
    })
  }
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition
  const recognition = SpeechRecognition ? new SpeechRecognition() : false

  Vue.prototype.$speechToText = {
    start: (lang = 'pt-BR', continuous = false) => {
      return new Promise((resolve, reject) => {
        let text = ''
        setTimeout(() => {
          Loading.show({
            // delay: 400,
            spinner: QSpinnerBars, // ms,
            backgroundColor: 'primary',
            message: 'Aguardando áudio',
            messageColor: 'white'
          })
          recognition.lang = lang // this.voiceSelect
          recognition.continuous = continuous
          recognition.start()
        }, 400)

        recognition.onresult = (event) => {
          let current = event.resultIndex
          // Get a transcript of what was said.
          let transcript = event.results[current][0].transcript
          // Add the current transcript to the contents of our Note.
          // var noteContent += transcript
          text += transcript
          resolve(text)
        }
        recognition.onspeechend = (event) => {
          // if (continuous) {
          reject(false)
          // }
        }
        recognition.nomatch = () => {
          reject(false)
        }
        recognition.onend = () => {
          text = ''
          Loading.hide()
          if (!continuous) {
            reject(false)
          }
        }
      })
    },
    stop: () => {
      recognition.stop()
    }
  }
}

Neste arquivo adicionamos 2 propriedades a instância Vue. A propriedade $speechTalk e $speechToText.

A $speechTalk irá disparar a api SpeechSynthesisUtterance e iniciar a fala do assistente. Para isso precisamos passar 2 parâmetros:

  • lang – onde passaremos o idioma escolhido para a narração.
  • text – o texto que será narrado

Além disso, conseguimos fazer com que as propriedades sejam Promises, já que é possível identificar os eventos de inicialização e finalização(start e end).

Na $speechToText passaremos os parâmetros:

  • lang – onde passamos o idioma escolhido para a fala.
  • continuous – quando true, fica constantemente recebendo eventos e detectando a fala e convertendo para texto

Temos 2 possibilidades com o $speechToText, o start para iniciar o processo de captura e o stop para forçar a parada.

Criando nossa Página(Page)

No diretório src/pages alteramos o Index.vue para o seguinte código:

<template>
  <q-page class="container">
    <div class="row q-col-gutter-md q-pt-md">
      <q-select
        outlined v-model="voiceSelect"
        :options="optionsVoice"
        label="Idiomas"
        class="col-12"
        emit-value
        map-options/>
      <div class="col-6 q-pt-md">
        <q-btn
          push color="primary"
          round size="lg" icon="keyboard_voice"
          class="float-right"
          @click="record()"/>
      </div>
      <div class="col-6 q-pt-md">
        <q-btn
        push color="primary"
        round size="lg" icon="play_arrow"
        @click="playAudio()"/>
      </div>
      <div class="col-12 text-center">
        <q-toggle
        v-model="continuous"
        label="Contínuo"
        left-label
      />
      </div>
      <div class="col-12 q-pa-xl">
        <q-input
          v-model="text"
          autogrow
          label="Texto"
          clearable
          outlined/>
      </div>
        <div class="col-12 q-pa-lg text-caption">
          <div class="text-bold">Instruções:</div>
          <div>Escolha seu idioma para que o assistente escreva corretamente sua fala.</div>
          <div>Aperte no botão microfone
             <q-btn dense color="primary" round size="xs" icon="keyboard_voice" />
             para iniciar a captura de fala, e autorize seu dispositivo a utilizar o microfone.
          </div>
          <div>
            Ao aparecer a tela com a mensagem "Aguardando Áudio" diga a frase que deseja que seja transcrita.<br>
            Ao finalizar, sua fala aparecerá no campo de Texto.
          </div>
          <div>Caso queira ouvir o texto, basta apertar no botão play <q-btn dense color="primary" round size="xs" icon="play_arrow" />. </div>
        </div>
      </div>
      <q-page-sticky v-if="btnStop" position="bottom-right" :offset="[15, 18]" style="z-index: 10000">
        <q-btn fab icon="stop" color="negative" @click="stop()" />
      </q-page-sticky>
  </q-page>
</template>

<style>
</style>

<script>
export default {
  name: 'PageIndex',
  data () {
    return {
      text: '',
      voiceSelect: 'pt-BR',
      optionsVoice: [],
      continuous: false,
      btnStop: false
    }
  },
  mounted () {
    this.setVoices()
  },
  methods: {
    setVoices () {
      let id = setInterval(() => {
        if (this.optionsVoice.length === 0) {
          this.voicesList()
        } else {
          clearInterval(id)
        }
      }, 50)
    },
    voicesList () {
      let teste = window.speechSynthesis
      this.optionsVoice = teste.getVoices().map(voice => ({
        label: voice.name, value: voice.lang
      }))
    },
    playAudio () {
      this.$speechTalk(this.voiceSelect, this.text)
    },
    record () {
      this.btnStop = true
      this.$speechToText.start(this.voiceSelect, this.continuous)
        .then((suc) => {
          this.text += ' ' + suc
          if (this.continuous) {
            this.record()
          }
          // this.btnStop = false
        })
        .catch(() => {
          this.btnStop = false
        })
    },
    stop () {
      this.$speechToText.stop()
      this.btnStop = false
    }
  }
}
</script>

Antes de tentar exibir o projeto é necessário habilitar alguns componentes em nosso quasar.conf.js.

components: [
  'QLayout',
  'QHeader',
  'QDrawer',
  'QPageContainer',
  'QPage',
  'QToolbar',
  'QToolbarTitle',
  'QBtn',
  'QIcon',
  'QList',
  'QItem',
  'QItemSection',
  'QItemLabel',
  'QInput',
  'QSelect',
  'QSpinnerBars',
  'QSpinnerComment',
  'QBadge',
  'QImg',
  'QAvatar',
  'QScrollArea',
  'QPageSticky',
  'QToggle'
],

No mounted() da nossa página, é disparado o método setVoices(), este método serve para recuperar os tipos de idiomas disponíveis no navegador, e são armazenados no parâmetro optionsVoice para serem exibidos no select de Idiomas.

O método playAudio(), dispara o narrador. Ele utiliza o idioma selecionado e o texto da textarea para enviar ao $speechTalk.

O método record irá ativar o $speechToText e ficar aguardando uma fala em seu app. Para isso é passado ao método o idioma selecionado e o parâmetro “continuous” que como vimos anteriormente, deixa o ouvinte constante ou não.

Por fim temos o método stop(), que é usado em um botão quando o modo continuo for ativado.

O resultado de nosso app é este:

Tela da aplicação

Não precisamos instalar nenhuma dependência externa em nossa aplicação. Isso mostra que os navegadores evoluíram muito e possuem muitos recursos poderosos e pouco utilizados por desenvolvedores.

O projeto está atualizado no meu github e você pode clona-lo ou baixa-lo através do link:

https://github.com/patrickmonteiro/quasar-speech-api

Ou acessar a DEMO: https://quasarspeechapi.surge.sh/#/

Referências:

https://flaviocopes.com/speech-synthesis-api/

https://developer.mozilla.org/pt-BR/docs/Web/API/SpeechSynthesisUtterance

https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *