動画に字幕をつける
JavaScriptとHTML5のvideoタグの動画に字幕を表示する方法を紹介します。
このページでは、track要素+WebVTT形式による標準的な字幕表示に加えて、JavaScriptで字幕を自力描画する方法も掲載。
さらに、字幕が右から左にスクロールする演出付きのサンプルもあり、柔軟な字幕表現を実装したい方におすすめです。
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を使用しています。
参考:<video>動画を自動再生(autoplay)させるには、ミュート(muted)属性を付ける必要があります。
<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>
