リアルタイムで動画に字幕ファイルを合成するhtmlを作ってみた

## はじめに
動画と字幕をリアルタイムに合成するhtmlを作ったので共有させていただきます。
（webサーバ上のページから再生できるよう改修しました。）
![play_movie_with_subtitles.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1906297/e9c96c6b-0dd9-2967-bb6a-14a1d4fee4c3.png)


## サンプル
[play_movie_with_subtitles](https://play-movie-with-subtitles.netlify.app/)

## github
https://github.com/NoguchiMasafumi/play_movie_with_subtitles


## ソース
```play_movie_with_subtitles.js
function st_sub(){
    document.getElementById('file').addEventListener('change', handleFileSelect, false);
}

function sleep(second) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, second * 1000)
    })
}

//******************************* srt file time style changer ***********************************
function sec_calc(str_mat2){
    i_hh=str_mat2.substring(0,2)
    i_mm=str_mat2.substring(3,5)
    i_ss=str_mat2.substring(6,8)
    i_ms=str_mat2.substring(9,12)
    str_sec=(parseInt(i_hh*3600)+parseInt(i_mm*60)+parseInt(i_ss))+"."+(i_ms)
    int_sec=str_sec
    return int_sec
}

/**********************************/
async function handleFileSelect(ev){
    var reader = new FileReader();
    for (let i = 0; i < ev.target.files.length; i++) {
        let file = ev.target.files[i];
        s_name=file.name
        splt=s_name.split(".")
        if(splt[1]!="srt"){
            v.src = URL.createObjectURL(file);
            await sleep(1)
            v.load()
            await sleep(1)
            v.play()
            reader.onload = function(evt) {
                var subtitles;
                let subtitle = evt.target.result;
                _subtitle(subtitle);
            }
        }else{
            reader.readAsText(file,"Shift_JIS");
        }
    }
};

//**************************** display subtitle **************************************
function _subtitle(text) {
    let Subtitle = text;
    let Pattern = /(\d+)\n([\d:,]+)\s+-{2}\>\s+([\d:,]+)\n([\s\S]*?(?=\n{2}|$))/g;
    let _regExp = new RegExp(Pattern);
    if (typeof (text) != "string") throw "Sorry, Parser accept string only.";
    if (Subtitle === null) return Subtitle;
    let Parse = Subtitle.replace(/\r\n|\r|\n/g, '\n');
    let Matches;
    //*************//
    v.addEventListener("timeupdate", function(){
    document.getElementById("subtitle").style.top = document.getElementById("subtitles_position").value+"%";
    document.getElementById("subtitle").style.fontSize = document.getElementById("fnt_siz").value+"px";
    while ((Matches = Pattern.exec(Parse)) != null) {
        int_start_sec=sec_calc(Matches[2])
        int_end_sec=sec_calc(Matches[3])
        int_v_time=v.currentTime+parseFloat(document.getElementById("delay").value)
        if(int_v_time > parseFloat(int_start_sec)){
        if(int_v_time < parseFloat(int_end_sec)){
            document.getElementById("subtitle").innerHTML = Matches[4].replace('\n','<br />');
        }
        if(int_v_time > parseFloat(int_end_sec)){
            document.getElementById("subtitle").innerHTML = "";
        }
        }
    }
    },false);
}
```

```base.css
body{
    text-align: center;
    background-color:#000000;
  }

  .disp_no{display:none;}
  .inps{background-color: #8f8f8f;text-align:right;}
  .w32{width:32px;}

  #sel_file{width:100%;text-align:right;}

  #v{
    width:100%;
    margin-right: auto;
    margin-left: auto;
      margin-bottom:15px;
  }

  #files_label{
    margin-right:5px;
    color:#000000;
    font-size:12px;
    padding:0px 5px 0px 5px;
    cursor:pointer;
  }

  #subtitle{
    z-index: 3;
    left:50%;
    top:58%;
    line-height:120%;
    height:3em;
    /*display:table-cell;
    vertical-align:bottom;*/
    display:flex;
    flex-direction: column;
    justify-content: flex-end;
    position:absolute;
    text-align:center;
    transform: translate(-50%, -50%);
    color:#cccccc;
    font-size: 30px;
    font-weight: bold;
    margin-right:auto;margin-left:auto;
    text-shadow:3px 0px 1px #111, 0px 3px 1px #111, -3px 0px 1px #111, 0px -3px 1px #111, 3px 3px 1px #111, -3px 3px 1px #111, -3px -3px 1px #111, 3px -3px 1px #111;
    /*   ,4px 0px 0px #ccc, 0px 4px 0px #ccc, -4px 0px 0px #ccc, 0px -4px 0px #ccc, 4px 4px 0px #ccc, -4px 4px 0px #ccc, -4px -4px 0px #ccc, 4px -4px 0px #ccc   */
  }
```


```index.htm
<html>
    <head>
        <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com; connect-src ; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src * blob:; media-src * blob:">
        <link rel="stylesheet" href="css/base.css">
    </head>

    <body onload="st_sub()">

        <video id="v" controls autobuffer></video>
        <div id="subtitle"></div><br />
        <div id="sel_file" style="color:#8f8f8f;">
          <label for="file" id="files_label" class="inps" >
            Select srt file<input type="file" id="file" class="disp_no" name="file[]" multiple />
          </label>
          Sub Pos Top<input type="text" id="subtitles_position" value="58" class="inps w32">%
          Font<input type="text" id="fnt_siz" value="30" class="inps w32">px
          Delay<input type="text" id="delay" value="0" class="inps w32">Sec
        </div>

    </body>

    <footer>
        <script src="js/play_movie_with_subtitles.js"></script>
    </footer>
</html>

```










## 使い方
まずブラウザで表示できる動画ファイルと、字幕ファイル(srt形式)を用意し
一つのフォルダにまとめておく必要があります。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1906297/ca29b13f-a40f-d873-b039-0e31385a7099.png)
srt形式が不明な場合
<a href="https://www.google.com/search?q=srt%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%A8%E3%81%AF" target="_blank">srtファイルとは</a> などで検索してみてください。
ちなみに形式は下記のような形式
メモ帳などで編集可能です
<pre>
1
00:00:43,209 --> 00:00:44,919
やってまいりました今日という日

