Python で CORS 対応の HLS サーバをサクッと書く

# 概要

- ffmpeg でマニフェストファイル、セグメントファイルを生成する
- Python で CORS 対応の HLS サーバをサクッと書く
- hls.js の demo ページで動画を再生する

# はじめに

HLS (HTTP Live Streaming) は、動画配信を行う上で広く使われている技術です。
モイが運営するツイキャスでも利用されています。

開発を進める上ではデバッグやモックなど、サービスと切り離してHLS配信を利用したくなることがあります。
そのため、Python でミニマムなHLSサーバを実装して動作させてみます。

作業ディレクトリを作成します。

```console
$ mkdir qiita-hls; cd qiita-hls
```

# ffmpeg でマニフェストファイル、セグメントファイルを生成する

動画素材から ffmpeg を利用して、HLS 配信に必要なマニフェストファイル、セグメントファイルを生成します。

配置ディレクトリを作成します。

```console
$ mkdir resource; cd resource 
```

動画素材には Big Buck Bunny を利用させていただきます。

- https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_720p_h264.mov

> [Big Buck Bunny](https://peach.blender.org/) is licensed under the [Creative Commons Attribution 3.0 license](http://creativecommons.org/licenses/by/3.0/).
> (c) copyright 2008, Blender Foundation / www.bigbuckbunny.org

以下の ffmpeg を実行します。

```console
$ ffmpeg -i ~/Downloads/big_buck_bunny_720p_h264.mov \
-c:v copy -c:a copy \
-f hls \
-hls_time 9 \
-hls_playlist_type vod \
-hls_segment_type fmp4 \
-hls_fmp4_init_filename "init.mp4" \
-hls_segment_filename "seg.%3d.mp4" \
media.m3u8
```

`-f hls` を指定して HLS 形式の出力を行います。また `-hls_segment_type fmp4` を指定して FMP4 形式でセグメントファイルを生成します。 この `-hls_segment_type fmp4` がないと初期化セグメントが生成されないので注意です。

# HLS サーバ

`resource` からでて `qiita-hls` ディレクトリに戻ります。

```console
$ cd ..
```

以下を `srv.py` として保存します。

<details>

<summary>srv.py</summary>

```python
#!/usr/bin/env python3

import os
from http.server import HTTPServer, SimpleHTTPRequestHandler


class MyRequestHandler(SimpleHTTPRequestHandler):
    def send_acao(self):
        origin = self.headers["Origin"]
        self.send_header("Access-Control-Allow-Origin", origin)
        self.send_header("Access-Control-Allow-Headers", "*")
        self.send_header("Access-Control-Allow-Methods", "*")
        self.send_header("Access-Control-Allow-Credentials", "true")

    def send_content_type(self):
        content_type = ""
        if self.path.endswith(".m3u8"):
            content_type = "application/vnd.apple.mpegurl"
        elif self.path.endswith(".mp4"):
            content_type = "video/mp4"

        if content_type != "":
            self.send_header("Content-type", content_type)

    def do_GET(self):
        data, ok = read_resource(os.path.basename(self.path))
        if not ok:
            self.send_response(404)
            self.end_headers()
            return

        self.send_response(200)
        self.send_acao()
        self.send_content_type()
        self.end_headers()
        self.wfile.write(data)
        return

    def do_OPTIONS(self):
        self.send_response(200)
        self.send_acao()
        self.end_headers()
        return


def read_resource(basename):
    filepath = "resource/" + basename
    if not os.path.exists(filepath):
        return b"", False

    with open(filepath, "rb") as f:
        return f.read(), True


httpd = HTTPServer(("0.0.0.0", 8003), MyRequestHandler)
httpd.serve_forever()
```

CORS でのリクエストを受け入れるよう、リクエストのはじめに CORS を許可するヘッダー群を返しています。
デバッグやモックなど、開発補助を目的にしているためきちんとした検証は行っていません。

</details>

ディレクトリレイアウトはこんな感じになってるはずです。

```console
qiita-hls
├── resource
│   ├── init.mp4
│   ├── media.m3u8
│   ├── seg.000.mp4
│   ├── seg.001.mp4
│   ├── seg.002.mp4
... 省略 ...
└── srv.py
```

HLSサーバを起動します。

```console
$ python3 srv.py
```

# hls.js の demo ページで動画を再生する

以下のリンクを開き、 `http://localhost:8003/media.m3u8` を入力してエンターを押して再生が開始されればOK :tada: 

- https://hlsjs.video-dev.org/demo/