Open4

Windows上でDenoからEmbeddable Pythonとdeno_pythonを使ってOpenAI Whisper使ってみる。

観測症観測症

うまく行ってしまったので記録

必要なもの

下準備

適当なディレクトリにdeno initしてプロジェクトを作成、そこにPythonのzipファイルを展開。
中にあるpython311._pthを編集してimport siteのコメントアウトを外して、以下のようにpipを導入する。

cd python-3.11.7-embed-amd64
curl https://bootstrap.pypa.io/get-pip.py
.\python.exe .\get-pip.py

ついでに、前もって使いたいライブラリをインストールする必要がある。
おそらくsetuptoolsあたりでの問題なのか、deno_python側から直接インストールすると正しくインストールできない。
今回はopenai-whisperを使用するので以下のようにする。

.\python.exe -m pip install openai-whisper

実装

これで下準備は完了、早速main.tsにコードを書いていきたいところだが、
環境変数色々いじらないといけない。
以下のようにDeno.envで設定すれば良さそう。

ただし、普通にインポートする場合は先に評価されるため、Deno.envで設定した環境変数を無視してしまう。
とりあえずDynamic Importで対応すればいいよねってことで以下のような感じになる。

import { join } from "https://deno.land/std@0.203.0/path/join.ts"

// ここはよしなに
const python_dir = "python-3.11.7-embed-amd64"
const python_lib = "python311.dll"

// 環境変数を設定
Deno.env.set("DENO_PYTHON_PATH", join(Deno.cwd(), python_dir, python_lib))
Deno.env.set("PYTHONHOME", join(Deno.cwd(), python_dir))
Deno.env.set("PYTHONPATH", join(Deno.cwd(), python_dir, "Lib", "site-packages"))

// dynamic importで読み込む
const { pip } = await import("https://deno.land/x/python@0.4.3/ext/pip.ts")

// Whisperをインポート
const whisper = await pip.import("openai-whisper")
console.log("Whisper loaded.")

// ミディアムモデルを使用する場合はこう
const model = whisper.load_model("medium")
console.log("Model loaded.")

// sample.wavを文字起こし
const result = model.transcribe("sample.wav")
console.log(result)

これでsample.wavを用意して実行すれば文字起こしができる。

deno run --unstable -A main.ts

問題点

FFIで呼び出している関係からか結構遅い気がする。
faster-whisperでも試してみたけどそんなに速くならなかった。

別にFFIじゃなくても良い場合は標準出力とか読んでいい感じにするのも手かもしれない。

さいごに

少なくとも可能性は感じる。
Whisperを組み込んだアプリを作りたかったので楽な道はないかなと探してたんだけど、意外と良い収穫になった。

あとDenoのcompileに--assetsフラグを追加する提案があったので、これが実装されたらEmbeddable Pythonごと組み込んでアプリが作れてしまうかもしれない。(ライセンス周りは要確認だが)

観測症観測症

同じ感じでfaster-whisperも試してみた。
例によって先にpip install faster-whisperが必要だ。

あと、Pythonのクラスにある値とかはvalueOf()で変換しないと駄目らしい。
以下が変更後のmain関数、kwargsの指定にはNamedArgumentが必要。

console.time("import whisper")
const whisper = await pip.import("faster-whisper")
console.timeEnd("import whisper")

console.time("load model")
const model_size = "large-v3"
const model = whisper.WhisperModel(
    model_size,
    new NamedArgument("device", "cpu"),
    new NamedArgument("compute_type", "int8"),
)
console.timeEnd("load model")

console.time("transcribe audio")
const result = model.transcribe(
    "sample.wav",
    new NamedArgument("beam_size", 5),
)
const [segments, info] = result.valueOf()
for (const segment of segments) {
    const { start, end, text } = segment.valueOf() as Segment
    console.log({ start, end, text })
}
console.timeEnd("transcribe audio")