vtt から exo へ！Python の黒魔術で AviUtl を制覇せよ！ (YouTube や Whisper などから得られる *.vtt 字幕ファイルを AviUtl でインポートできる *.exo オブジェクトファイルに変換する)

[vtt ファイル](https://ja.wikipedia.org/wiki/WebVTT)を [exo ファイル](https://subarunote.com/make-objectfile/)に変換して [AviUtl](http://spring-fragrance.mints.ne.jp/aviutl/) へのインポートを試した際のメモです。(タイトルの前半部分は ChatGPT で生成しました)

# コード
Google Colab で以下のコードを実行することで変換できました。

```python
import webvtt
from datetime import datetime, timedelta

def frames_from_timestamp(timestamp_str):
    timestamp = datetime.strptime(timestamp_str, "%H:%M:%S.%f") - datetime(1900, 1, 1)
    seconds = timestamp.total_seconds()
    return int(seconds * 60)  # Convert to frames

def convert_vtt_to_exo(vtt_file, exo_file):
    vtt = webvtt.read(vtt_file)

    with open(exo_file, 'w', encoding='shift_jis', newline='\r\n') as file:
        file.write("[exedit]\nwidth=1920\nheight=1080\nrate=60\nscale=1\nlength={}\naudio_rate=44100\naudio_ch=2\n".format(frames_from_timestamp(vtt[-1].end)))

        for i, caption in enumerate(vtt):
            start_frame = frames_from_timestamp(str(caption.start))
            end_frame = frames_from_timestamp(str(caption.end))

            if i > 0:
                start_frame += 1

            if i < len(vtt) - 1:
                end_frame -= 1

            text = caption.text.encode('utf-16le').hex().ljust(4096, '0')
            file.write("[{0}]\nstart={1}\nend={2}\nlayer=9\noverlay=1\ncamera=0\n[{0}.0]\n_name=テキスト\nサイズ=100\n表示速度=0.0\n文字毎に個別オブジェクト=0\n移動座標上に表示する=0\n自動スクロール=0\nB=0\nI=0\ntype=4\nautoadjust=0\nsoft=1\nmonospace=0\nalign=4\nspacing_x=0\nspacing_y=0\nprecision=1\ncolor=0000ff\ncolor2=ffffff\nfont=メイリオ\ntext={3}\n[{0}.1]\n_name=標準描画\nX=150.0\nY=250.0\nZ=0.0\n拡大率=100.00\n透明度=0.0\n回転=0.00\nblend=0\n".format(i, start_frame, end_frame, text))

convert_vtt_to_exo('input.vtt', 'output.exo')
```

## Google Colab

以下の notebook を動かすことで変換を試せます。

[vtt2exo.ipynb](https://github.com/tset-tset-tset/hoge/blob/master/vtt2exo.ipynb)

# コード生成に利用したプロンプト

今回、プロンプトだけでコードを生成しようと試行錯誤しました。
上記のコードは以下のプロンプトを [OpenAI](https://chat.openai.com/) に投げて生成しています。

```
字幕ファイル vtt ファイルを、aviutl でインポートできる exo フォーマットに変換する Python コードを書いてください。
aviutl の exoフォーマットは以下のような形式で、ファイルの文字コードは Shift_JIS です。
改行コードは LF でなくて、CR + LF です。
vtt ファイルのパースには webvtt モジュールを利用してください。


[{数字}]
start={開始フレーム}
end={停止フレーム}
layer=9
overlay=1
camera=0
[{数字}.0]
_name=テキスト
サイズ=100
表示速度=0.0
文字毎に個別オブジェクト=0
移動座標上に表示する=0
自動スクロール=0
B=0
I=0
type=4
autoadjust=0
soft=1
monospace=0
align=4
spacing_x=0
spacing_y=0
precision=1
color=0000ff
color2=ffffff
font=メイリオ
text={テキスト}
[{数字}.1]
_name=標準描画
X=150.0
Y=250.0
Z=0.0
拡大率=100.00
透明度=0.0
回転=0.00
blend=0


上記のうち、{数字} は字幕ごとに 0 始まりで順に増えていき、{開始フレーム} {停止フレーム} は1秒を60フレーム換算した、整数になります。
{開始フレーム} と {停止フレーム} は前後の要素で重複しないように指定する必要があります。
プログラム側でこの問題を防ぐために、各キャプションのフレームを調整し、
前のキャプションの終了フレームと次のキャプションの開始フレームが重複しないようにしてください。
{テキスト} は字幕の内容がはいります。文字列を utf-16le でエンコードして、16進数にした文字列にしたものを入れる必要がます。
4096 バイトの固定長で、満たない部分は 0 埋めされます。以下のようなコードになります。

text.encode('utf-16le').hex().ljust(4096, '0')

また、exo ファイルの冒頭は以下のようなヘッダーパートがあります。


[exedit]
width=1920
height=1080
rate=60
scale=1
length={最後の停止フレーム}
audio_rate=44100
audio_ch=2


{最後の停止フレーム} には、最も長い停止フレームの数が入ります。
```

## プロンプトの改善と、コード出力

プロンプトを見てわかるとおり、最初に全ての条件を洗い出せたわけではありません。簡素な文で投げたあと、動くのに必要な条件を付け足すことで動くコードが出力されるように修正しています(フレームの重複部分や、改行コードの指定など)。文字コードの変換部分などは、言葉で説明するのが面倒なため、コードをそのまま指示してしまっています。

更に、これを実行しても直接冒頭の出力コードになったわけではなく、いくつか動かないコードを経由しています。例えば、`frames_from_timestamp` 関数は最初の出力では

```python
def frames_from_timestamp(timestamp):
    seconds = timestamp.total_seconds()
    return int(seconds * 60)  # Convert to frames
```

となっていて、`total_seconds` などという属性はないためエラーになります。これを、

```
AttributeError: 'str' object has no attribute 'total_seconds'

というエラーがでます
```

というプロンプトを入力することで、

```
申し訳ありません、私の誤りです。
webvttモジュールでは、`caption.start`と`caption.end`は文字列として返されます。
そのため、これらの値をフレームに変換するためには、まず時間に変換する必要があります。
以下に修正したコードを提供します。
～～～
```

修正してくれます。

コードを動かす→エラーをフィードバック、を何回か繰り返し、最終的に動くコードを出力してくれました。

# 参考文献

- [Go言語でWhisperのvttファイルをAVIUtlのexoファイルに変換するバッチを作った.pdf - Speaker Deck](https://speakerdeck.com/toranoana/qu-wei-tezuo-tutamonolt)
  - そのまま使えそうと思いましたが、公開されたプログラムを見つけられなかったため、今回作成しました
- [AviUtlのexoファイル出力時のテキストの変換 - Qiita](https://qiita.com/R_TES_/items/57ad42b7b66882c39797)
  - text パラメータのフォーマットの参考にしました