LoginSignup
12

More than 5 years have passed since last update.

posted at

YouTubeの字幕をカスタマイズする

注意

YouTube APIの公式ドキュメントに載っていない設定値を利用します。
いつ使えなくなるかわかりません。

tl;dr

  1. http://video.google.com/timedtext?type=list&v={videoId} で字幕リスト取得
  2. http://video.google.com/timedtext?lang={lang}&name={name}&v={videoId} で字幕情報取得
  3. YouTube APIにプログレスイベントがないので requestAnimationFrameplayer.getCurrentTime()で時間を取得
  4. 2と3で字幕を表示
  5. APIのonApiChangeイベント検知後に player.setOption('captions', 'displaySettings', {option})で既存の字幕を非表示に

最低でも2つXMLを取得する必要がある

字幕本体は http://video.google.com/timedtext?lang={lang}&name={name}&v={videoId} にリクエストすれば取得できるが、各パラメータが完全にマッチしている必要がある。特にnameはわからないので、一度 http://video.google.com/timedtext?type=list&v={videoId} へアクセスし、リストを取得してnameを取得してからになる。面倒くさい。

import superagent from 'superagent'
const YOUTUBE_VIDEO_TIMED_TEXT_API = 'http://video.google.com/timedtext'

const lang = 'ja'
const videoId = 'videoId'
const ccList = []
let ccTrackName;

requestCCList () {
    superagent
    .get(YOUTUBE_VIDEO_TIMED_TEXT_API)
    .accept('json')
    .query({
        type: 'list',
        v: videoId,
    })
    .end(responseCCList)
}

requestCCList()から以下のXMLが返る。

(ちなみにこのサーバへのリクエストはどのドメインからも許可されているので、クロスドメイン制限はない。)

<transcript_list docid="xxxxxxxxxxxxxxxxxx">
<track id="0" name="hogehoge" lang_code="en" lang_original="English" lang_translated="English" lang_default="true"/>
<track id="1" name="ほげほげ" lang_code="ja" lang_original="日本語" lang_translated="Japanese"/>
</transcript_list>

受け取ったXMLからnameを取得して、字幕情報本体をリクエストする

responseCCList (err, res) {
    if (err) {
        throw new Error(err)
    }
    if (res.text && res.type === 'text/xml' && res.xhr && res.xhr.responseXML) {
        const body = res.xhr.responseXML
        const trackList = body.querySelectorAll('track')
        for (let i = 0, l = trackList.length; i < l; i++) {
            const trackElement = trackList[i]
            const trackLang = trackElement.getAttribute('lang_code')
            if (trackLang === this.lang) {
                ccTrackName = trackElement.getAttribute('name')
                break
            }
        }
        if (this.ccTrackName != null) {
            requestCC()
        } else {
            throw new Error('CC is not exist in ' + this.lang)
        }
    }
}

requestCC () {
    superagent
    .get(YOUTUBE_VIDEO_TIMED_TEXT_API)
    .accept('json')
    .query({
        lang: lang,
        name: ccTrackName,
        v: videoId,
    })
    .end(responseCC)
}
<transcript>
<text start="39.2" dur="5">あいうえお</text>
<text start="47.14" dur="4">かきくけこ</text>
<text start="53.65" dur="4">さしすせそ</text>
</transcript>

あとは表示時に処理しやすいように自由に配列などに正規化すればよい。

YouTube APIにプログレスイベントがない

ので requestAnimationFrame(もしくはsetTimeout)の再帰の中で player.getCurrentTime() から時間を取得する。

表示の仕方は要件次第なので割愛。

既存の字幕を非表示にする

これが一番問題だった。公式的には iframe のオプション cc_load_policy しか設定できないことになっていて、しかも cc_load_policy=1 は強制的に表示、他の値は無効で、デフォルトだとユーザの設定に依存となる。つまり、ユーザが表示させる設定にしていれば非表示にできない。

onApiChangeイベント と player.getOptions にヒント

公式によると、取得できるオプション(モジュール)は cc だけということになっていたが、どうも変更になっているようで、captionsから字幕に関する設定を取得できた。

player.getOptions() // => ["iv-module", "captions"]
player.getOptions('captions') // => ["reload", "fontSize", "track", "tracklist", "translationLanguages", "displaySettings", "sampleSubtitle"]

いろいろ試したところ、非表示のために設定できるのは以下のとおりで、フォントカラーを透明にしたり、フォントサイズをゼロにしても無効だった。

背景と文字の不透明度を 0 にすることで、なんとか非表示にできた。


const player = new YT.Player('iframeのid', {
    events: {
        onApiChange: () => {
            player.setOption('captions', 'displaySettings', {
                backgroundOpacity: 0,
                textOpacity: 0,
            })
        }
    }
})

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
12