トップへ(mam-mam.net/)

Webサイトの動画で字幕を表示させる

<video>に字幕をつける

動画に字幕を入れる方法について、.vtt ファイルを用いる方法と、Javascriptを使って自力で要素をオーバーレイする方法の2つのサンプルを示します。
余談ですが、<video>動画を自動再生(autoplay)させるには、ミュート(muted)属性を付ける必要があります。

vttで字幕表示

.vttファイルを使うと字幕表示できます。
.vttファイルは以下のような字幕文字と時間を記述したテキストです。

WEBVTT

00:00.000 --> 00:02.000
開始しました。

00:02.000 --> 00:05.000
飛んでいます。

00:05.000 --> 00:08.000
操縦が下手なのでフラフラしています。

00:09.000 --> 00:14.000
ぶつからないか、墜落しないか心配です。

00:15.000 --> 00:18.000
着陸しました。

<track>タグで vtt ファイルを指定すれば字幕が表示されます。
疑似要素 ::cue を使って字幕の色や文字サイズを変えることが出来ます。

<video id="vtt"  src="./imgs/video.mp4" muted autoplay controls playsinline loop controlslist="nofullscreen" style="width:320px;height:auto;" width="1280" height="780">
  <track default kind="captions" srclang="ja" label="label" src="./imgs/video.vtt" />
</video>
<style>
  video#vtt::cue{
    font-size:20px;
    color:#fff;
    background:rgba(0,0,0, 0.7);
  }
</style>

Javascriptを使って自力で要素をオーバーレイ(被せる)

Javascriptを使って字幕の要素をビデオにオーバーレイ(ビデオに字幕を被せる)してみます。

<div id="overlay1" style="box-sizing:border-box;position:relative;width:400px;max-width:100%;">
  <video src="./imgs/video.mp4" muted controls playsinline loop controlslist="nofullscreen"
         style="width:100%;height:auto;margin:auto;display:block;position:relative;" width="1280" height="780">
  </video>
  <div style="position:absolute;margin:auto;top:0;pointer-events:none;">
    <!-- ここに字幕をオーバーレイさせる -->
  </div>
</div>
<script>
  class TAddTextToVideo{
    setOverlay(){
      let w="",h="";
      if(this.ow>=this.oh){
        w="100%";
        h="auto";
      }else{
        if(this.oh>400){
          h="400px";
        }else{
          h=this.oh+"px";
        }
        w="auto";
      }
      this.video.style.width=w;
      this.video.style.height=h;
      let style=window.getComputedStyle(this.video);
      this.overlay.style.width=style.width;
      this.overlay.style.height=style.height;
      this.overlay.style.marginLeft=style.marginLeft;
      for(let i=0;i<this.elms.length;i++){
        this.elms[i].style.fontSize = 6+parseInt(style.width)/40+"px";
      }
    }
    constructor(video, overlay){
      this.tables=[
        {"min": 0.0, "max": 2.0, "left":"25%", "top":"10%", "text":"開始しました。" ,},
        {"min": 2.0, "max": 5.0, "left":"38%", "top":"20%", "text":"飛んでいます。" ,},
        {"min": 5.0, "max": 8.0, "left":"10%", "top":"40%", "text":"操縦が下手なのでフラフラしています。"   ,},
        {"min": 9.0, "max":14.0, "left":" 2%", "top":"60%", "text":"ぶつからないか<br>墜落しないか心配です。" ,},
        {"min":15.0, "max":18.0, "left":"30%", "top":"80%", "text":"着陸(墜落)しました。" ,},
      ];
      this.ow=0;
      this.oh=0;
      this.video=video;
      this.overlay=overlay;
      this.elms=[];
      for(let i=0;i<this.tables.length;i++){
        this.elms[i]=document.createElement("p");
        this.elms[i].style.display="none";
        this.elms[i].style.position="absolute";
        this.elms[i].style.left=this.tables[i].left;
        this.elms[i].style.top=this.tables[i].top;
        this.elms[i].style.margin=0;
        this.elms[i].style.padding=0;
        this.elms[i].style.background="rgba(255,255,255, 0.8)";
        this.elms[i].style.color="black";
        this.elms[i].style.pointerEvents="auto";
        this.elms[i].innerHTML=this.tables[i].text;
        this.overlay.appendChild(this.elms[i]);
      }
      //ウィンドウ リサイズ時
      window.addEventListener('resize',function(){
        this.setOverlay();
      }.bind(this));
      //再生時間が更新されているとき
      this.video.addEventListener('timeupdate',function(){
        for(let i=0;i<this.tables.length;i++){
          if(this.tables[i].min<=this.video.currentTime && this.tables[i].max>=this.video.currentTime){
            this.elms[i].style.display="block";
          }else{
            this.elms[i].style.display="none";
          }
        }
      }.bind(this));
      //再生の準備が完了したとき
      this.video.addEventListener('canplay',function(){
        this.setOverlay();
        this.video.play();
      }.bind(this));
      //メタ情報の読み込みが完了
      this.video.addEventListener('loadedmetadata',function(){
        this.ow=this.video.videoWidth;
        this.oh=this.video.videoHeight;
      }.bind(this));
    }
  }
  window.addEventListener("DOMContentLoaded",function(){
    addTextToVideo=new TAddTextToVideo(
      document.querySelector("#overlay1>video"), 
      document.querySelector("#overlay1>div")
    );
  });
