Flet で Audio コントロール付きアプリのビルドに成功

前回アップした音声+SRT 同時再生・テキスト編集アプリ『字幕極楽丸』ですが、NumPy を不使用にすることで macOS アプリとしてビルドできるようになりました。ただ、過去の記事紹介したテンプレートを使ってコピーライト等を追加するビルド方法だとエラーで失敗します。Audio コントロールを使っているとうまくいかない様です。その後試行錯誤の末、成功ビルドパターンがわかったのでご紹介します。

念のため環境はこちら

  • Hardware: Mac Studio M2 Max 12-core CPU 30-core GPU 32GB RAM
  • OS: macOS Sonoma 14.3.1
  • Xcode: 15.3
  • Python 3.11.7
  • Flet: 0.21.2
  • Flutter 3.19.3
  • Dart: 3.3.1
  • CocoaPods: 1.15.2

まずは NumPy を使わないように変更

以前公開したコードでは、音声の再生位置に応じてスクロールさせるため、該当するテキスト位置を算出する事だけに NumPy を使用していました。具体的には、以下の 520 行目になります。これを 522行目に変更することで、NumPy の import を不要にしました (GitHub 反映済み)。3行目のインポート部分もコメントアウト(# import numpy as np) もしくは削除します。これでとりあえず flet build macos --include-packages flet_audio でアプリのビルドが確認できました。

    # Called when slider position is changed and scroll to subtitle with the nearest end_time.
    async def scroll_to(self, e):
        end_time = [item[2] for item in self.subtitles]
        # Numpy is only used below:
        #index = np.argmin(np.abs(np.array(end_time) - e))
        # Below works without using Numpy:
        index = min(range(len(end_time)), key=lambda i: abs(end_time[i]-e))
        key=str(self.subtitles[index][0])
        self.subs_view.scroll_to(key=key, duration =1000)
        self.update()

この変更による体感できるような遅延は発生していません。ガッツリ NumPy を使ったアプリをビルドしたい方は、Flet 自体の解決を待つか (Issue 報告済み)、flet pack main.py を使うかですかね (非推奨っぽいですけど)。

単純なビルドは成功、しかしテンプレートが使えない

次にコピーライト表記などを行った上でビルドしようと、テンプレートのクローンをしてファイルを編集して、でビルドを実施したところ、以下のエラーで失敗となりました。flet_audio のバージョン?依存関係?か何かの問題のようです。複数のバージョンが入り乱れて非常に難解です。

% flet build macos --build-version "1.0.1" --template flet-build-template --include-packag
es flet_audio
Creating Flutter bootstrap project...OK
Customizing app icons and splash images...OK
Generating app icons...Because flet_audio <0.20.1 depends on flet ^0.20.0 and flet_audio >=0.20.1 <0.20.2 depends on flet ^0.20.1, flet_audio <0.20.2 requires flet ^0.20.0.
And because flet_audio ^0.20.2 depends on flet ^0.20.2 and flet_audio >=0.21.0 <0.21.1 depends on flet ^0.21.0, flet_audio <0.21.1 requires flet ^0.20.0 or 
^0.21.0.
And because flet_audio >=0.21.1 <0.21.2 depends on flet ^0.21.1 and flet_audio >=0.21.2 depends on flet ^0.21.2, every version of flet_audio requires flet 
^0.20.0 or >=0.21.0 <0.22.0.
So, because fletaudioplayback depends on both flet ^0.19.0 and flet_audio any, version solving failed.


You can try the following suggestion to make the pubspec resolve:
* Try upgrading your constraint on flet: dart pub add flet:^0.21.2

Error building Flet app - see the log of failed command above.

Flet のバージョンを上げたり下げたり、Flutter やその他環境面でのバージョンを最新にしたりなどいろいろと試しましたが、最終的にはテンプレートファイルを --template で読み込むとエラーになることがわかりました。

【解決方法】テンプレートをやめてオプションを使う

どうやら Audio コントロールを使う場合はテンプレートを読み込めない?みたいなので、オプションを使用してコピーライト表記等を組み込むことで解決しました。スマートじゃ無いですが、いつかは解決すると思うので仕方なし。実際に使ったコマンドとオプションは以下となります。長いです。

flet build macos --build-version "1.0.1" --copyright "Copyright (c) 2024 Peddals.com" --product "地獄極楽丸" --include-packages flet_audio

軽く説明すると、--product に文字列を渡すとアプリ名にできます (指定しない場合は、親フォルダ名がアプリ名になります)。--include-packages flet_audio は Audio コントロールを使用する場合に必須です。あとは見たまんまです。使えるオプションのは確認こちらでどうぞ。

ちなみに、assets フォルダ以外のファイル・フォルダはビルドに関係が無いと思っていましたが、main.py と同じフォルダにあった音声ファイルなども取り込まれたようで、最初にビルドした時は非常に時間がかかり、完成したアプリも 1.7GB ほどありました (削除後は 260MB 程度)。なので、ビルドの際は専用のフォルダを作ってそこに main.py とその他必要なファイルのみ格納した状態にするのが望ましいです。

アプリとして実行した後の動き

build/macos フォルダ内にビルドされたアプリをダブルクリックで開くと、初めての場合ボクの環境では画面が描画されるまでしばらく待たされました。よく見てみるとアプリ自体の問題というより、アンチウィルスソフトが CPU を 300% ほど使って懸命にスキャンをしていたのがその理由のようです。安全性が確認された後は一瞬で開くようになりました。音声ファイルの再生中 の CPU 使用率は、おおよそ 30~40%というところです (なぜか起動時のスプラッシュが無いので、何か問題があるのかもしれません。影響ないので深追いしてないですけど)。

おまけ (Bing Copilot にコードを直してもらったやり方)

冒頭で書いた NumPy 必須のコードを書き換えた、というところですが、実は自力では無く Bing の Copilot 無料版に手伝ってもらいました。字幕極楽丸を仕上げるまでにも Python のロジック部分に何度も手伝ってもらったのですが、やっとお願いの仕方に慣れてきた感じです。具体的な情報を伝えて、何度かやりとりすることを想定して挑むと、チャット上限の 5回でも望む結果が得られやすいですね。参考まで、実際のやりとりがこちらになります。

自分

python で numpy を使っているコードがあるのですが、numpy を使わないで実装できるよう手伝ってもらえますか?

Copilot

もちろん、お手伝いします。numpyを使っているPythonのコードを教えていただけますか?そのコードを見て、numpyを使わないバージョンを提案します。よろしければ、コードを共有してください。よろしくお願いします。🙂

自分

end_time がリストで、小さい値から大きな値までの整数を持っています。e は整数です。import numpy as np でインポートしています。対象のコードはこちらです: index = np.argmin(np.abs(np.array(end_time) – e))

Copilot

numpyを使わないで同じ処理を行うPythonのコードは以下の通りです:

index = min(range(len(end_time)), key=lambda i: abs(end_time[i]-e))

AI で生成されたコード。ご確認のうえ、慎重にご使用ください。FAQ で詳細を確認

このコードは、end_timeリストの各要素とeとの絶対値の差が最小となるインデックスを見つけます。これは、元のnumpyのコードと同じ動作をします。ただし、numpyを使わないため、大きなリストに対しては実行速度が遅くなる可能性があります。ご了承ください。🙂

Image by Stable Diffusion

まあいいんですが、当初 gray hair (と glasses もかな?) に引っ張られて、恰幅の良いおじさんばかりが生成されました。jumping も無視されがち。Exclude に不要な要素をいくつか加えたらそれっぽくなりました。人物が遠いと顔面が破綻しがちで、ステップを増やしてもあんまり関係ないですね。

Date:
2024年3月31日 1:55:34

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
realistic, masterpiece, best quality, retro future, happy young guy partially gray hair with glasses jumping with big simle in front of a beautiful building

Exclude from Image:
frame, old, fat, suit

Seed:
1847693693

Steps:
50

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

Flet で作った、音声と字幕 (SRT) の同時再生・字幕編集アプリ『字幕極楽丸』のご紹介

OpenAI の音声文字起こし AI である Whisper を試したところ、その性能の高さに非常に興奮しました。無料なうえローカルで完結し、M1 mac mini 16GB RAM なら音声ファイル自体の 90% 位の時間でかなり使える文字起こしをしてくれます。すごい時代です。で、精度を高める方法を調べていろいろ試したものの、やはり 100% にはなりません。だったら逆に、人間がテキストを簡単に修正ができるアプリがあれば良いだろうとの発想から、音声を聞きながら Whisper が書き出した文章を編集できるアプリ、その名も『字幕極楽丸』を Flet で作りました。今回はとりあえずアプリの紹介として、使い方やインストール方法を説明します。コードの内容に関しては別記事にするつもりですが、(適当な英語の) コメントを入れてあるので、気になる方は GitHub を覗いてみてください。

アプリの紹介

Whisper 等の文字起こし (speech-to-text) ツールで書き出した字幕ファイルを、音声ファイルと同期しながら再生し、必要に応じてテキストを編集できるというのがこのアプリです (と言っても、現状は flet build macos コマンドで実行ファイルとして書き出すと Numpy の読み込みができないためにクラッシュします。←解決済み。また、いくつか既知の不具合もあるため、一応の完成型といういう状態で公開してます)。英名は全くひねらず『Speech + Subtitles Player』、略して SPSP (または、SPS Player)。和名は『字幕極楽丸』です。はい、ファミカセ『地獄極楽丸』インスパイアー系の命名となっています (名前だけ)。ま、どうでも良い話ですね。ロゴにはカッコイイフリーフォント (フロップデザインさんの、源界明朝) を使わせていただきました。ありがとうございます。

アプリの使い方

(実行方法は下記「アプリの実行方法」参照) Open Speech File ボタンで音声ファイルを読み込むと、同名の字幕ファイル (拡張子 .srt) もしくはテキスト (拡張子 .txt) が同じフォルダにあれば自動的に読み込み、タイムスタンプと字幕テキストを表示します。Play ボタンで音声を再生し、テキストは音声に合わせてスクロールします。タイムスタンプをクリックすると、そこへ頭出しします。テキスト部分をクリックすると修正が行えます。編集内容は Save ボタンで同一ファイルに上書きし、SRT と TXT ボタンはそれぞれ別ファイルとして書き出します。1.5x と Auto scroll のスイッチはそれぞれ、1.5 倍速再生、音声に合わせたテキストの自動スクロールをオン・オフできます (自動スクロールはタイムスタンプのある SRT 形式のみ可能)。現在既知の問題として、Open/Export as のボタンをクリックするとダイアログが開かず、アプリ全体が動作しなくなることが確認できています。編集を行っている際には頻繁に Save をクリックするようにしてください。

TXT で書き出した場合はタイムスタンプが含まれない文章のみのため、アプリ内でのオートスクロールができませんが、議事録やレポートなど本アプリ以外の様々な用途に利用できます。SRT は字幕フォーマットとしてよく使われる形式 (Wikipedia) で、音声の元データが映像であれば、DaVinci Resolve (フリーでも利用できる映像編集アプリ) 等で字幕データとして動画に取り込めます。

アプリの対象ユーザ/ユースケース

映像に字幕を埋め込みたい方が主な対象ユーザになると思います。また、Whisper 含め文字起こし AI の精度の検証を行うエンジニアや、コールセンタで通話内容をレポートにまとめるオペレータと言った方々には有用だと思います。他には、ミーティングの議事録を AI に出力させてから清書をするとか、外国語の学習にも便利に使ってもらえるでしょう (精度の違いを無視すれば、Whisper ではかなりの数の言語がサポートされています: Supported languages)。

アプリの実行方法

アプリアプリ言ってますが、現状ダブルクリックで開くアプリケーションとして書き出せないため、下記方法でコマンドラインからの実行が推奨です。無事アプリにビルドできたので、別記事にしました。テストおよびビルドは macOS のみで行っています。大きな違いは無いはずですが、Windows や Linux の方は、すみませんがよしなにお願いします。コードは GitHub に置いてあります。

https://github.com/tokyohandsome/Speech-plus-Subtitles-Player

コードをクローンし、Python の仮想環境を作り、Flet と Numpy をインストール

Python は 3.8 以降であれば大丈夫のハズです (制作環境の Python は 3.11.7)。以下例では仮想環境の作成に pipenv を使用していますが、何でもかまいません。

git clone https://github.com/tokyohandsome/Speech-plus-Subtitles-Player.git
cd Speech-plus-Subtitles-Player
pipenv --python 3.11
pipenv shell
pip install flet
pip install numpy

アプリを実行

環境ができたら、以下コマンドで字幕極楽丸を実行できます。ダブルクリックで開くアプリをビルドする方法は別記事にしました。

python main.py

音声ファイルを選択

起動後、Open Speech File ボタンをクリックして、MP3 や WAV 等の音声ファイルを選択します。macOS では初回に書類フォルダへのアクセス権を与えるか聞かれると思いますので、許可してあげてください。音声ファイルと同じフォルダに、同じファイル名で拡張子が .srt (もしくは .txt) となっているファイルがあると、自動的に読み込まれます (スクショで言うところの、kishida.m4a を開くと kishida.srt も読み込まれる)。音声ファイルを読み込んだ後ならば、手動で字幕ファイルを読み込むこともできます。

既知の不具合

クリティカルな問題は無いと思いますが、心配な方は SRT や TXT のバックアップを別の場所に保存した上でご利用ください。

  • 字幕のボタンが多くなると、ウィンドウの移動やリサイズの際にカクつきます
  • flet build macos --include-packages flet_audio 等としてアプリケーションとして書き出しても、実行時にクラッシュします  (Flet version == 0.21.2)。オートスクロールが不要であれば、import numpy as np をコメントしてもらえれば、書き出した実行ファイルも動きますNumpy を不使用にすることでビルドできるようにしました
  • Open や Export のボタンをクリックすると、ダイアログが開かず、アプリを閉じる以外できなくなることがあります (原因調査中)。セーブは頻繁に行ってください
  • macOS で再生できる MP3 のサンプルレートは 44.1KHz までのようです。それより高いサンプルレートの場合は Audacity 等で変換してください
  • SRT は本来、字幕部分が複数行あっても良いみたいですが、本アプリでは 2行以上あることを想定していません。Whisper で書き出した SRT ファイルは問題無いはずです

おまけ

細かいことは書きませんが、Youtube 等のオンライン動画を音声ファイルとしてダウンロードする方法と、Apple が Apple Sillicon 用に最適化した Whisper で音声ファイルを SRT に書き出す方法を貼っておきます。

オンラインの動画を m4a 音声ファイルとしてダウンロード

pip install yt_dlp
python -m yt_dlp -f 140 "動画ページのリンク"

Whisper で音声ファイルを SRT に書き出す (文字起こしする) Python スクリプト

macOS であれば、MLX 版 Whisper が動く環境を作り、Hugging Face から whisper-large-v3-mlx (jsonnpz) をダウンロードして mlx_models フォルダに展開します。その後、mlx-examples/whisper フォルダに以下のファイル speech2srt.py を作ります。5-6行目のフォルダ名とファイル名はそれぞれ書き換えてください。日本語以外の場合は、音声の言語に合わせて language='ja', の部分を変更してください (日本語の音声を ‘en’ で指定すると英語訳した字幕が書き出され、英語音声を ‘ja’ 指定すると日本語訳されたものが書き出されます。が、残念ながら翻訳の精度は非常に低いのでやめた方が良いでしょう)。

import whisper
import time
import os

base_dir = "音声ファイルのあるフォルダ名"
speech_file_name = "読み込みたい音声ファイル名"

start_time = time.time()
speech_file = base_dir + speech_file_name
model = "mlx_models/whisper-large-v3-mlx" 

result = whisper.transcribe(
                            speech_file, 
                            language='ja', 
                            #language='en', 
                            path_or_hf_repo=model, 
                            verbose=True,
                            #fp16=True,
                            word_timestamps=True,
                            condition_on_previous_text=False,
                            #response_format='srt',
                            append_punctuations="\"'.。,,!!??::”)]}、",
                            #append_punctuations="。!?、",
                            #initial_prompt='です。ます。した。',
                            temperature=(0.0, 0.2, 0.4, 0.6, 0.8, 1.0),
                            )

end_time = time.time()
elapsed_time = round(end_time - start_time, 1)

print('############################')
print(f"処理にかかった時間: {elapsed_time}秒")
print('############################')

def ms_to_srt_time(milliseconds):
    seconds = int(milliseconds / 1000)
    h = seconds // 3600
    m = (seconds - h * 3600) // 60
    s = seconds - h * 3600 - m * 60
    n = round(milliseconds % 1000)
    return f"{h:02}:{m:02}:{s:02},{n:03}"

subs = []
sub = []
for i in range(len(result["segments"])):
    start_time = ms_to_srt_time(result["segments"][i]["start"]*1000)
    end_time = ms_to_srt_time(result["segments"][i]["end"]*1000)
    text = result["segments"][i]["text"]

    sub = [str(i+1), start_time+' --> '+end_time, text+'\n']
    subs.append(sub)

text_file = base_dir + os.path.splitext(os.path.basename(speech_file_name))[0] + ".srt"

# Overwrites file if exists.
with open(text_file, 'w') as txt:
    for i in subs:
        for j in range(len(i)):
            txt.write('%s\n' % i[j])

後は Python で実行すれば、音声ファイルと同じフォルダに SRT ファイルが作られます。同名ファイルがあると上書きするので注意してください。

python speech2srt.py

Whisper は GPU で動きます。参考まで、Mac Studio (M2 Max 30 コア GPU) だと、大体音声の長さの 1/6 位で書き出しが完了します。

Image by Stable Diffusion

文字起こしをするために音声に集中している女性達それぞれの表情の違いが良いですね。指が怖いですが、ステップ数をいじってもこれ以上良いバランスの絵は生成できませんでした。

Date:
2024年3月24日 23:45:42

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
realistic, masterpiece, best quality, retro future, office ladies transcribing audio from record player

Exclude from Image:

Seed:
2389164678

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

© Peddals.com