TVerで字幕出るけどあれどうなってるんだろう
TVerで字幕のある動画を見たりするのですが、あれはどうなっているのか気になったので調べてみました。
HTTP Live Streaming (HLS) における字幕
RFC8216にはHLS全般についての定義がされており、字幕についても定義されています。
TVerで配信しているデータもこの形式に沿っていました。
プレイリストの種類
HLSでは、以下のようなプレイリストが使用されます。
それぞれUTF-8エンコードのファイルです。
マスタープレイリスト
マスタープレイリストは、複数のビデオ、オーディオ、字幕のプレイリストを関連付けるために使用されます。
ビデオプレイリスト
ビデオプレイリストは、ビデオコンテンツのセグメントを含むプレイリストです。
オーディオプレイリスト
オーディオプレイリストは、オーディオコンテンツのセグメントを含むプレイリストです。
字幕プレイリスト
字幕プレイリストは、字幕のセグメントを含むプレイリストです。
字幕はWebVTT形式で提供されることが多いようです。
プレイリストの関連
マスタープレイリストと各種プレイリストの関連は以下のような感じです:
マスタープレイリスト
├── ビデオプレイリスト1
│ ├── video_segment1.ts
│ ├── video_segment2.ts
│ └── video_segment3.ts
├── ビデオプレイリスト2
│ ├── video_segment1.ts
│ ├── video_segment2.ts
│ └── video_segment3.ts
├── オーディオプレイリスト1
│ ├── audio_segment1.ts
│ ├── audio_segment2.ts
│ └── audio_segment3.ts
└── 字幕プレイリスト1
├── subtitle_segment1.vtt
├── subtitle_segment2.vtt
└── subtitle_segment3.vtt
このように、マスタープレイリストが各種ビデオプレイリスト、オーディオプレイリスト、および字幕プレイリストを関連付けています。
TVerの字幕を読み解いてみる
TVerで、さがすから字幕ありをクリックすると、字幕がある動画がリストアップされます。
この中から適当なデータを選んで見ていきます。
動画のページを開いて、開発者ツールでネットワークタブを表示し、m3u8
でフィルタします。
一番最初に検出されるファイルがおそらくマスタープレイリストだと思われるので、最初に検出されたパスのデータを選んで表示してみるとこのようなデータが入っていました:
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-0",NAME="en (Main)",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="en",URI="https://example.com/hls/rendition.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subtitles-0",NAME="字幕",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="ja-JP",URI="https://example.com/hls/rendition.m3u8"
#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=1039500,CODECS="mp4a.40.2,avc1.4d001f",RESOLUTION=960x540,AUDIO="audio-0",CLOSED-CAPTIONS=NONE,SUBTITLES="subtitles-0"
https://example.com/hls/rendition.m3u8
#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=1039500,CODECS="mp4a.40.2,avc1.4d001f",RESOLUTION=960x540,URI="https://example.com/hls/iframe.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-1",NAME="en (Main)",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="en",URI="https://example.com/hls/rendition.m3u8"
#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=334400,CODECS="mp4a.40.2,avc1.420015",RESOLUTION=480x270,AUDIO="audio-1",CLOSED-CAPTIONS=NONE,SUBTITLES="subtitles-0"
https://example.com/hls/rendition.m3u8
#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=334400,CODECS="mp4a.40.2,avc1.420015",RESOLUTION=480x270,URI="https://example.com/hls/iframe.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-2",NAME="en (Main)",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="en",URI="https://example.com/hls/rendition.m3u8"
#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=570900,CODECS="mp4a.40.2,avc1.4d001e",RESOLUTION=640x360,AUDIO="audio-2",CLOSED-CAPTIONS=NONE,SUBTITLES="subtitles-0"
https://example.com/hls/rendition.m3u8
#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=570900,CODECS="mp4a.40.2,avc1.4d001e",RESOLUTION=640x360,URI="https://example.com/hls/iframe.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-3",NAME="en (Main)",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="en",URI="https://example.com/hls/rendition.m3u8"
#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=1669800,CODECS="mp4a.40.2,avc1.4d001f",RESOLUTION=1280x720,AUDIO="audio-3",CLOSED-CAPTIONS=NONE,SUBTITLES="subtitles-0"
https://example.com/hls/rendition.m3u8
#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=1669800,CODECS="mp4a.40.2,avc1.4d001f",RESOLUTION=1280x720,URI="https://example.com/hls/iframe.m3u8"
#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=3163600,CODECS="mp4a.40.2,avc1.640028",RESOLUTION=1920x1080,AUDIO="audio-3",CLOSED-CAPTIONS=NONE,SUBTITLES="subtitles-0"
https://example.com/hls/rendition.m3u8
#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=3163600,CODECS="mp4a.40.2,avc1.640028",RESOLUTION=1920x1080,URI="https://example.com/hls/iframe.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES
かつ、LANGUAGE="ja-JP"
のURLのデータが日本語の字幕のようです。
このURLのデータを取得してみます。
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:30
#EXTINF:30.000,
https://example.com/hls/segment0.vtt
#EXTINF:30.000,
https://example.com/hls/segment1.vtt
#EXTINF:30.000,
https://example.com/hls/segment2.vtt
#EXTINF:30.000,
https://example.com/hls/segment3.vtt
#EXTINF:30.000,
https://example.com/hls/segment4.vtt
#EXTINF:30.000,
https://example.com/hls/segment5.vtt
#EXTINF:30.000,
https://example.com/hls/segment6.vtt
#EXTINF:30.000,
https://example.com/hls/segment7.vtt
#EXTINF:30.000,
https://example.com/hls/segment8.vtt
#EXTINF:30.000,
https://example.com/hls/segment9.vtt
#EXTINF:30.000,
https://example.com/hls/segment10.vtt
:
:
#EXT-X-ENDLIST
WebVTTの字幕のURLがリストアップされていました。
EXTINFを見ると、30秒ごとに区切られているようです。
この中身を見てみます。
WEBVTT
X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:0
00:02.033 --> 00:06.561 line:60% position:17.71% align:start
<c.fgc-7.bgc-65.sdc-0>生字幕放送です。一部、字幕で</c>
00:02.033 --> 00:06.561 line:72% position:17.71% align:start
<c.fgc-7.bgc-65.sdc-0>表現しきれない場合があります。</c>
:
:
放送局によりますが、line
やposition
、align
などの属性があります。
これらはRFCに定義されていないのでTVer独自の属性だと思われます。
line
はtopからの位置、position
がalign
からの位置のような感じだと思います。
また、<c.fgc-7.bgc-65.sdc-0>
のようなタグっぽいものもTVer独自のもののようで、スピーカーを区別するためのタグのような感じでした。
まとめ
字幕のデータはWebVTT形式で提供されていることが多いようです。
NHK+の字幕もWebVTT形式でしたがかなりクセが強かったです。
NetflixやAmazon Prime等の動画配信サービスは暗号化が施されているので、字幕のデータを取得することはできませんでした。
普段何気なく見ている字幕も、HLSという技術を使って配信されていることがわかりました。
参考