YouTubeのライブ配信をOpenCVで再生する

# TL;DR
Pythonを使って、YouTubeのライブ配信をリアルタイムでOpenCVで再生しました。
動画に音声を付けるのは難しいため、今回は動画のみの再生を試みました。
結果としては、ライブ配信の動画をOpenCVで再生することは出来ましたが、リアルタイムというほどスムーズにはいかず、再生速度が遅かったりラグがあったりしました。
今後、スムーズに再生するために改良の余地はあるので、リトライしようと思います。

![vdo_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197942/da1390b3-41eb-cdb3-8e60-800b80020afa.gif)

# ストリーミング技術 DASHについて
YouTubeのストリーミング技術はDASH(Dynamic Adaptive Streaming Over Http)とHLSを使っています。
ちなみに、HLSはApple製品でサポートされている形式です。
動画ファイルを数秒の短いセグメントに分割して配信することによって、高画質でリアルタイムに再生することができます。

# 実装
YouTubeのライブ配信を再生して、ネットワークの通信状況を確認してみると、セグメントに分割された数秒ほどの動画と音声が連続して取得されていることが分かります。
このURLリンクからwebm形式の動画やmp3形式の音声を読み込むことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197942/75938035-5cd0-3d3a-48ec-90b449b3f95b.png)

配信リンクからvideoplaybackを取得する必要があるので、youtube.com/watch?v=videoidからJavaScriptを用いて取得する。

```js
var format_url = ytInitialPlayerResponse['streamingData']['adaptiveFormats'][0]['url']
```

取得したurlのパラメータに取得したいセグメントを指定するためにsq(sequence)パラメータを追加します。
これにより、sequenceパラメータをずらしていけばmp4のセグメントを順番に取得できるようになります。

### ソースコード
threadingを用いて再生とダウンロードを並列処理できるようにした。
```python
import re, os, requests, urllib, dukpy, tempfile, cv2, threading

_sequence = ""

def update_url(streamurl, sq):
    streamurl_parse = urllib.parse.urlparse(streamurl)
    params = urllib.parse.parse_qs(streamurl_parse.query)
    params['sq'] = sq
    streamurl = urllib.parse.urlunparse(streamurl_parse._replace(query=urllib.parse.urlencode(params, doseq=True)))
    return streamurl

def cv2destroy(cap):
    cap.release()
    cv2.destroyAllWindows()
    os._exit

def request_video():
    global req, sequence
    while True:
        print("load sequence:", str(sequence))
        req = requests.get(update_url(streamurl, str(sequence)))
        sequence += 1

def play_video():
    global req, _sequence
    while True:
        if _sequence != sequence:
            with tempfile.NamedTemporaryFile(dir='./') as fp:
                fp.write(req.content)
                fp.file.seek(0)
                cap = cv2.VideoCapture(fp.name)

                while(cap.isOpened()):
                    ret, frame = cap.read()
                    if ret == True:
                        frame = cv2.resize(frame, (1280,720))
                        cv2.imshow("YouTube", frame)
                        if cv2.waitKey(25) & 0xFF == ord('q'):
                            cv2destroy(cap)
                    else:
                        break
            _sequence = sequence

youtube_live_url = input("YouTube Live link here:")

f = urllib.request.urlopen(youtube_live_url)
html = f.read().decode('utf-8')
m = re.findall(r'<script nonce="(.*?)">(.*?)</script>', html, re.DOTALL)
script = m[10][1]
js_obj = dukpy.evaljs([script, 'ytInitialPlayerResponse'])
streamurl = js_obj['streamingData']['adaptiveFormats'][0]['url']

req = requests.get(streamurl)
sequence = int(req.headers['X-Sequence-Num'])

th1 = threading.Thread(target=request_video)
th2 = threading.Thread(target=play_video)
th1.start()
th2.start()
```

# 課題
- OpenCVでの再生速度が遅いのでネックになっている部分を調査する
- ダウンロード速度が遅いのでスレッド化を改善する必要がある

ありがとうございました。

# 参考
https://qiita.com/gabby-gred/items/c1a3dbe026f83dd7e1ff

https://www.cloudflare.com/ja-jp/learning/video/what-is-mpeg-dash