2020. 3. 22. 13:53ㆍFrontend/JavaScript
해당 포스트는 https://courses.wesbos.com/account/access/5d760ba285f96c03c1e5725d/view/194128542 를 기반으로 작성되었습니다.
이번 예제는 웹에서 음성 인식 추출 API( Web Speech API
) 사용하기 입니다.
기본으로 제공해주고 있어 별도의 작업 없이 사용할 수 있습니다만, 아쉽게도 Chrome에서만 제공되는 걸로 알고 있습니다.
만들어볼 예제 설명
이러한 웹 사이트를 만드는 예제입니다.
필요 기능을 분석해보자면,
- 사이트에 접속하는 순간 부터 음성 인식은 시작되어야 하며, 흐름이 끊기는 순간 문단이 바뀝니다.
- 실시간으로 음성 인식 텍스트 결과 값이 그대로 표시됩니다.
예제 시작 코드
Wesbos님께서 시작 코드를 제공해주시고 계십니다.
https://github.com/wesbos/JavaScript30/blob/master/20%20-%20Speech%20Detection
여기서 index-START.html
의 코드를 저희가 원하는 방향으로 수정해가면 됩니다.
코드 실행하고 싶으시면 해당 package.json
도 같이 정의하시고npm install
-> npm start
로 실행 후 index-START.html
눌러주세요.
코드 - 과정
1. SpeechRecognition 사용하기
SpeechRecognition의 자세한 설명은 아래에서 확인하실 수 있습니다.
https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition
MDN 예제 따라 아래와 같이 SpeechRecognition
사용하기 위해 선언해주었습니다.
한국어 지원도 되길래 저는 한국어로 테스트 해보았습니다.
let recognition = new SpeechRecognition();
recognition.interimResults = true;
recognition.lang = 'ko-KR';
설정 값 더 파악해보기
-
interimResults
임시 결과를 반환 여부를 제어합니다false
. 임시 결과는 아직 최종 결과가 아닙니다.
즉, 실시간으로 인식된 결과 값을 모두 확인하고 싶다면true
를 설정하고, 최종 적으로 인식된 문장을 확인하고 싶다면false
를 작성하면 됩니다. -
lang
말 그대로 인식할 언어를 설정합니다.
영어는en-US
, 한국어는ko-KR
입력하시면 됩니다. -
continuous
여기에는 없지만continuous
를 설정할 수 있습니다. (기본 값은false
입니다.)true
로 설정 할 경우, 각각 인식된 문장을 하나로 합쳐줍니다. 그리고 중간에 쉬어도 stop되지 않습니다. stop을 원할 경우 강제 호출이 필요합니다.
음성 인식 시작 및 종료 제어
음성이 시작 및 종료 이벤트는 onend
, onstart
를 사용해 파악할 수 있습니다.
저는 웹 페이지에 들어가자마자 음성 인식을 시작하고,
인식이 자동 종료되었을 때 다른 문장을 또 인식하기 위해 onend
를 사용해 다시 시작될 수 있게 설정하였습니다.
recognition.start(); // 음성 인식(녹음) 시작
recognition.onend = function() {
console.log('한 뭉탱이 인식이 끝났습니다.');
recognition.start();
};
2. 음성 추출 데이터 가공하기
우선 닥치는대로 onresult
이벤트에 따른 결과를 console.log로 출력해보았습니다.
recognition.onresult = function(e) {
console.log(e.results);
}
그 결과 아래 사실을 파악
onresult
는 말하는 동안만 유효하다. 한번 말하다가 쉬면 녹음이 끊긴다.- 그 한 타임에 실시간으로 인식한 데이터 최종 값이 계속해서 갱신된다.
- 여러 문장이 인식된 경우, 인덱스를 달리하여 각
results
객체 안에 정보를 담아 리턴한다. - 그리고 우리가 원하는 인식된 문장은
results
의 첫번째Alternative
값의transcript
에 담겨있다.
그래서 제가 원하는 실시간 인식된 최종 문장을 이렇게 가공하였습니다.
recognition.onresult = function(e) {
let texts = Array.from(e.results)
.map(results => results[0].transcript).join("");
console.log(texts);
}
문장 별로 텍스트 실시간 변화 모습 출력해주기
예제 결과처럼 우리는
- 연속으로 말할 경우 계속 인식하여 출력하고, 말을 멈추면 문단을 바꾸어 동일한 작업을 실시한다.
- 인식 중인 실시간 변화 값을 그대로 출력해준다.
이를 위해서 새로운 p
엘리먼트에 textNode
를 추가하여 DOM에 넣어주는 작업이 필요합니다.
우선, 해당 작업을 간단히 테스트 해보았습니다.
let p = document.createElement('p');
p.textContent = "테스트";
document.querySelector('.words').appendChild(p); // .words가 정의된 div내에 새로운 p 문단들을 넣어줄 예정입니다.
-
연속으로 말할 경우 계속 인식하여 출력하고, 말을 멈추면 문단을 바꾸어 동일한 작업을 실시한다.
해당 작업을 수행하기 위해 새로운p
생성 및div
내에 추가하는 메소드를 정의하였습니다.
그리고 음성 인식 작업이 시작될 때 마다 해당 메소드를 호출하였습니다.let makeNewTextContent = function() { p = document.createElement('p'); document.querySelector('.words').appendChild(p); }; let p = null; recognition.start(); recognition.onstart = function() { makeNewTextContent(); // 음성 인식 시작시마다 새로운 문단을 추가한다. };
-
인식 중인 실시간 변화 값을 그대로 출력해줍니다.
onresult
에서 실시간으로 인식된 값을p.textNode
에 설정해줍니다.recognition.onresult = function(e) { let texts = Array.from(e.results) .map(results => results[0].transcript).join(""); p.textContent = texts; };
기타 - 문자 대체하기
추가로, '느낌표, 강조, 뿅'이라는 음성이 인식될 경우 ❗를 대신 출력해주는 간단한 코드를 추가해봅시다.
recognition.onresult = function(e) {
let texts = Array.from(e.results)
.map(results => results[0].transcript).join("");
texts.replace(/느낌표|강조|뿅/gi, '❗️');
p.textContent = texts;
};
최종 결과
코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Speech Detection</title>
</head>
<body>
<div class="words" contenteditable>
</div>
<script>
window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
let recognition = new SpeechRecognition();
recognition.interimResults = true;
recognition.lang = 'ko-KR';
let makeNewTextContent = function() {
p = document.createElement('p');
document.querySelector('.words').appendChild(p);
};
let p = null;
recognition.start();
recognition.onstart = function() {
makeNewTextContent(); // 음성 인식 시작시마다 새로운 문단을 추가한다.
};
recognition.onend = function() {
recognition.start();
};
recognition.onresult = function(e) {
let texts = Array.from(e.results)
.map(results => results[0].transcript).join("");
texts.replace(/느낌표|강조|뿅/gi, '❗️');
p.textContent = texts;
};
</script>
<style>
html {
font-size: 10px;
}
body {
background: #ffc600;
font-family: 'helvetica neue';
font-weight: 200;
font-size: 20px;
}
.words {
max-width: 500px;
margin: 50px auto;
background: white;
border-radius: 5px;
box-shadow: 10px 10px 0 rgba(0,0,0,0.1);
padding: 1rem 2rem 1rem 5rem;
background: -webkit-gradient(linear, 0 0, 0 100%, from(#d9eaf3), color-stop(4%, #fff)) 0 4px;
background-size: 100% 3rem;
position: relative;
line-height: 3rem;
}
p {
margin: 0 0 3rem;
}
.words:before {
content: '';
position: absolute;
width: 4px;
top: 0;
left: 30px;
bottom: 0;
border: 1px solid;
border-color: transparent #efe4e4;
}
</style>
</body>
</html>