Stable Video DiffusionのWeb API呼び出しをPythonで実装して静止画を動画にしてみた

[Supership](https://supership.jp/)の名畑です。[魔神英雄伝ワタルの新作テレビアニメ制作決定のニュース](https://www.famitsu.com/news/202401/13331041.html)は新年早々熱すぎますね。

## はじめに

少し前にはなりますが、[Stable Diffusion](https://ja.stability.ai/stable-diffusion)の提供元である[stability.ai](https://www.stability.ai/)より興味深い情報が発信されていました。

> ・Developer Platform APIに、ビデオ生成の基盤モデルであるStable Video Diffusionを追加しました。
> ・モデルは、平均41秒以内に、25フレームの生成フレームと24フレームのFILM補間からなる2秒間のビデオを生成することができます。
> ・APIを通じたStable Video Diffusionの活用に関心のある開発者は、Stability AI Developer Platformで今すぐアクセスできます。
> 
> 参考：[Stability AI Developer Platform APIでStable Video Diffusionが利用可能になりました](https://ja.stability.ai/blog/stability-ai-developer-platform-apistable-video-diffusion)

つまりは、**静止画を元にした2秒間の動画生成をWeb API経由で行えるようになった**ということ。

これを実際に試してみましたというのが今回の記事の内容となります。
実装にはPythonを用いています。

## 注意

私はとりあえず試してみたかったのでWeb API経由で呼び出してみましたが、ローカルで動かせます。
**Stable Video Diffusion**について詳しくは下記の情報をご覧ください。

- [GitHubリポジトリ](https://github.com/Stability-AI/generative-models)
- [Hugging Face](https://huggingface.co/stabilityai/stable-video-diffusion-img2vid)
- [研究論文](https://stability.ai/research/stable-video-diffusion-scaling-latent-video-diffusion-models-to-large-datasets)

## 自環境

macOS(Venture)です。

Pythonはすでにインストール済みです。

```shell-session
$ python --version
Python 3.10.12
```

また、**requests**を用いるためインストールしておきました。

```shell-session
$ pip install requests
```

今のバージョンは2.31.0でした。

```shell-session
$ pip show requests
Name: requests
Version: 2.31.0
以下略
```

## アカウント作成

[Developer Platform](https://platform.stability.ai/)のLoginを押すと、下部に**Don't have an account? Sign up**の記載がありますので、こちらからアカウント作成が可能です。

Google、あるいは、メールアドレスでのアカウント作成に対応しています。

## API key発行

ログイン後の[Account](https://platform.stability.ai/account/)のページで行えます。

取得したAPI keyはハードコーディングを避けるため環境変数に設定しました。
**STABILITY\_API\_KEY**という環境変数名としました。

```bash
export STABILITY_API_KEY =ここに取得したAPI keyを書く
```

## 価格

[Pricing Table](https://platform.stability.ai/pricing)に価格が掲載されています。
現在は1動画生成につき **$0.2** のようです。$1を150円とすると30円ですね。

## 元画像

![original_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/6734ae1b-122a-3df5-8f4a-1ea3ccf12c96.png)

過去記事「[Stable Diffusionの最新モデル「SDXL 1.0」と過去モデルの生成画像をひたすら比較して進化を実感してみた](https://qiita.com/nabata/items/a9b3e759bc09c20581f6)」で生成した画像を選びました。

API仕様として、画像サイズは**1024x576**、**576x1024**、**768x768**のいずれか、画像フォーマットは**image/jpeg**、**image/png**のいずれかとのことなので、今回は**768x768のPNG画像**としました。

## Pythonでのコード

[v2alpha/generationのAPI reference](https://platform.stability.ai/docs/api-reference#tag/v2alphageneration)を参考にして実装しました。

まず生成のAPIである**image-to-video**を呼び出し、それから一定時間後に結果取得用のAPIである**image-to-video/result**を呼び出しています。

元画像ファイルはこのコードと同一階層に**original\_image.png**というファイル名で保存してあります。

```python
import requests
import os
import base64
import time

api_key = os.getenv("STABILITY_API_KEY")
api_host = 'https://api.stability.ai'
original_image = "original_image.png"  # 画像ファイル名
seed = 0  # シード値(0〜2147483648)
cfg_scale = 2.5  # 画像にどれだけ忠実とするか(0〜10)
motion_bucket_id = 40  # 動画の動きの大きさ(1〜255)

# 生成API呼び出し
params = {
    "seed": seed,
    "cfg_scale": cfg_scale,
    "motion_bucket_id": motion_bucket_id,
}
file = {
    "image": (original_image, open(original_image, 'rb'), 'image/png'),
}
headers = {
        "Authorization": f"Bearer {api_key}"
    }

response = requests.post(
    f"{api_host}/v2alpha/generation/image-to-video",
    headers=headers,
    files=file,
    data=params
)
if response.status_code != 200:  # 失敗
    print(response.json())
    exit()
    
response_id = response.json()['id']
print(response_id)

# 結果取得API呼び出し 60秒間隔で3度確認
count = 0
while count < 3:
    count += 1
    time.sleep(60)

    headers["accept"] = "application/json"
    response = requests.get(
        f"{api_host}/v2alpha/generation/image-to-video/result/{response_id}",
        headers=headers
    )
    if response.status_code == 200:  # 成功
        # 動画の保存
        with open(f"./{int(time.time())}_seed_{seed}_cs_{cfg_scale}_mbi_{motion_bucket_id}.mp4", "wb") as f:
            f.write(base64.b64decode(response.json()["video"]))
        break
    else:  # 失敗
        print(response.json())
```

生成時に指定可能なパラメータは画像(image)を除くと下記の3つです。

- **seed**：シード値(0〜2147483648)
- **cfg\_scale**：画像にどれだけ忠実とするか(0〜10)
- **motion_bucket_id**：動画の動きの大きさ(1〜255)

現時点では**アルファ版**ということもあってなのか「**どんな動きをさせるか**」は指定できないため、これらのパラメータを動かして狙った動画を生成するしかなさそうです。

## 生成結果

![1704610199_seed_0_cs_2.5_mbi_40.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/c4e05476-ac9a-a2b0-878e-0bb5e3612125.gif)

実際の生成物はmp4ですが、アニメーションGIFにしたものを掲載しておきます。
容量の関係で画質は落としてあります。ですので実際はもっと鮮明です。


## パラメータを少しいじってみる

```python
seed = 0
cfg_scale = 2.5
motion_bucket_id = 255
```

**motion\_bucket\_id**を**255**にしてみました。
変化をはっきりさせたいために**最大値**を指定しました。

生成結果は以下です。

![1704615606_seed_0_cs_2.5_mbi_255.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/04fc58a8-d267-0a02-f84b-78562417c56c.gif)

確かに動きが大きくなっています。

## 最後に

生成された動画がかなり自然ですね。

今年も1年が終わる頃にはかなりの変化が訪れていそうと実感します。

## 宣伝

[SupershipのQiita Organization](https://qiita.com/organizations/supership-inc)を合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方は[Supership株式会社 採用サイト](https://supership.jp/recruit/)よりご確認ください。