YMM4のセリフ入力自動化してみた

# はじめに
:::note info
初めて書いた記事なので、わかりにくいかもしれませんがご容赦ください。

また、最新版を確認したい場合や、ZIPファイルでインストールしたい場合は[こちら](https://github.com/akazdayo/AutoYukkuri)
:::
:::note warn
以下のソースコードはすべてGNU General Public License v3.0の元公開されています。
:::


こんにちは、あかずと申します。
突然ですが、皆さんはゆっくり実況を作成したことはありますか？
もし作成したことがある方は、セリフの入力が面倒だと感じたことが一度はあると思います。
というわけで、生声実況の動画をゆっくり実況に変換することで、
できる限りセリフ入力の作業を楽にしていきます。

# 環境
| もの | バージョン |
|:-:|:-:|
| Python  |  3.10.9 |
| YMM4 | 最新版 |


## インストール
```bash
$ pip install openai-whisper pykakasi
```
### PyTorchのインストール
[公式サイト](https://pytorch.org/)からご自身の環境に合ったものをインストールしてください。

### ffmpegのインストール
ffmpegのインストール方法を検索し、ご自身でインストールをお願いします。

# 準備
## プロジェクトファイルの雛形を作成する
プロジェクトファイル(.ymmp)の雛形を作成し、その雛形に合わせてセリフを自動的に追加します。
以下の通りに進めてみてください。
1. YMM4を起動します。
1. 使用する予定のキャラクターの立ち絵を適当に全て配置します。<br>(読み込むため)
1. プロジェクト直下にdefaultフォルダを作成します。
1. defaultの中にproject.jsonとして保存します。

## セリフの雛形を作成する
少し面倒ですが、以下の通りに進めてみてください。
1. YMM4を起動します。
1. お好きなキャラクターで一言だけ喋らせます。
1. defaultフォルダにitems.jsonとして保存します。
1. items.jsonを開きます。
1. TimelineのItemsのみを残し、それ以外を削除します。
1. VoiceCacheの中を空の文字列にします。
以下の様になっていたら成功です！
```json:items.json
{
  "$type": "YukkuriMovieMaker.Project.Items.VoiceItem, YukkuriMovieMaker",
  "IsWaveformEnabled": false,
  "CharacterName": "ゆっくり魔理沙",
  "Serif": "こんばんは",
  "Decorations": [],
  "Hatsuon": "こんばんわ",
...
```

# 実装
以下のコードをプロジェクト直下にgenerate.pyとして保存してください！
```python:generate.py
import whisper
import time
from pykakasi import kakasi
import json
import re
import sys


# 読み込み
def read(path):
    with open(path, "r", encoding="utf-8-sig") as file:
        return json.load(file)


# 書き込み
def save(path, data):
    with open(path, "w", encoding="utf-8") as file:
        json.dump(data, file, indent=4, ensure_ascii=False)


# 数字変換
def convert_numbers(word):
    pattern = r"\d+"
    matches = re.findall(pattern, word)
    for match in matches:
        word = word.replace(match, f"<NUMK VAL={match}>")
    return word


# ひらがなに変換
def convert_hiragana(word):
    kks = kakasi()
    converted = kks.convert(word)
    result = ""
    for converted_word in converted:
        result += f"{converted_word['hira']}"
    return convert_numbers(result)


# 入力
args = sys.argv

# 初期化
model = whisper.load_model(args[2])
default_items = read("./default/items.json")
default_project = read("./default/project.json")  # .copy()

# 処理
print("---変換開始---")
start = time.time()
result = model.transcribe(
    args[1],  # ファイルパス
    verbose=True,
    language="ja",
)
end = time.time()
print(f"---変換終了---\n変換時間{end-start}秒です。")


# 書き出し
items = []
for i, j in enumerate(result["segments"]):
    items.append(default_items.copy())
    items[i]["Serif"] = j["text"].replace("!", "")
    items[i]["Hatsuon"] = convert_hiragana(j["text"])
    items[i]["VoiceLength"] = "00:00:" + str(j["end"] - j["start"])
    items[i]["Length"] = int(j["end"] - j["start"]) * 60
    items[i]["Frame"] = int(j["start"] * 60)
    items[i]["CharacterName"] = str(sys.argv[3])

default_project["Timeline"]["Items"] = items

file_path = input("---書き出し完了---\nファイル名: ")
save(f"./export/{file_path}.ymmp", default_project)
```

# 実行
実行の前にexportフォルダを作成したら完成です:tada:
[whisperモデル一覧](https://github.com/openai/whisper#available-models-and-languages)
```bash
$ python generate.py 音声/動画のファイルパス whisperのモデル キャラ名
```

最後まで読んでいただき、ありがとうございます。
お疲れ様でした！