Text-to-Speech SpringBoot Web Service com Watson

Quem trabalha com desenvolvimento de software conhece bem essa sensação. É o seu primeiro contato implementando algo totalmente novo, eu particularmente adoro isso, mas sei que no meio desse caminho algo vai dar errado e até que eu fale “noooooooooossa, mas era tão simples”, vou dizer muitos “quem foi o gênio que resolveu fazer isso dessa maneira?” ou “quem inventou isso não tem mãe não!?”, isso somente para pegar leve.

Boa parte desses problemas ocorrem em função de informações desencontradas. A internet é muito legal, tudo que você quer aprender está nela, mas nem sempre estruturado e organizado como um livro ou um curso, as vezes aprendemos algo juntando pedaços de código, tutoriais e documentações que encontramos em foruns, stackoverflow, google, udemy e por ai vai, o que dificulta e leva a gente as vezes para caminhos errados até encontrar a saída correta.

Por isso esse POST de hoje, pois tive muita dificuldade de juntar e encontrar essas informações na internet. Desenvolver uma solução para um chatbot que convertesse no backend um texto em áudio, para ser obtido via streaming por javascript, foi cansativo e testei diversas soluções sem exito.

O que eu precisava era o seguinte, desenvolver um novo recurso para um chatbot que usando uma ferramenta de text-to-speech (conversão de texto para áudio) fosse permitido ao usuário ouvir a resposta do chatbot. 

A solução foi construída em java utilizando o framework springboot que já servia como backend do chatbot. 

BACKEND – Java

[cc lang=”java”]
@PostMapping(“/text-to-speech”)
@Consumes(value = MediaType.APPLICATION_JSON_VALUE)
public void textToSpeech(@RequestBody ConversationBean bean, HttpServletRequest request, HttpServletResponse response) {

        try {

if (manager.isActive(EFeatureFlag.CHATBOT_TEXT_TO_SPEECH)) {

SpeechModel speechModel = new SpeechModelFactory().create(EPlataforma.IBM_BLUEMIX.getCodigo());

InputStream audio = speechModel.textToSpeech(bean.getInput());
response.setContentType("audio/wav");
response.setHeader("Accept-Ranges", "bytes");

ServletOutputStream out = response.getOutputStream();

IOUtils.copy(audio, out);

}

} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
[/cc]

No código acima obtemos um inputStream do serviço de text-to-Speech do Watson, mas serviria qualquer inputStream retornado por outros players, como AWS, google e azure, que possuem serviços semelhantes.

O código do backend copia para o response os bytes do áudio retornado para o inputStream e avisa ao navegador que precisa tratar o retorno como um audio/wav.

O que me travou bastante nesse ponto foi o retorno do serviço para que o frontend pudesse tratar o retorno via streaming. Na documentação do watson não encontrei este procedimento, pois em todos os exemplos apos receber o inputstream, o backend guardava o arquivo em um .wav dentro do filesystem, o que funcionava perfeitamente, porem era um exemplo pouco usual para o dia a dia.

O grande problema foi obter esses dados apos uma requisição jQuery e executar o áudio. 80% do tempo perdido na construção dessa funcionalidade foi devido ao áudio quando executado, apresentar um conjunto de ruídos indescritíveis e depois de alguns sustos com o barulho do áudio sem sentido, finalmente encontrei uma solução conforme descrevo abaixo:

FRONTEND- Javascript

[cc lang="javascript"]
window.onload = loadTextToSpeech;
var context; // Audio context
var buf; // Audio buffer

function loadTextToSpeech() {

if (!window.AudioContext) {
if (!window.webkitAudioContext) {
alert("Your browser does not support any AudioContext and cannot play back this audio.");
return;
}

window.AudioContext = window.webkitAudioContext;

}

context = new AudioContext();
console.log("saida de audio ligada")

}
[/cc]

chamando serviço de backend para converter o texto em audio

[cc lang="javascript"]
var urlTextToSpeech = 'http://localhost:8081/chatbot/text-to-speech';
var texto = "ola bom dia";

textToSpeech(texto);

function textToSpeech(text) {

var data = {
input: text
};

var request = new XMLHttpRequest();
request.open('POST', urlTextToSpeech, true);

request.responseType = 'arraybuffer';
request.setRequestHeader("Authorization", tokenAuth);
request.setRequestHeader("Content-Type", 'application/json');

// Decode asynchronously
request.onload = function() {

context.decodeAudioData(request.response, function(buffer) {

var source = context.createBufferSource(); //cria uma fonte de audio
source.buffer = buffer; // informa a fonte de audio o que deve ser executado
source.connect(context.destination); // conecta no alto falante
source.start(0); //play do audio
});
}
request.send(JSON.stringify(data));

}
[/cc]

Esta solução hoje funciona muito bem e encerrou uma serie de frustrações e xingamentos que foram jogados ao espaço.

Related Posts

IBM Cloud além do Watson – Parte 1
Watson Assistant
WKS – Watson Knowledge Studio