tv

民放のみならず、最近ではNHKの一部も対応するようになった公式見逃し配信ポータルサイトTVer。ほとんどの番組はGYAOでも配信されているが、放送局独自の見逃しサービス以外ではここでしか見られないものも一部ある

しかしGYAOとは違ってTVerはyoutube-dlに対応していないので、そのままではmpv等のメディアプレーヤーで視聴したり、ローカルに保存して後から見たりできない。動画自体はHLSで配信されているようなので、ブラウザのリクエストを覗いてm3u8ファイルのURLを検索しmpvでURLを開けば視聴できるが、面倒だ。

m3u8ファイルを検索する様子
m3u8ファイルを検索する様子

このm3u8ファイルだが、TVerではmanifest.prod.boltdns.netというドメインから配信されている。なんだか見覚えがあると思ったらGYAOと同じだ。実はGYAOもm3u8ファイルがmanifest.prod.boltdns.netから配信されている。GYAOはbrightcoveというプラットフォームを利用しているので、つまりTVerもbrighcoveを利用しているということだ。

youtube-dlbrightcaveに対応しているので、うまくやればTVerもイケると思いyoutube-dlのpythonで書かれたbrightcave用エキストラクターTVerのplayer jsなどを見ながらアレコレ試していたらyoutube-dlからTVerが利用できるようになった。

例えば下のサンプルをtver.pyとして保存してmpv "$(./tver.py https://tver.jp/episode/1234567)"youtube-dl -o "あとで見る.%(ext)s" "$(./tver.py https://tver.jp/episode/1234567)"のようにして使うと便利。

#!/usr/bin/env python3

"""
CC0で公開する https://creativecommons.org/publicdomain/zero/1.0/deed.ja
"""

import re
from urllib.request import urlopen
import argparse

def extract(htm):
    """tverのhtmlからidなどを抽出する"""

    pattern = r"""^\s+addPlayer\(\n"""\
    r"""\s+'(?P<player_id>[\da-zA-Z]*)',\n"""\
    r"""\s+'(?P<player_key>[\da-zA-Z]*)',\n"""\
    r"""\s+'(?P<catchup_id>[\da-zA-Z]*)',\n"""\
    r"""\s+'(?P<publisher_id>[a-z\d]*)',\n"""\
    r"""\s+'(?P<reference_id>[\da-zA-Z_-]*)',\n"""\
    r"""\s+'(?P<title>[^']+)',\n"""\
    r"""\s+'(?P<sub_title>[^']*)',\n"""\
    r"""\s+'(?P<service>[a-z]+)',\n"""\
    r"""\s+'(?P<service_name>[^']+)',\n"""\
    r"""\s+(?P<sceneshare_enabled>true|false),\n"""\
    r"""\s+(?P<share_start>\d+)"""

    m = re.search(pattern, htm, re.M)

    if not m:
        raise Exception("ids not found")
    else:
        data = m.groupdict()
        url = ""

        if data["service"] == "cx" and data.get("publisher_id", ""):
            #フジテレビオンデマンド
            url = f'https://i.fod.fujitv.co.jp/abr/pc_html5/{data["publisher_id"]}.m3u8'
        
        #reference_id, publisher_id, player_keyが必要
        elif data.keys() >= {"reference_id", "publisher_id", "player_key"}: 
            url = f'http://players.brightcove.net/{data["publisher_id"]}/{data["player_key"]}_default/index.html?videoId=ref:{data["reference_id"]}'
        else:
            raise Exception("one of ids not found")

        return url


def dl(url):
    """getでリスエストを出してレスポンスを返す"""
    with urlopen(url) as req:
        return req.read().decode('utf-8')

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("urls", nargs="+", help="tver url")

    args = parser.parse_args()

    for url in args.urls:
        html = dl(url)
        print(extract(html))

ちなみに正規表現のtitlesub_titleというグループで番組名や各エピソードタイトルをキャプチャしているのでyoutube-dlに渡して保存する場合に利用してもいいかもしれません。楽しい📺ライフを!