vtt ファイルを exo ファイルに変換して AviUtl へのインポートを試した際のメモです。(タイトルの前半部分は ChatGPT で生成しました)
コード
Google Colab で以下のコードを実行することで変換できました。
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 を動かすことで変換を試せます。
コード生成に利用したプロンプト
今回、プロンプトだけでコードを生成しようと試行錯誤しました。
上記のコードは以下のプロンプトを OpenAI に投げて生成しています。
字幕ファイル 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
関数は最初の出力では
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
- そのまま使えそうと思いましたが、公開されたプログラムを見つけられなかったため、今回作成しました
-
AviUtlのexoファイル出力時のテキストの変換 - Qiita
- text パラメータのフォーマットの参考にしました