</script>

Javascriptを使って自力で要素をオーバーレイ(ビデオに字幕を被せる)するが右から左へ流す

自力で字幕の要素をオーバーレイ(ビデオに被せる)しますが、右から左へ文字を流してみます。
timeupdate は0.25秒~0.5秒間隔しかイベントが飛んでこないので、setIntervalを使用しています。

<div id="overlay2" style="box-sizing:border-box;position:relative;width:400px;max-width:100%;">
  <video src="./imgs/video.mp4" muted controls playsinline loop controlslist="nofullscreen"
         style="width:100%;height:auto;margin:auto;display:block;position:relative;" width="1280" height="780">
  </video>
  <div style="position:absolute;margin:auto;top:0;pointer-events:none;overflow:hidden;">
    <!-- ここに字幕をオーバーレイさせる -->
  </div>
</div>
<script>
  class TAddScrollTextToVideo{
    setOverlay(){
      let w="",h="";
      if(this.ow>=this.oh){
        w="100%";
        h="auto";
      }else{
        if(this.oh>400){
          h="400px";
        }else{
          h=this.oh+"px";
        }
        w="auto";
      }
      this.video.style.width=w;
      this.video.style.height=h;
      let style=window.getComputedStyle(this.video);
      this.overlay.style.width=style.width;
      this.overlay.style.height=style.height;
      this.overlay.style.marginLeft=style.marginLeft;
      for(let i=0;i<this.elms.length;i++){
        this.elms[i].style.fontSize = 6+parseInt(style.width)/40+"px";
        this.tables[i].right=parseFloat(style.width);
        this.tables[i].left = - parseFloat(window.getComputedStyle(this.elms[i]).width);
      }
    }
    constructor(video, overlay){
      this.tables=[
        {"min": 0.0, "max": 2.0, "top":"10%", "text":"開始しました。" ,},
        {"min": 2.0, "max": 5.0, "top":"20%", "text":"飛んでいます。" ,},
        {"min": 5.0, "max": 8.0, "top":"40%", "text":"操縦が下手なのでフラフラしています。"   ,},
        {"min": 9.0, "max":14.0, "top":"60%", "text":"ぶつからないか<br>墜落しないか心配です。" ,},
        {"min":15.0, "max":18.0, "top":"80%", "text":"着陸(墜落)しました。" ,},
      ];
      this.ow=0;
      this.oh=0;
      this.video=video;
      this.overlay=overlay;
      this.elms=[];
      for(let i=0;i<this.tables.length;i++){
        this.elms[i]=document.createElement("div");
        this.elms[i].style.display="block";
        this.elms[i].style.position="absolute";
        this.elms[i].style.left="100%";
        this.elms[i].style.top=this.tables[i].top;
        this.elms[i].style.margin=0;
        this.elms[i].style.padding=0;
        this.elms[i].style.background="rgba(255,255,255, 0.8)";
        this.elms[i].style.color="black";
        this.elms[i].style.pointerEvents="auto";
        this.elms[i].style.whiteSpace="nowrap";
        this.elms[i].innerHTML=this.tables[i].text;
        this.overlay.appendChild(this.elms[i]);
      }
      //ウィンドウ リサイズ時
      window.addEventListener('resize',function(){
        this.setOverlay();
      }.bind(this));

      setInterval(this.interval.bind(this),33);

      //再生の準備が完了したとき
      this.video.addEventListener('canplay',function(){
        this.setOverlay();
        this.video.play();
      }.bind(this));
      //メタ情報の読み込みが完了
      this.video.addEventListener('loadedmetadata',function(){
        this.ow=this.video.videoWidth;
        this.oh=this.video.videoHeight;
      }.bind(this));
    }

    interval(){
      for(let i=0;i<this.tables.length;i++){
        if(this.tables[i].min<=this.video.currentTime && this.tables[i].max>=this.video.currentTime){
          let rate= 1-(this.video.currentTime-this.tables[i].min)/(this.tables[i].max-this.tables[i].min);
          this.elms[i].style.left = this.tables[i].left + (this.tables[i].right-this.tables[i].left)*rate + 'px';
        }else{
          //this.elms[i].style.display="none";
          this.elms[i].style.left="100%";
        }
      }
    }
  }

  window.addEventListener("DOMContentLoaded",function(){
    addScrollTextToVideo=new TAddScrollTextToVideo(
      document.querySelector("#overlay2>video"), 
      document.querySelector("#overlay2>div")
    );
  });
</script>