tusプロトコルを利用してVimeoに動画をアップロードする

# 概要
動画ファイルをtusプロトコルでVimeoへの動画アップロード方法について調べた際のメモ

## アップロードの大まかな流れ
Vimeo APIを利用すれば容易にアップロードが可能なようだが、その場合アップロード処理をすべてサーバで行う必要がありそうなので、今回は一部のみVimeo APIを利用して、アップロード処理の部分については可能な限りブラウザ側で処理するようにする。

1. クライアント側でアップロードするファイルのサイズを取得しサーバに投げる
2. 渡されたファイルサイズを元にサーバ側でVimeo APIを利用してファイルアップロード用のURLを取得し、クライアントに返す
3. クライアントは返ってきたアップロードURLに対してtusプロトコルにてファイルをアップする

## tusプロトコル
https://tus.io/

公式ライブラリのソースを見る感じだと、アップロード対象のファイルをいくつかのチャンクに分割して、そのチャンクを1つずつアップしている模様で、何個目のチャンクまでをアップしたかをこちら側で把握しておくことで、何かしらの理由でアップロードが中断した場合でも途中からファイルのアップロードを再開できる模様。
アップロード中断と再開についてはそのうち試してみたい。

# アップロードのための下準備

## Vimeo API 関連の準備
1. Vimeo のアカウントを取得する
2. https://developer.vimeo.com/apps でappを作成する
3. 作成したappの管理画面からappに対してupload権限の付与をVimeoにリクエストする（判定には2,3営業日かかる）
4. upload権限についてOKが出たら、アクセストークンを発行する[^token]（生成したトークンはこのタイミングでしか確認できないためどこかにコピペしておく）
5. API利用に必要なほかの情報（_Client identifier_, _Client secrets_）をコピペする

以上でVimeo API利用に必要なVimeo上での作業が完了

[^token]: スコープを絞ったトークンをいくつか生成して使い分けたほうが良いとは思うが、検証なので全スコープを付与したトークンを生成する

### 公式ライブラリの取得と動作確認
各言語のライブラリについては下記のページでまとめられているので、利用したい言語のcodeをクリックするとGitHubのページに遷移するので、そこに記載されているインストール方法に従う。

https://developer.vimeo.com/api/libraries

今回はサーバサイドの処理はPythonで行うのでpipでライブラリをインストールする。

```bash
pip install PyVime
```

同ページに利用方法についても記載されているが、だいたい下記の通り。

```python
# vimeoライブラリのインポート
import vimeo

# クライアントの生成
client = vimeo.VimeoClient(
    token=YOUR_ACCESS_TOKEN, # 準備 4.でコピペしたトークン
    key=YOUR_CLIENT_ID, # 準備 5.でコピペしたClient identifier
    secret=YOUR_CLIENT_SECRET # 準備 5.でコピペしたClient secrets
)

# リクエストの実効
user_info = client.get('/users/{user_id}') # ユーザ情報の取得
```

