Bluemix의 Node-RED에서 Text to Speech 노드를 사용하여 음성을 읽습니다.
목적
Node-RED와 WatsonAPI를 사용하고 싶습니다! 그래서 Bluemix의 Node-RED에서 Text to Speech 노드를 사용하여 입력한 텍스트(영어)를 읽는 것을 만들었습니다.
API를 통해 호출하는 방법이 아니라 굳이 TTS 노드를 사용하고 있습니다.
환경
화면
채팅 화면 상단에 오디오 태그를 추가했습니다.<audio controls id="audio"></audio>
화면 하단의 "What do you want to say?"라고 쓰여진 상자에 텍스트를 입력하고 Send 버튼을 누릅니다.
입력한 텍스트를 TTS에서 음성 데이터(wav)로 변환하여 채팅 화면 상단의 오디오로 재생하도록 구현합니다.
구현
text to speech 노드 호출은 WebSocket을 사용합니다.
websocket은 입력한 텍스트를 msg.payload로 설정하고 전달합니다.
var payload = {
message: message.value,
user: user.value,
ts: (new Date()).getTime(),
payload: message.value
};
text to speech로 변환된 음성 데이터는 반환값 payload.speech.data에 바이너리 데이터(wav)로 설정되어 있습니다.
"speech": { "type":"Buffer", "data":[82, 73, ...]}
이대로 audio.src로 설정할 수 없으므로 Blob로 변환하여 createObjectURL(blob)하여 설정합니다.
function playTTS(payload){
var wavString = payload.speech.data;
// console.log(wavString);
var len = wavString.length;
// console.log(len);
var buf = new ArrayBuffer(len);
// console.log(buf);
var view = new Uint8Array(buf);
for (var i = 0; i < len; i++) {
view[i] = wavString[i] & 0xff;
}
console.log(view);
var blob = new Blob([view], {type: "audio/wav"});
var URL = window.URL || window.webkitURL
var blobUrl = URL.createObjectURL(blob);
// console.log(blobUrl);
var audio = document.getElementById('audio');
audio.src = blobUrl;
audio.load();
audio.preload = 'auto';
audio.play();
}
이 근처의 구현 방법을 조사하고 있었는데, view에 값을 넣고있는 곳에서
wavString.charCodeAt(i) & 0xff
하지만, 이번에는 wavString이 String이 아닌 Array였습니다.
wavString[i] & 0xff 입니다.
charCodeAt(i) & 0xff 라고 하는 것을 왜 하지 않으면 안 되는가 하는 것은, 여기 의 설명을 알기 쉽다.
요약
text to speech 노드 호출은 WebSocket을 사용합니다.
websocket은 입력한 텍스트를 msg.payload로 설정하고 전달합니다.
var payload = {
message: message.value,
user: user.value,
ts: (new Date()).getTime(),
payload: message.value
};
text to speech로 변환된 음성 데이터는 반환값 payload.speech.data에 바이너리 데이터(wav)로 설정되어 있습니다.
"speech": { "type":"Buffer", "data":[82, 73, ...]}
이대로 audio.src로 설정할 수 없으므로 Blob로 변환하여 createObjectURL(blob)하여 설정합니다.
function playTTS(payload){
var wavString = payload.speech.data;
// console.log(wavString);
var len = wavString.length;
// console.log(len);
var buf = new ArrayBuffer(len);
// console.log(buf);
var view = new Uint8Array(buf);
for (var i = 0; i < len; i++) {
view[i] = wavString[i] & 0xff;
}
console.log(view);
var blob = new Blob([view], {type: "audio/wav"});
var URL = window.URL || window.webkitURL
var blobUrl = URL.createObjectURL(blob);
// console.log(blobUrl);
var audio = document.getElementById('audio');
audio.src = blobUrl;
audio.load();
audio.preload = 'auto';
audio.play();
}
이 근처의 구현 방법을 조사하고 있었는데, view에 값을 넣고있는 곳에서
wavString.charCodeAt(i) & 0xff
하지만, 이번에는 wavString이 String이 아닌 Array였습니다.
wavString[i] & 0xff 입니다.
charCodeAt(i) & 0xff 라고 하는 것을 왜 하지 않으면 안 되는가 하는 것은, 여기 의 설명을 알기 쉽다.
요약
코드
[{"id":"68a12744.975ed8","type":"websocket-listener","z":"52dbf05.fad241","path":"/ws/english","wholemsg":"true"},{"id":"804b9d82.96fcd","type":"http in","z":"800bc702.676838","name":"","url":"/speech","method":"get","swaggerDoc":"","x":236,"y":349,"wires":[["2cbc3872.627ae"]]},{"id":"2cbc3872.627ae","type":"template","z":"800bc702.676838","name":"html","field":"","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<head>\n <meta name=\"viewport\" content=\"width=320, initial-scale=1\">\n <title>speech!</title>\n</head>\n\n<body>\n <div id=\"wrapper\">\n <div id=\"chat_box\" class=\"content\">\n <p><audio controls id=\"audio\"></audio></p>\n </div>\n\n <div id=\"footer\">\n <div class=\"content\">\n <input type=\"text\" id=\"user\" placeholder=\"Who are you?\" />\n <input type=\"text\" id=\"message\" placeholder=\"What do you want to say?\" />\n <input type=\"button\" id=\"send_btn\" value=\"Send\" onclick=\"sendMessage()\">\n </div>\n </div>\n </div>\n</body>\n\n<script type=\"text/javascript\">\n var wsUri = \"ws://{{req.headers.host}}/ws/english\";\n var ws = new WebSocket(wsUri);\n\n function createSystemMessage(message) {\n var message = document.createTextNode(message);\n\n var messageBox = document.createElement('p');\n messageBox.className = 'system';\n\n messageBox.appendChild(message);\n\n var chat = document.getElementById('chat_box');\n chat.appendChild(messageBox);\n }\n\n function createUserMessage(user, message) {\n var user = document.createTextNode(user + ': ');\n\n var userBox = document.createElement('span');\n userBox.className = 'username';\n userBox.appendChild(user);\n\n var message = document.createTextNode(message);\n\n var messageBox = document.createElement('p');\n messageBox.appendChild(userBox);\n messageBox.appendChild(message);\n\n var chat = document.getElementById('chat_box');\n chat.appendChild(messageBox);\n }\n\n function playTTS(payload){\n var wavString = payload.speech.data;\n // console.log(wavString);\n var len = wavString.length;\n // console.log(len);\n var buf = new ArrayBuffer(len);\n // console.log(buf);\n var view = new Uint8Array(buf);\n for (var i = 0; i < len; i++) {\n view[i] = wavString[i] & 0xff;\n }\n console.log(view);\n var blob = new Blob([view], {type: \"audio/wav\"});\n var URL = window.URL || window.webkitURL\n var blobUrl = URL.createObjectURL(blob);\n // console.log(blobUrl);\n var audio = document.getElementById('audio');\n audio.src = blobUrl;\n audio.load();\n audio.preload = 'auto';\n audio.play();\n }\n\n ws.onopen = function(ev) {\n createSystemMessage('[Connected]');\n };\n\n ws.onclose = function(ev) {\n createSystemMessage('[Disconnected]');\n }\n\n ws.onmessage = function(ev) {\n var payload = JSON.parse(ev.data);\n createUserMessage(payload.user, payload.message);\n playTTS(payload);\n\n var chat = document.getElementById('chat_box');\n chat.scrollTop = chat.scrollHeight;\n }\n\n function sendMessage() {\n var user = document.getElementById('user');\n var message = document.getElementById('message');\n\n var payload = {\n message: message.value,\n user: user.value,\n ts: (new Date()).getTime(),\n payload: message.value\n };\n\n ws.send(JSON.stringify(payload));\n message.value = \"\";\n };\n</script>\n\n<style type=\"text/css\">\n * {\n font-family: \"Palatino Linotype\", \"Book Antiqua\", Palatino, serif;\n font-style: italic;\n font-size: 24px;\n }\n\n html, body, #wrapper {\n margin: 0;\n padding: 0;\n height: 100%;\n }\n\n #wrapper {\n background-color: #ecf0f1;\n }\n\n #chat_box {\n box-sizing: border-box;\n height: 100%;\n overflow: auto;\n padding-bottom: 50px;\n }\n\n #footer {\n box-sizing: border-box;\n position: fixed;\n bottom: 0;\n height: 50px;\n width: 100%;\n background-color: #2980b9;\n }\n\n #footer .content {\n padding-top: 4px;\n position: relative;\n }\n\n #user { width: 20%; }\n #message { width: 68%; }\n #send_btn {\n width: 10%;\n position: absolute;\n right: 0;\n bottom: 0;\n margin: 0;\n }\n\n .content {\n width: 70%;\n margin: 0 auto;\n }\n\n input[type=\"text\"],\n input[type=\"button\"] {\n border: 0;\n color: #fff;\n }\n\n input[type=\"text\"] {\n background-color: #146EA8;\n padding: 3px 10px;\n }\n\n input[type=\"button\"] {\n background-color: #f39c12;\n border-right: 2px solid #e67e22;\n border-bottom: 2px solid #e67e22;\n min-width: 70px;\n display: inline-block;\n }\n\n input[type=\"button\"]:hover {\n background-color: #e67e22;\n border-right: 2px solid #f39c12;\n border-bottom: 2px solid #f39c12;\n cursor: pointer;\n }\n\n .system,\n .username {\n color: #aaa;\n font-style: italic;\n font-family: monospace;\n font-size: 16px;\n }\n\n @media(max-width: 1000px) {\n .content { width: 90%; }\n }\n\n @media(max-width: 780px) {\n #footer { height: 91px; }\n #chat_box { padding-bottom: 91px; }\n\n #user { width: 100%; }\n #message { width: 80%; }\n }\n\n @media(max-width: 400px) {\n #footer { height: 135px; }\n #chat_box { padding-bottom: 135px; }\n\n #message { width: 100%; }\n #send_btn {\n position: relative;\n margin-top: 3px;\n width: 100%;\n }\n }\n</style>\n","x":425,"y":348,"wires":[["73d2e2e0.a0d35c"]]},{"id":"73d2e2e0.a0d35c","type":"http response","z":"800bc702.676838","name":"","x":607,"y":348,"wires":[]},{"id":"9852e538.cee01","type":"websocket in","z":"800bc702.676838","name":"","server":"68a12744.975ed8","client":"","x":187,"y":257,"wires":[["600df703.08308"]]},{"id":"600df703.08308","type":"function","z":"800bc702.676838","name":"","func":"delete msg._session;\n//msg.payload = msg.message;\nreturn msg;\n\n","outputs":1,"noerr":0,"x":351,"y":257,"wires":[["b8226832.18236"]]},{"id":"8a76e158.d0166","type":"websocket out","z":"800bc702.676838","name":"","server":"68a12744.975ed8","client":"","x":737,"y":258,"wires":[]},{"id":"b8226832.18236","type":"watson-text-to-speech","z":"800bc702.676838","name":"","lang":"english","voice":"en-US_MichaelVoice","format":"audio/wav","x":532,"y":258,"wires":[["8a76e158.d0166","b7ce263.5b52858"]]},{"id":"b7ce263.5b52858","type":"debug","z":"800bc702.676838","name":"","active":false,"console":"false","complete":"true","x":731,"y":204,"wires":[]}]
Reference
이 문제에 관하여(Bluemix의 Node-RED에서 Text to Speech 노드를 사용하여 음성을 읽습니다.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/tmikada/items/a022f6f689ab500c1aad
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
[{"id":"68a12744.975ed8","type":"websocket-listener","z":"52dbf05.fad241","path":"/ws/english","wholemsg":"true"},{"id":"804b9d82.96fcd","type":"http in","z":"800bc702.676838","name":"","url":"/speech","method":"get","swaggerDoc":"","x":236,"y":349,"wires":[["2cbc3872.627ae"]]},{"id":"2cbc3872.627ae","type":"template","z":"800bc702.676838","name":"html","field":"","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<head>\n <meta name=\"viewport\" content=\"width=320, initial-scale=1\">\n <title>speech!</title>\n</head>\n\n<body>\n <div id=\"wrapper\">\n <div id=\"chat_box\" class=\"content\">\n <p><audio controls id=\"audio\"></audio></p>\n </div>\n\n <div id=\"footer\">\n <div class=\"content\">\n <input type=\"text\" id=\"user\" placeholder=\"Who are you?\" />\n <input type=\"text\" id=\"message\" placeholder=\"What do you want to say?\" />\n <input type=\"button\" id=\"send_btn\" value=\"Send\" onclick=\"sendMessage()\">\n </div>\n </div>\n </div>\n</body>\n\n<script type=\"text/javascript\">\n var wsUri = \"ws://{{req.headers.host}}/ws/english\";\n var ws = new WebSocket(wsUri);\n\n function createSystemMessage(message) {\n var message = document.createTextNode(message);\n\n var messageBox = document.createElement('p');\n messageBox.className = 'system';\n\n messageBox.appendChild(message);\n\n var chat = document.getElementById('chat_box');\n chat.appendChild(messageBox);\n }\n\n function createUserMessage(user, message) {\n var user = document.createTextNode(user + ': ');\n\n var userBox = document.createElement('span');\n userBox.className = 'username';\n userBox.appendChild(user);\n\n var message = document.createTextNode(message);\n\n var messageBox = document.createElement('p');\n messageBox.appendChild(userBox);\n messageBox.appendChild(message);\n\n var chat = document.getElementById('chat_box');\n chat.appendChild(messageBox);\n }\n\n function playTTS(payload){\n var wavString = payload.speech.data;\n // console.log(wavString);\n var len = wavString.length;\n // console.log(len);\n var buf = new ArrayBuffer(len);\n // console.log(buf);\n var view = new Uint8Array(buf);\n for (var i = 0; i < len; i++) {\n view[i] = wavString[i] & 0xff;\n }\n console.log(view);\n var blob = new Blob([view], {type: \"audio/wav\"});\n var URL = window.URL || window.webkitURL\n var blobUrl = URL.createObjectURL(blob);\n // console.log(blobUrl);\n var audio = document.getElementById('audio');\n audio.src = blobUrl;\n audio.load();\n audio.preload = 'auto';\n audio.play();\n }\n\n ws.onopen = function(ev) {\n createSystemMessage('[Connected]');\n };\n\n ws.onclose = function(ev) {\n createSystemMessage('[Disconnected]');\n }\n\n ws.onmessage = function(ev) {\n var payload = JSON.parse(ev.data);\n createUserMessage(payload.user, payload.message);\n playTTS(payload);\n\n var chat = document.getElementById('chat_box');\n chat.scrollTop = chat.scrollHeight;\n }\n\n function sendMessage() {\n var user = document.getElementById('user');\n var message = document.getElementById('message');\n\n var payload = {\n message: message.value,\n user: user.value,\n ts: (new Date()).getTime(),\n payload: message.value\n };\n\n ws.send(JSON.stringify(payload));\n message.value = \"\";\n };\n</script>\n\n<style type=\"text/css\">\n * {\n font-family: \"Palatino Linotype\", \"Book Antiqua\", Palatino, serif;\n font-style: italic;\n font-size: 24px;\n }\n\n html, body, #wrapper {\n margin: 0;\n padding: 0;\n height: 100%;\n }\n\n #wrapper {\n background-color: #ecf0f1;\n }\n\n #chat_box {\n box-sizing: border-box;\n height: 100%;\n overflow: auto;\n padding-bottom: 50px;\n }\n\n #footer {\n box-sizing: border-box;\n position: fixed;\n bottom: 0;\n height: 50px;\n width: 100%;\n background-color: #2980b9;\n }\n\n #footer .content {\n padding-top: 4px;\n position: relative;\n }\n\n #user { width: 20%; }\n #message { width: 68%; }\n #send_btn {\n width: 10%;\n position: absolute;\n right: 0;\n bottom: 0;\n margin: 0;\n }\n\n .content {\n width: 70%;\n margin: 0 auto;\n }\n\n input[type=\"text\"],\n input[type=\"button\"] {\n border: 0;\n color: #fff;\n }\n\n input[type=\"text\"] {\n background-color: #146EA8;\n padding: 3px 10px;\n }\n\n input[type=\"button\"] {\n background-color: #f39c12;\n border-right: 2px solid #e67e22;\n border-bottom: 2px solid #e67e22;\n min-width: 70px;\n display: inline-block;\n }\n\n input[type=\"button\"]:hover {\n background-color: #e67e22;\n border-right: 2px solid #f39c12;\n border-bottom: 2px solid #f39c12;\n cursor: pointer;\n }\n\n .system,\n .username {\n color: #aaa;\n font-style: italic;\n font-family: monospace;\n font-size: 16px;\n }\n\n @media(max-width: 1000px) {\n .content { width: 90%; }\n }\n\n @media(max-width: 780px) {\n #footer { height: 91px; }\n #chat_box { padding-bottom: 91px; }\n\n #user { width: 100%; }\n #message { width: 80%; }\n }\n\n @media(max-width: 400px) {\n #footer { height: 135px; }\n #chat_box { padding-bottom: 135px; }\n\n #message { width: 100%; }\n #send_btn {\n position: relative;\n margin-top: 3px;\n width: 100%;\n }\n }\n</style>\n","x":425,"y":348,"wires":[["73d2e2e0.a0d35c"]]},{"id":"73d2e2e0.a0d35c","type":"http response","z":"800bc702.676838","name":"","x":607,"y":348,"wires":[]},{"id":"9852e538.cee01","type":"websocket in","z":"800bc702.676838","name":"","server":"68a12744.975ed8","client":"","x":187,"y":257,"wires":[["600df703.08308"]]},{"id":"600df703.08308","type":"function","z":"800bc702.676838","name":"","func":"delete msg._session;\n//msg.payload = msg.message;\nreturn msg;\n\n","outputs":1,"noerr":0,"x":351,"y":257,"wires":[["b8226832.18236"]]},{"id":"8a76e158.d0166","type":"websocket out","z":"800bc702.676838","name":"","server":"68a12744.975ed8","client":"","x":737,"y":258,"wires":[]},{"id":"b8226832.18236","type":"watson-text-to-speech","z":"800bc702.676838","name":"","lang":"english","voice":"en-US_MichaelVoice","format":"audio/wav","x":532,"y":258,"wires":[["8a76e158.d0166","b7ce263.5b52858"]]},{"id":"b7ce263.5b52858","type":"debug","z":"800bc702.676838","name":"","active":false,"console":"false","complete":"true","x":731,"y":204,"wires":[]}]
Reference
이 문제에 관하여(Bluemix의 Node-RED에서 Text to Speech 노드를 사용하여 음성을 읽습니다.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/tmikada/items/a022f6f689ab500c1aad텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)