HTML の動画へのキャプションと字幕の追加
他の記事で、 ブラウザーに依存しない動画プレイヤーの構築を、 HTMLMediaElement および Window.fullScreen API を使用して行う方法と、プレイヤーのスタイル設定の方法について見てきました。この記事では、同じプレイヤーと使って、WebVTT 形式および <track> 要素を用いてキャプションや字幕を追加する方法を紹介します。
この記事ではキャプションが付いた動画プレイヤーの例を参照します。この例では Blender Foundation が作成した Sintel open movie を使用します。
HTML と動画のキャプション
動画プレイヤーにキャプションをつける方法について説明する前に、私たちが知っておくべきいくつかのことについて説明します。
キャプションと字幕
キャプションと字幕は同じではありません(英語)。対象となる視聴者が大きく異なり、伝える情報も異なります。その違いがわからない場合は、その違いについて調べておくことをお勧めします。ただし、技術的には同じ方法で実装されるため、この記事の内容は両方に当てはまります。
この記事では、字幕として表示されるテキストトラックを参照します。そのコンテンツは、聴覚障害者や難聴者ではなく、映画の言語を理解するのが困難な聴者向けに作成されているためです。
<track> 要素
HTML では、<track> 要素を使用して、動画の字幕を指定することができます。この要素のさまざまな属性により、追加するコンテンツの種類や言語、そしてもちろん、実際の字幕情報が記載されたテキストファイルへの参照などを指定することができます。
WebVTT
実際の字幕データが含まれているファイルは、指定された書式に従ったテキストファイルであり、この場合は Web Video Text Tracks (WebVTT) 形式です。
動画配信事業者(Blender Foundation など)は、動画にキャプションや字幕をテキスト形式で提供していますが、通常は SubRip Text (SRT) 形式となっています。これらは、オンラインコンバーターを使用すれば、簡単に WebVTT 形式に変換することができます。
HTML および CSS への変更
この節では、動画に字幕を追加しやすくするために、前回の記事のコードに加えた変更点をまとめます。これには興味がなく、JavaScript コードやより関連性の高い CSS をすぐに確認したい場合は、字幕の実装の節に進んでください。
この例では、別の動画である Sintel を使用しています。この動画には実際に台詞が含まれているため、字幕の仕組みを示すのに適しているからです。
HTML マークアップ
前述の通り、HTML 動画に字幕ファイルを追加するには、新しい HTML の <track> 要素を使用する必要があります。実際には、英語、ドイツ語、スペイン語の 3 つの言語の字幕を保有しているため、HTML の <video> 要素内に <track> 要素を追加し、関連する 3 つの VTT ファイルすべてを参照するようにします。
<video id="video" controls preload="metadata">
<source src="/shared-assets/videos/sintel-short.mp4" type="video/mp4" />
<source src="/shared-assets/videos/sintel-short.webm" type="video/webm" />
<track
label="English"
kind="subtitles"
srclang="en"
src="/shared-assets/misc/sintel-en.vtt"
default />
<track
label="Deutsch"
kind="subtitles"
srclang="de"
src="/shared-assets/misc/sintel-de.vtt" />
<track
label="Español"
kind="subtitles"
srclang="es"
src="/shared-assets/misc/sintel-es.vtt" />
</video>
ご覧のとおり、それぞれの <track> 要素には、以下の属性が設定されています。
kindにsubtitlesという値が指定されており、ファイルに含まれるコンテンツの種類を示していますlabelは一連の字幕が何語であるかを示す値が入ります。 — 例えばEnglishやDeutschです。 — これらのラベルが画面上に表示され、ユーザーは簡単に表示する言語を選択することができます。srcに、それぞれの該当する WebVTT 字幕ファイルを指す有効な URL が割り当てられています。srclangは、それぞれの字幕ファイルのコンテンツがどの言語で書かれているかを示します。default属性が英語の<track>要素に設定されており、これにより、字幕が有効になっており、ユーザーが特定の選択を行っていない場合に、これが使用するデフォルトの字幕ファイル定義であることをブラウザーに示します。
<track> 要素を追加したことに加え、今後作成する字幕メニューを制御するための新しいボタンも追加しました。その結果、動画のコントロールは次のように表示されるようになりました。
<div id="video-controls" class="controls" data-state="hidden">
<button id="play-pause" type="button" data-state="play">再生/一時停止</button>
<button id="stop" type="button" data-state="stop">停止</button>
<div class="progress">
<progress id="progress" value="0">
<span id="progress-bar"></span>
</progress>
</div>
<button id="mute" type="button" data-state="mute">消音/解除</button>
<button id="vol-inc" type="button" data-state="vol-up">音量↑</button>
<button id="vol-dec" type="button" data-state="vol-down">音量↓</button>
<button id="fs" type="button" data-state="go-fullscreen">全画面</button>
<button id="subtitles" type="button" data-state="subtitles">
<span>CC</span>
</button>
</div>
CSS の変更
CSS については、前回バージョンからほとんど変更はありません。ただし、新しいボタンを考慮して、1 ヶ所だけ width: calc(100% / 6) を width: calc(100% / 7) に置き換えています。また、figure に position: relative を設定し、サブタイトルメニューのポップアップをその figure を基準にして配置できるようにしました。
キャプションボタンには画像が使用されていないため、次のようにスタイル設定されています。
.controls button[data-state="subtitles"] {
padding: 2px;
}
.controls button[data-state="subtitles"] span {
max-width: 100%;
width: 2rem;
height: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: bold;
color: #666666;
background-color: black;
border-radius: 4px;
}
同時に、追加の JavaScript 実装に向けた CSS の変更もいくつかありますが、これらについては下記で適切な場所で説明します。
字幕の実装
動画の字幕をアクセスするための操作の多くは、JavaScript を中心に展開されます。動画のコントロールと同様に、ブラウザーが HTML の動画字幕に対応している場合、ネイティブの一連のコントロール内に字幕をアクセスするためのボタンが指定されています。しかし、ここでは独自の動画コントロールを定義しているため、このボタンは非表示になっており、独自にボタンを定義する必要があります。
ブラウザーによって対応状況が異なるため、可能な限りそれぞれのブラウザーで統一感のある UI を実現するよう努めていきます。ブラウザーの互換性に関する課題については、まだあります。
初期セットアップ
他のすべてのボタンと同様、まず最初に行う必要があることの 1 つは、字幕ボタンのハンドルを取得することです。
const subtitles = document.getElementById("subtitles");
同時に、ブラウザーがデフォルトで字幕を有効にしてしまう場合に備え、最初はすべての字幕をオフにしておきます。
for (const track of video.textTracks) {
track.mode = "hidden";
}
video.textTracks プロパティには、その動画に添付されているすべてのテキストトラックが配列として含まれています。これらを 1 つずつ順に処理し、それぞれの mode を hidden に設定します。
メモ: WebVTT API を使用すると、<track> 要素を使用して HTML 動画に定義されたすべてのテキストトラックにアクセスできます。
キャプションメニューの構築
私たちの目標は、先ほど追加した subtitles ボタンを使用して、ユーザーが字幕を表示させる言語を選択したり、字幕を完全にオフにしたりすることができるメニューを表示することです。
ボタンを追加しましたが、このボタンに何らかの動作をする前に、それに対応するメニューを作成する必要があります。このメニューは動的に生成されるため、後で動画のマークアップ内の <track> 要素を編集することで、言語を追加したり除去したりすることが可能です。
必要なのは、動画の textTracks を走査し、そのプロパティを読み取り、それに基づいてメニューを構築することだけです。
const subtitleMenuButtons = [];
let subtitlesMenu;
if (video.textTracks) {
const df = document.createDocumentFragment();
subtitlesMenu = df.appendChild(document.createElement("ul"));
subtitlesMenu.className = "subtitles-menu";
subtitlesMenu.appendChild(createMenuItem("subtitles-off", "", "Off"));
for (const track of video.textTracks) {
subtitlesMenu.appendChild(
createMenuItem(
`subtitles-${track.language}`,
track.language,
track.label,
),
);
}
videoContainer.appendChild(subtitlesMenu);
}
このコードは、documentFragment を生成します。これは、字幕メニューが含まれている順序なしリストを格納するために使用されます。まず、ユーザーがすべての字幕をオフにすることができるオプションを追加し、次に各テキストトラック用のボタンを追加して、それぞれの言語とラベルを読み取ります。
それぞれのリストアイテムとボタンの作成は、次のように定義した createMenuItem() 関数によって行います。
function createMenuItem(id, lang, label) {
const listItem = document.createElement("li");
const button = listItem.appendChild(document.createElement("button"));
button.setAttribute("id", id);
button.className = "subtitles-button";
if (lang.length > 0) button.setAttribute("lang", lang);
button.value = label;
button.setAttribute("data-state", "inactive");
button.appendChild(document.createTextNode(label));
button.addEventListener("click", (e) => {
// すべてのボタンを無効にする
subtitleMenuButtons.forEach((button) => {
button.setAttribute("data-state", "inactive");
});
// 有効にする言語を探す
const lang = button.getAttribute("lang");
for (const track of video.textTracks) {
// 'subtitles-off' ボタンについては、まず最初の条件が一致しないため、すべての字幕がオフになる
if (track.language === lang) {
track.mode = "showing";
button.setAttribute("data-state", "active");
} else {
track.mode = "hidden";
}
}
subtitlesMenu.style.display = "none";
});
subtitleMenuButtons.push(button);
return listItem;
}
この関数は、必要な <li> および <button> 要素を作成し、字幕メニューのリストに追加できるようそれらを返します。また、関連する字幕セットの表示・非表示を切り替えるために、ボタンに必要なイベントリスナーを設定します。これは、対象の字幕の mode 属性を showing に設定し、その他の字幕を hidden に設定することで行われます。
メニューが作成されると、videoContainer の最下部に DOM へ挿入されます。
当初、メニューはデフォルトで非表示になっているため、表示・非表示を切り替えるには、字幕ボタンにイベントリスナーを追加する必要があります。
subtitles.addEventListener("click", (e) => {
if (subtitlesMenu) {
subtitlesMenu.style.display =
subtitlesMenu.style.display === "block" ? "none" : "block";
}
});
字幕メニューの CSS
同時に、新しく生成した字幕メニューに、基本的なスタイル設定を追加しました。
.subtitles-menu {
display: none;
position: absolute;
bottom: 3rem;
right: 0;
background: #666666;
list-style-type: none;
margin: 0;
width: 100px;
padding: 10px;
}
.subtitles-menu li {
padding: 0;
text-align: center;
}
.subtitles-menu li button {
border: none;
background: black;
color: white;
cursor: pointer;
width: 90%;
padding: 2px 5px;
border-radius: 2px;
}
表示される字幕のスタイル設定
あまり知られておらず、対応状況も不十分な WebVTT の機能の一つに、個々の字幕(テキストキューと呼ばれるもの)のスタイルを CSS 経由で設定する機能があります。
::cue 擬似要素は、定義されたあらゆるキューに一致するため、個々のテキストトラックのキューを対象としてスタイル設定するための鍵となります。テキストキューに適用できる CSS プロパティはごくわずかです。
coloropacityvisibilitytext-decorationtext-shadowbackground一括指定プロパティoutline一括指定プロパティfont一括指定プロパティ(line-heightを含む)white-space
例えば、テキストトラックのキューの文字色を変更するには、次のように記述します。
::cue {
color: #cccccc;
}
WebVTT ファイルが voice span を使用している場合、キューを特定の "voice" を持つものとして定義することができます。
0 00:00:00.000 --> 00:00:12.000 <v Test>[Test]</v>
そうすれば、この特定の 'voice' は同様にスタイルを設定できるようになります。
::cue(v[voice="Test"]) {
color: white;
background: #0095dd;
}