Vimeo APIのリファレンスは、下のような書き方になっていて、必要なスコープ・HTTPメソッド・エンドポイント・パラメータ・ボディ・レスポンスをここから判断する。
![vimeoAPI_Add_Comment.jpg](https://qiita-image-store.s3.amazonaws.com/0/99844/11cb7050-04a0-e036-60f4-047e78f304bf.jpeg)


この図の場合は _video\_id_ で指定した動画に対してコメントを投稿するもので、

* HTTPメソッド：POST
* エンドポイント：/videos/{video_id}/comments もしくは /channels/{channel_id}/videos/{video_id}/comments
* 必要なスコープ：interact
* パラメータ：対象の動画ID
* ボディ：投稿するコメント
* レスポンス：正常終了 => 201

となり、以下のようにするとコメントを投稿出来る。

```python
import vimeo

client = vimeo.VimeoClient(
    token=YOUR_ACCESS_TOKEN,
    key=YOUR_CLIENT_ID,
    secret=YOUR_CLIENT_SECRET
)

# Bodyに渡すデータは辞書型
body = {'text': 'APIを利用して投稿したコメントです'}

response = client.post('/videos/{video_id}/comments', data=body)

# レスポンスはjsonで返ってきて下のようにすることで取得が可能
response_detail = response.json() 

# レスポンスコードを見て処理を分岐したいような場合は下のようにすることができる
if response.status_code == 201:
    # 投稿成功時の処理
else:
    # 投稿失敗字の処理
```

## tus 関連の準備
下のURLからtusのライブラリを入手する。
https://github.com/tus/tus-js-client/tree/v1.5.2

# アップロード処理の実装

サーバ側はVimeo APIを利用してアップロードURLの取得や動画情報の登録だけを行うので、bottleをつかってシンプルに済ます。

```python

from bottle import route, get, post, request
from bottle import Bottle, jinja2_template as template, static_file
import json
import vimeo

"""
POSTされたアップロードするファイルのサイズをもとに、動画アップロード用のURLを取得する。
併せてタイトルや説明文も登録しておく。
"""
@app.post('/upload')
def entry():
    contentType = request.get_header('Content-Type')
    print(f"header Content-Type: {contentType}")
    if contentType == "application/json":
        file_size = request.json['size']
        file_type = request.json['type']
        file_name = 'Tus Upload Test'
        file_description = "This video was edited through the Vimeo API's"
        if file_type == "video/mp4":
            upload_data = {'name': file_name, 'description': file_description, 'upload': {'approach': 'tus', 'size': file_size}}
            vimeo_response = client.post('/me/videos', data=upload_data)
            if vimeo_response.status_code == 200:
                print(vimeo_response.json())
                return json.dumps({"status": "OK", "upload_link": vimeo_response.json().get('upload').get('upload_link')})
                # return vimeo_response.json()
            else:
                return json.dumps({'status': 'NG', 'message': 'create video failure', 'status_code': vimeo_response.status_code})
        else:
            return json.dumps({'status': 'NG', 'message': 'file type error'})
    else:
        return json.dumps({'status': 'NG', 'message': 'content type error'})

```

ファイルサイズはMDNの以下のサイトを参考にして取得しつつ、取得したファイルサイズをサーバ側に投げる。
https://developer.mozilla.org/ja/docs/Web/API/File/Using_files_from_web_applications#Getting_information_about_selected_files

```javascript
<script>
// サーバにアップロードURLの取得を依頼する
function fileUpload(){
    var iles = document.getElementById("uploadInput").files;
    // 複数選択された場合も先頭のファイルだけをアップロード対象にする
    var obj = {
        size: files[0].size,
        type: files[0].type
    };
    fetch('./upload', {
        method: 'POST',
    headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
        body: JSON.stringify(obj)
    })
    .then(function(response){
        // レスポンスが正常に返ってきた場合は、startUpload()にアップロードUrlとファイルオブジェクトを渡す。
        if (response.ok){
            response.json().then(function(json){
            startUpload(files[0], json.upload_link);
            })
        }
    })
    .catch(
        console.error
    );
}
</script>

```

あとはtusライブラリを利用してファイルをアップロードする。
入手してきたものtusライブラリのうち実際に利用するのは /dist/tus.js のみなので、このファイルを自身の環境に合わせて配置する。
今回の例では、/static/js/に配置している。
なお、利用しているtusクライアントは v1.5.2と少し古いものなので、最新バージョンでは利用方法が変わっている可能性が高い。


```javascript
// tusライブラリのインポート
<script type="text/javascript" src="/static/js/tus.js"></script>

<script>
// サーバから返ってきたアップロードURLに動画をアップロードする
function startUpload(file, endpoint) {
    if (!file) {
        return;
    }

    // アップロード時のチャンクサイズ設定
    // 指定しなければデフォルトのチャンクサイズでアップロードされる模様（デフォルトサイズについては要調査）
    // var chunkSize = parseInt(1024, 10);

    var options = {
        endpoint: 'none',
        resume  : true,
        // chunkSize: chunkSize,
        retryDelays: [0, 1000, 3000, 5000],
        metadata: {
            filename: file.name,
            filetype: file.type
        },
        onError : function (error) {
            if (error.originalRequest) {
                if (window.confirm("Failed because: " + error + "\nDo you want to retry?")) {
                    upload.start();
                    uploadIsRunning = true;
                    return;
                }
            } else {
                window.alert("Failed because: " + error);
            }
            reset();
        },
        onProgress: function (bytesUploaded, bytesTotal) {
            var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2);
            // コンソールにアップロードの進捗を表示する
            console.log(bytesUploaded, bytesTotal, percentage + "%");
        },
        onSuccess: function () {
            var anchor = document.createElement("a");
            anchor.textContent = "Download " + upload.file.name + " (" + upload.file.size + " bytes)";
            anchor.href = upload.url;
            anchor.className = "btn btn-success";
            uploadList.appendChild(anchor);

            reset();
        }
    };

    upload = new tus.Upload(file, options);
    upload.url = endpoint;
    upload.start();
    uploadIsRunning = true;
}
</script>
```

以上



# 参考
[PHPからvimeo APIを使う（情報取得、変更、アップロード）](https://qiita.com/tsunet111/items/491f145f453fed333708)