ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • speech-text-synchronize
    기타 2022. 12. 22. 23:25

    speech-text-synchronize

    WHAT : 재생되는 영상의 Speech와 Text의 sync를 맞추는 것
    WHY : 영상의 사운드에 해당하는 대사를 사용자가 인식하기 쉽도록 하기 위해 사용하고자 한다.
    HOW : 기술적으로 어떻게 구현할 수 있나?

     

    - Sync Audio With Text Using Javascript : https://growth-msleeffice.tistory.com/80

     

    하단의 내용은 "영상"이 아니라 "오디오"에 적용하기 좋은 방법이다.

    영상과 dialog의 sync에 vtt를 활용할 수 있는 방법이 있어 상기 내용으로 대체함.

    1. LRC 파일 + lrc-parser(npm module) + Audio Sync With text (npm module)

    1.0 Logic

    1. 영상 관련 대본을 LRC 파일 포맷으로 저장
    2. lrc-parser로 해당 데이터를 JSON 형태로 바꾸어 front로 전송
    3. front에서 영상 재생 시 audioSync 모듈을 활용, 로직 구현

    1.1 LRC (출처 : https://ko.wikipedia.org/wiki/LRC_(파일_포맷))

    • 정의 LRC는 MP3나 OGGMIDI와 같은 음악 파일과 동시에 동작되는 노랫말을 담은 파일 확장자이다. .lrc로 된 확장자를 사용하며 일반적으로 그 음악 파일과 동일한 이름으로 존재한다. 예를 들면 노래.mp3와 노래.lrc 식으로 존재한다. LRC 포맷은 텍스트를 기반으로 하고 있으며 자막 파일과 비슷하다.
    • 파일포맷
      • 심플 포맷
      이 프로그램은 일반적으로 가사의 전체 행을 표시하지만, 각 단어마다 표시하는게 아니라 각 행에 하나의 태그를 생성해 주면 노래방 기계에서 볼 법한 한번에 한 단어를 표시할 수 있었다. 각 줄의 시간 태그 방식은 [mm:ss.xx] 방식으로 표시하는데 여기서 mm은 분, ss는 초, xx는 100분의 1초다. 이 때 xx는 밀리초가 아니다.
      • 강화 포맷
      강화된 LRC 포맷은 A2 미디어 플레이어의 디자이너에 의해 개발된 심플 LRC 포맷의 확장 버전이다. 이것은 mm:ss.xx 방식으로 각 단어 및 시간을 태그해 추가한다.
    • ``` **- 강화된 LRC 포맷의 예시** [mm:ss.xx] <mm:ss.xx> 첫 번째 줄 첫 번째 가사 <mm:ss.xx> 첫 번째 줄 두 번째 가사 <mm:ss.xx> ... 첫 번째 줄 마지막 가사 <mm:ss.xx> [mm:ss.xx] <mm:ss.xx> 두 번째 줄 첫 번째 가사 <mm:ss.xx> 두 번째 줄 두 번째 가사 <mm:ss.xx> ... 두 번째 줄 마지막 가사 <mm:ss.xx> ... [mm:ss.xx] <mm:ss.xx> 마지막 줄 첫 번째 가사 <mm:ss.xx> 마지막 줄 두 번째 가사 <mm:ss.xx> ... 마지막 줄 마지막 가사 <mm:ss.xx> ```
    • ``` **- 일반적인 예시** [00:12.00]첫 번째 가사 행 [00:17.20]두 번째 가사 행 [00:21.10]세 번째 가사 행 ... [mm:ss.xx]마지막 가사 행 ``` ID태그 일부 플레이어가 무시하거나 가사전에 표시할 수 있음 ``` [ar:가사 아티스트] [al:노래의 앨범] [ti:가사(노래) 제목] [au:가사 작성자] [length:음악의 길이] [by:LRC파일의 작성자] [offset:+/- ms단위로 전체 오브셋조정] [re:LRC를 작성한 플레이어나 편집기] [ve:프로그램 버전] ```

    1.2 lrc-parser(npm module)

    • 출처 : https://www.npmjs.com/package/lrc-parser-ts) / https://github.com/AdoneZ/lrc-parser#readme
    • Parse LRC file into json format, works on both node and browser → LRC 파일을 json 형식으로 구문 분석하고, 노드와 브라우저 모두에서 작동한다.
    • Usage
      • node.js
      • const lrcParser = require('lrc-parser') const fs = require('fs') fs.readFile('havana.lrc', (err, data) => { const data = lrcParser(data.toString('utf8')) })
      • Browser
      • const file = ... const fileReader = new FileReader() fileReader.addEventListener('load', e => { const data = lrcParser(e.target.result) }) fileReader.readAsText(file)

    1.3 Sync Audio With Text Using JavaScript

    var audioSync = function (options) {
    
        var audioPlayer = document.getElementById(options.audioPlayer);
        var subtitles = document.getElementById(options.subtitlesContainer);
        var syncData = [];
        var rawSubTitle = "";
        var convertVttToJson = require('vtt-json');
    
        var init = function() {
            return fetch(new Request(options.subtitlesFile))
                    .then(response => response.text())
                    .then(createSubtitle)
        }();
    
        function createSubtitle(text)
        {
            var rawSubTitle = text;
            convertVttToJson(text)
            .then((result) => {
                var x = 0;
                for (var i = 0; i < result.length; i++) { //cover for bug in vtt to json here
                    if (result[i].part && result[i].part.trim() != '') {
                        syncData[x] = result[i];
                        x++;
                    }
                }
            });
        }
    
        audioPlayer.addEventListener("timeupdate", function(e){
            syncData.forEach(function(element, index, array){
                var el;
                if( (audioPlayer.currentTime*1000) >= element.start && (audioPlayer.currentTime*1000) <= element.end ) {
    
                    while(subtitles.hasChildNodes())
                        subtitles.removeChild(subtitles.firstChild)
    
                    el = document.createElement('span');
                    el.setAttribute("id", "c_" + index);
                    el.innerText = syncData[index].part + "\\n";
                    el.style.background = 'yellow';
                    subtitles.appendChild(el);
    
                }
            });
        });
    }
    
    module.exports = audioSync;
    

    댓글