HLSとは

HLS(HTTP Live Streaming)とはAppleが開発したストリーミング動画配信プロトコルで大抵のブラウザが対応しています。一つの動画を複数のセグメントに分割し.tsファイル(セグメントファイル)として保存、そのリストを.m3u8ファイル(インデックスファイル)に保存します。これらのファイルは通常のHTTP通信で静的配信されます。つまり、キャッシュされるということです。

準備

アドオンでHeader修正

たまにCache-Control: no-storeになってたり、max-ageに極端に小さい値が設定されてたりするのでこの拡張機能でキャッシュ保存期間を伸ばします。

設定はこんな感じでOKでしょう。とにかくキャッシュする設定です。

1
2
3
4
5
6
Url Patterns: *
Action: Modify
Header Field Name: cache-control
Header Field Value: max-age=1000000000
Comment: hoge
Apply on: Response

キャッシュ容量変更

すべてキャッシュするようにしてもデフォルトのキャッシュ容量が1GB程度なので動画だとすぐにいっぱいになって古いのから消えます。そこでキャッシュ容量を拡張します。

  1. FirefoxのURLバーからabout:configにアクセスし、
  2. browser.cache.disk.smart_size.enabledを検索して、falseにします。
  3. browser.cache.disk.capacityを検索して、適当に100000000(100GB)くらいにします。
  4. Firefoxを再起動します。

普通に見る

HLS配信されている動画をFirefoxで普通に見ます。倍速で最小化しててもOKです。

URLを調べる

デベロッパーツールのネットワークタブで.m3u8で検索してインデックスファイルを探し、そのURLを書き留めておきます。また、インデックスファイルに書いてあるセグメントファイルのURLも書き留めておきます。

インデックスファイルはこんな長いやつです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-KEY:METHOD=AES-128,URI="https://manifest.example.net/license/v1/aes128/4394098882001",IV=0x2d21
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:10
#EXTINF:10.010,
https://video-cdn.example.com/media/v1/hls/v5/segment0.ts
#EXTINF:10.010,
https://video-cdn.example.com/media/v1/hls/v5/segment1.ts
#EXTINF:10.010,
https://video-cdn.example.com/media/v1/hls/v5/segment2.ts
#EXTINF:10.010,
https://video-cdn.example.com/media/v1/hls/v5/segment3.ts
#EXTINF:10.010,
https://video-cdn.example.com/media/v1/hls/v5/segment4.ts
#EXTINF:10.010,
https://video-cdn.example.com/media/v1/hls/v5/segment5.ts
#EXTINF:10.010,

ここで、セグメントファイルがたくさん書かれていることが分かりますが、セグメントファイルが絶対URLの場合と相対URLの場合があります。絶対URLだと最後にffmpegで結合する時にアクセスしてしまうので、相対パスに書き換えておきましょう。

また、EXT-X-KEY:METHOD=AES-128ではなんちゃって暗号化を指定しています。ここがhttps://の場合や、この項目がない場合は何もしなくてOKですが、面倒な場合もあるのでその時はHLS暗号化とかでググったり、がんばったりしてください。

インターネットを切る

これからの作業にインターネット接続は不要なので切断します。

キャッシュを抜く

https://github.com/shosatojp/ffcache

このプログラムを使ってFirefoxのキャッシュを抜き取ります。

Firefoxのキャッシュフォルダは/home/me/.cache/mozilla/firefox/hogehogehoge.default/cache2です。

1
2
3
4
export FFCACHE=/home/me/.cache/mozilla/firefox/hogehogehoge.default/cache2
ffcache $FFCACHE https://video-cdn.example.com/media/v1/hls/v5/index.m3u8 index.m3u8
ffcache $FFCACHE https://video-cdn.example.com/media/v1/hls/v5/segment0.ts segment0.ts
...

こんな感じでダウンロードします。手作業では無理ゲーなのでシェルスクリプト書くなり、Python書くなりします。

結合する

最後にffmpegで結合します。

1
ffmpeg -allowed_extensions ALL -i index.m3u8 -c copy -o out.mp4

実装例

GithubActionsのArtifactsで配布されているPythonバインディングを利用して実装してみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/python3
import time
import glob
from ffcache import FirefoxCache
import re
import os
import argparse
import subprocess
import shutil

parser = argparse.ArgumentParser()
parser.add_argument('prefix', type=str)
parser.add_argument('--out', '-o', type=str, default='out.mp4')
parser.add_argument('--cache', '-c', type=str, required=True)
args = parser.parse_args()

cache = FirefoxCache(args.cache)

outdir = f'cache-{time.time()}'
if not os.path.exists(outdir):
os.mkdir(outdir)

for e in cache.records:
if re.match(args.prefix, e.key):
print(e.key)
_, file = os.path.split(e.key)
e.save(os.path.join(outdir, file), True)

index = glob.glob(os.path.join(outdir, 'index*.m3u8'))[0]
subprocess.run(['ffmpeg', '-allowed_extensions', 'ALL', '-i', index, '-c', 'copy', args.out])
shutil.rmtree(outdir)