DOKを使ってText2Video-Zeroでテキストから動画を生成する

[DOK](https://www.sakura.ad.jp/koukaryoku-dok/)はコンテナー型のGPUサービスで、NVIDIA V100やH100を実行時間課金で利用できるサービスです。

[コンテナー型GPUクラウドサービス 高火力 DOK（ドック） | さくらインターネット](https://www.sakura.ad.jp/koukaryoku-dok/)

今回はこのDOKを使って、Text2Video-Zeroにてテキストから動画を生成してみます。

## 結果

結果は以下のようになっています。4FPSがデフォルトなので、別途フレーム補間は必要です。

![text2video_A_horse_galloping_on_a_street.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/df8ab568-ff7f-4c11-07b1-c0ab4ceef8f4.gif)


## とりあえず試す

学習済みのデータを使って実行してみたい方は、DOKにて新しいタスクを作成し、以下の情報を入力してください。

| 項目     | 設定                                                          |
| -------- | ------------------------------------------------------------- |
| イメージ | dok-handson.sakuracr.jp/text2video                            |
| 環境変数 | PROMPT=A horse galloping on a street<br />FPS=4<br />LENGTH=8 |

PROMPTで指定したテキストに対して動画を生成します。FPSはフレームレート、LENGTHは秒数です。処理は5分弱で終わります（V100の場合。Dockerイメージの準備などもあるので、実際の実行時間はもっと短いはずです）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/64c1657c-0530-0953-bf98-df13d06b529e.png)


## コンテナイメージの作成と登録

上記タスクで利用したDockerイメージを作成する手順は以下の通りです。ベースとしては、[Picsart\-AI\-Research/Text2Video\-Zero](https://github.com/Picsart-AI-Research/Text2Video-Zero)を使っています。

完成版は[goofmint/dok\-text2video](https://github.com/goofmint/dok-text2video)にありますので、実装時の参考にしてください。

### 必要なファイルの取得

[上記リポジトリ](https://github.com/Picsart-AI-Research/Text2Video-Zero)で、処理に必要なファイルを取得します。

- __assets__
- annotator
- gradio_utils.py
- model.py
- requirements.txt
- text_to_video_pipeline.py
- utils.py

### requirements.txt の修正

DOKで実行する際に必要なライブラリを追加します。 `huggingface_hub` も必要でした。

```txt
huggingface_hub==0.25.0
boto3
```

### Dockerfile の作成

Dockerfileの内容は以下の通りです。必要なライブラリをインストールし、ファイルをコピーしています。また、 `requirements.txt` を使ってライブラリをインストールしています。

```Dockerfile
FROM nvidia/cuda:12.5.1-runtime-ubuntu22.04
ENV DEBIAN_FRONTEND=noninteractive

# 必要なパッケージのインストール
# /appはアプリのディレクトリ、/opt/artifactはアウトプット先のディレクトリ
RUN apt-get update && \
    apt-get install -y \
        git \
        git-lfs \
        python3 \
        python3-pip \
	libgl1-mesa-glx \
	libgl1-mesa-dev \
	libglib2.0-0 \
      && \
    mkdir /app /opt/artifact && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app
# 必要なファイルのコピー
COPY * /app/
COPY annotator/ /app/annotator/
COPY __assets__/ /app/__assets__/
# 必要なライブラリのインストール
RUN pip install -r requirements.txt
RUN pip cache purge

# 実行スクリプト（後で作成）
COPY runner.py /app/
# Dockerコンテナー起動時に実行するスクリプト（後で作成）
COPY docker-entrypoint.sh /
# 実行権限を付与
RUN chmod +x /docker-entrypoint.sh /

WORKDIR /
# Dockerコンテナー起動時に実行するスクリプトを指定して実行
CMD ["/bin/bash", "/docker-entrypoint.sh"]
```

### docker-entrypoint.sh の作成

`docker-entrypoint.sh` はDockerコンテナー起動時に実行するスクリプトです。ここでは環境変数をチェックして、 `runner.py` を呼び出します。以下の内容で作成します。

```bash
#!/bin/bash

set -ue

export TZ=${TZ:-Asia/Tokyo}

# アウトプット先ディレクトリ（自動付与） /opt/artifact固定です
if [ -z "${SAKURA_ARTIFACT_DIR:-}" ]; then
  echo "Environment variable SAKURA_ARTIFACT_DIR is not set" >&2
  exit 1
fi

# DOKのタスクID（自動付与）
if [ -z "${SAKURA_TASK_ID:-}" ]; then
  echo "Environment variable SAKURA_TASK_ID is not set" >&2
  exit 1
fi

# プロンプト
if [ -z "${PROMPT:-}" ]; then
  echo "Environment variable PROMPT is not set" >&2
  exit 1
fi

if [ -z "${FPS:-}" ]; then
  FPS=4
fi

if [ -z "${LENGTH:-}" ]; then
  LENGTH=8
fi

# S3_はすべてboto3用の環境変数です
pushd /app
  python3 runner.py \
	  --id="${SAKURA_TASK_ID}" \
	  --output="${SAKURA_ARTIFACT_DIR}" \
	  --prompt="${PROMPT}" \
	  --fps="${FPS}" \
	  --length="${LENGTH}" \
	  --s3-bucket="${S3_BUCKET:-}" \
	  --s3-endpoint="${S3_ENDPOINT:-}" \
	  --s3-secret="${S3_SECRET:-}" \
	  --s3-token="${S3_TOKEN:-}"
popd
```

### runner.py の作成

runner.py は実際に処理を行うスクリプトです。以下の内容で作成します。

```python
import torch
from model import Model
import boto3
import argparse

def main():
    # この中に実装します

if __name__ == "__main__":
    main()    # メイン関数を実行
```

#### パラメータの取得

`docker-entrypoint.sh` から渡されたパラメータを取得します。

```python
arg_parser = argparse.ArgumentParser()

arg_parser.add_argument(
    '--output',
    default='/opt/artifact',
    help='出力先ディレクトリを指定します。',
)
arg_parser.add_argument(
    '--prompt',
    help='プロンプトです',
)
arg_parser.add_argument(
    '--id',
    default='',
    help='タスクIDを指定します。',
)
arg_parser.add_argument(
    '--fps',
    default='4',
    help='FPSです。',
)
arg_parser.add_argument(
    '--length',
    default='8',
    help='動画の長さです。',
)
arg_parser.add_argument('--s3-bucket', help='S3のバケットを指定します。')
arg_parser.add_argument('--s3-endpoint', help='S3互換エンドポイントのURLを指定します。')
arg_parser.add_argument('--s3-secret', help='S3のシークレットアクセスキーを指定します。')
arg_parser.add_argument('--s3-token', help='S3のアクセスキーIDを指定します。')

args = arg_parser.parse_args()
```

また、モデルを読み込む処理も追加します。

```python
model = Model(device = "cuda", dtype = torch.float16)
```

#### main 関数の実装

main関数では、動画を生成するパスを作成し、後述する `txt2video` 関数を呼び出します。また、S3にアップロードする処理も追加しています。

```python
def main():
    # この中に実装します
    file_path = f"{args.output}/{args.id}.mp4"
    print(f"Save to {file_path}")
    txt2video(file_path, args.prompt, int(args.fps), int(args.length))
    if args.s3_token and args.s3_secret and args.s3_bucket:
        # S3クライアントの作成
        s3 = boto3.client(
            's3',
            endpoint_url=args.s3_endpoint if args.s3_endpoint else None,
            aws_access_key_id=args.s3_token,
            aws_secret_access_key=args.s3_secret,
        )
        # ファイルアップロード
        s3.upload_file(file_path, args.s3_bucket, os.path.basename(file))
```    

`txt2video` 関数は以下の通りです。 `model.process_text2video` で動画を生成しています。

```python
def txt2video(file_path, prompt, fps, length):
  params = {"t0": 44, "t1": 47 , "motion_field_strength_x" : 12, "motion_field_strength_y" : 12, "video_length": length}
  model.process_text2video(prompt, fps = fps, path = file_path, **params)
```

#### 全体の処理

`runner.py` の全体の処理は以下の通りです。

```python
import torch
from model import Model
import boto3
import argparse

arg_parser = argparse.ArgumentParser()

arg_parser.add_argument(
    '--output',
    default='/opt/artifact',
    help='出力先ディレクトリを指定します。',
)
arg_parser.add_argument(
    '--prompt',
    help='プロンプトです',
)
arg_parser.add_argument(
    '--id',
    default='',
    help='タスクIDを指定します。',
)
arg_parser.add_argument(
    '--fps',
    default='4',
    help='FPSです。',
)
arg_parser.add_argument(
    '--length',
    default='8',
    help='動画の長さです。',
)
arg_parser.add_argument('--s3-bucket', help='S3のバケットを指定します。')
arg_parser.add_argument('--s3-endpoint', help='S3互換エンドポイントのURLを指定します。')
arg_parser.add_argument('--s3-secret', help='S3のシークレットアクセスキーを指定します。')
arg_parser.add_argument('--s3-token', help='S3のアクセスキーIDを指定します。')

args = arg_parser.parse_args()


model = Model(device = "cuda", dtype = torch.float16)

def txt2video(file_path, prompt, fps, length):
  params = {"t0": 44, "t1": 47 , "motion_field_strength_x" : 12, "motion_field_strength_y" : 12, "video_length": length}
  model.process_text2video(prompt, fps = fps, path = file_path, **params)

def main():
    # この中に実装します
    file_path = f"{args.output}/{args.id}.mp4"
    print(f"Save to {file_path}")
    txt2video(file_path, args.prompt, int(args.fps), int(args.length))
    if args.s3_token and args.s3_secret and args.s3_bucket:
        # S3クライアントの作成
        s3 = boto3.client(
            's3',
            endpoint_url=args.s3_endpoint if args.s3_endpoint else None,
            aws_access_key_id=args.s3_token,
            aws_secret_access_key=args.s3_secret,
        )
        # ファイルアップロード
        s3.upload_file(file_path, args.s3_bucket, os.path.basename(file))
    
if __name__ == "__main__":
    main()    # メイン関数を実行
```

### Dockerイメージのビルド

上記の内容で、Dockerイメージをビルドします。Linux環境などで行います。

#### コンテナレジストリの用意

Dockerイメージを登録するコンテナレジストリを作成します。さくらのクラウドではLAB機能で、コンテナレジストリを提供しています。さくらのクラウドにログインしたら `さくらのクラウド` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e247a30c-bbc7-fb7e-136d-71ee2a795533.png)

左側のメニューの `LAB` の中にある `コンテナレジストリ` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/25d041f2-9039-fec3-77c6-88edf5afa936.png)

`追加` を押して、コンテナレジストリを作成します。最低限、以下の入力が必要です。

| 項目                 | 設定                                                                      |
| -------------------- | ------------------------------------------------------------------------- |
| 名前                 | 分かりやすい、任意の名前を入力してください                                |
| コンテナレジストリ名 | ドメイン名に使われます。以下では、 `EXAMPLE.sakuracr.jp` として説明します |
| 公開設定             | Pullのみとします                                                          |

#### ユーザーの作成

コンテナレジストリを作成したら、作成したコンテナレジストリを一覧でダブルクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/6425f493-0ce1-db62-f322-64a51867ce58.png)

詳細表示にて、ユーザータブをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/79bdc51c-ea66-8d8f-c801-8f54346cf960.png)