2
00:00:45,211 --> 00:00:48,882
1967年１月27日
大統領も声明を発表

3
00:00:49,257 --> 00:00:53,261
</pre>



index.html　を準備し　開きます
開くと
このような画面になります。
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1906297/fed5ae93-4e37-80f0-2075-dccef71b9d62.png)

右下に　Select srt file　というボタンがあるのでクリックし
先程用意したフォルダを探します
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1906297/de3bacc7-7475-acb1-032a-9311b1e16750.png)

字幕ファイルと　動画ファイル　の2つともを選択すると
字幕を合成した動画の再生が始まります
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1906297/8ce45b55-8bdf-6427-6709-2c8768f07157.png)

字幕の微調整は
右下の部分で
Sub posが　字幕の縦位置の調整になります。
半角数字の入力で即時反映です
うまい具合の位置でないとシークバーの操作ができなくなります。
![21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1906297/1292eea1-0267-53b4-7322-3c06f397754e.png)

字幕の文字の大きさ調整
半角数字入力即反映
![22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1906297/9c24c3f0-82dc-e2c5-823c-4fa5e775e7b3.png)

字幕のタイミング調整
遅くする場合は数値を入力
早くする場合は-(マイナス)付きの数字を入力
どれだけ調整してもタイミングがズレる場合は
動画のバージョン（ディレクターズカット版など）と
字幕のバージョンがあっていない可能性があります。
正しい字幕ファイルを入手するか
メモ帳などでsrtファイルを編集するなどしてみてください。
![23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1906297/fdad26ae-a379-befa-676c-8485fd111975.png)




## なぜ作ったか
動画ファイルも字幕ファイルもあるのに合わせて再生するには
アプリのインストールが必要だとのこと。
そんな大仰なことか？と疑問に思い
そのくらいならhtml+javascriptでできるのでは？と考えたためです。
で、誰かそのくらいならもう書いてるだろうと探したのですが
見当たらなかったため書きました。
コード進行が古風ですがご了承ください。