追加ボタンを押し、ユーザーを作成します。 `YOUR_USER_NAME` と `PASSWORD` は任意のものを指定してください。

| 項目           | 設定           |
| -------------- | -------------- |
| ユーザー名     | YOUR_USER_NAME |
| パスワード     | YOUR_PASSWORD  |
| ユーザ権限設定 | All            |

#### Dockerイメージのビルド

DockerイメージはLinuxで行います。今回はUbuntu 24.04を使っています。Dockerが使える環境であれば、Windows + WSL2でも問題ありません。macOSの場合、アーキテクチャが異なるので動かせないかも知れません（未検証です）。

`EXAMPLE.sakuracr.jp` の部分は、作成したコンテナレジストリのドメイン名に置き換えてください。

```bash
sudo docker build -t EXAMPLE.sakuracr.jp/text2video:latest .
```

#### コンテナレジストリへのログイン

作成したコンテナレジストリにログインします。ログインIDとパスワードが求められるので、作成したものを入力してください。

```bash
sudo docker login EXAMPLE.sakuracr.jp
```

#### イメージのプッシュ

作成したイメージをコンテナレジストリにプッシュします。イメージサイズが大きいので、数十分かかります。

```bash
sudo docker push EXAMPLE.sakuracr.jp/text2video:latest
```

### タスクを作成する

後は最初と同じようにDOKでタスクを作成、実行します。

| 項目     | 設定                                                          |
| -------- | ------------------------------------------------------------- |
| イメージ | EXAMPLE.sakuracr.jp/text2video                                |
| 環境変数 | PROMPT=A horse galloping on a street<br />FPS=4<br />LENGTH=8 |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/64c1657c-0530-0953-bf98-df13d06b529e.png)


## まとめ

今回はText2Video-Zeroを使って、DOK上で動画を作成する手順を解説しました。まずは実行できるのみ、次にDockerイメージの作成と段階的に進められるようにしています。ぜひ、試してみてください。

DOKはタスクを多数立ち上げて、後は結果を待つのみと言った使い方ができます。ぜひAI・機械学習に活用してください。

[コンテナー型GPUクラウドサービス 高火力 DOK（ドック） \| さくらインターネット](https://www.sakura.ad.jp/koukaryoku-dok/)