寒くなったせいか早速風邪引いた。
木曜日に喉痛くなったなと思ったら夜にはふとんにもぐって暖房つけてても寒気がひどくて一睡もできない状況に。 金曜日はさすがに休みにして土日もほぼ寝転んでいた。寒気はすぐ落ち着いたが喉の痛みがなかなか治らず。
月曜は喉の痛みも落ち着いてきたしまあまあ大丈夫そうになったので出社したが、夕方あたりから結構元気になったのと引き換えに今度は鼻水と痰が絡んで咳が出るようになっちゃった。 今日休みにしたんだが(プラモの予約戦争があるため)、おとなしくしててもなかなかよくなりませんね。
しかし風邪引いて寝てたかと思いきや意外と作業が進んでる不思議。さすがにプラモ作りはしてないがプログラムが進む。 あったかくして座ってるだけだから少しくらい体調悪くてもいけるんだよね。仕事は無駄に出社させられるからきついんだが。
なおプラモ戦争は30MMと30MFがすんなり予約完了。30MSはオプションの一部だけ予約できたが、フロートユニットとエイダーコスチュームと素体2体は予約できず。キャンセル待ちと抽選に賭けよう。素体はまあいいんだけどオプションは買えないやつはほんと買いづらいから予約できてほしいんだけどな~。 コスチュームはエイダーだけなんで買えねんだ思ったけど、そっかドスケベピンクナース服だから人気なのか……。
PeerCastStationは大幅な整理が時間かかりそうなので、それを終わらせる前に.NET8に移行しようかと。 .NET6もサポート切れちゃうしね。
.NET8への移行自体は特になんも難しいところなく6.0を8.0に置き換えるだけ。まあそんな変なことやってませんし。 ただついでにいろいろ古いのを更新してしまいたい。
インストーラーがWiX3なのを4に移行してビルドしやすくしたい……と調べたらもう5が出てた。4はいつまでも出なかったのに5は早いな。まあ3→4程大きな変更はなさそうだ。 移行は大変そうに思えたがVisualStudioの拡張入れて変換をポチっとしたらそれで済んでしまった。なんか結構めんどくさいことやってたような気がしたんだが?と中身みてみたら、いつだかに整理してそんなめんどくさいことはない形式になってたようだ。それはよかった。 最終的には再確認したいが。
jQueryが古いとかなんとかめちゃくちゃ文句言われてるのもなんとかしたい。nugetパッケージで入れてるのを軒並み更新してみるが、そういやこいつらってどこにどう入ってくるんだっけ? パッケージの中身を見るとなんかContentとかScriptsてフォルダに物が入ってるっぽいけど、なんかそれっぽいの元からリポジトリにコピって追加してあるんだよな。何をどうしたんだ昔の俺は。 あとbootstrapも最新のパッケージを入れたらSolution Explorer上ではwwwrootってのが見えてその中にぶちこまれてるようになったんだが、ビルドしてもコピーされるわけでもなく、こいつらをどう扱えばいいのかさっぱりわからん。なんだこれ……?
いろいろ調べたんだが、ビルド時に勝手にコピーされるようにするにはnuspec内でこのファイルはコピーすると指定しないといけないようだ。 だがnuspecはパッケージ作る側が書くもので、使おうとしているbootstrapではコピーされる指定はない。使う側では手遅れ。むむむ。
試しにSolution Explorer上で見えるwwwroot内のファイルをプロパティページからコピーするように指定してみたら、csprojの方にはそのファイルのローカルキャッシュへのパスが書かれちゃった。確かにそこにファイルあるのかもしれないが、そんなところ参照されても困るぞ……。
なんとかならんかドキュメント見てたらPackageReference
にGeneratePathProperty
てのがあって、これを使うとパッケージのローカルキャッシュへのパスが取得できるようになるらしい。うわ、以前見た覚えがあるけど何に使うんだろう?と思ってたけどこれじゃん!!
実際にどんな展開されるかはobj/~.csproj.g.nuget.props
を見てねということなので見てみると、確かにローカルキャッシュのトップレベルへのパスが入っている。
あとnuspecのcontentFiles
指定から生成された各ファイル項目もここに展開されていて、wwwroot内のパスを指定したLinkも書かれていた。
なるほどSolution Explorerから見える謎はここにあったのか。
謎が解けたらあとはやることは簡単っすね。ということでちょちょっと書いて解決した。
<ItemGroup>
<PackageReference Include="bootstrap" Version="5.3.3" GeneratePathProperty="true"/>
<PackageReference Include="jQuery" Version="3.7.1" GeneratePathProperty="true"/>
<PackageReference Include="knockoutjs" Version="3.5.1" GeneratePathProperty="true"/>
<Content Update="$(Pkgbootstrap)/contentFiles/any/any/wwwroot/**/*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(PkgjQuery)/Content/Scripts/jquery-*.min.js">
<Link>wwwroot/js/%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(Pkgknockoutjs)/Content/Scripts/knockout*.js">
<Link>wwwroot/js/%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
GeneratePathProperty
をtrue
にするとPkgパッケージ名
というプロパティが生えてきてインストール先の絶対パスが入っている。
bootstrapはnuspecにcontentFiles
指定があるためobj/~.csproj.g.nuget.props
にContent
が生成されているのでコピー指定だけ書き換える時はUpdate
属性を使う。
jQuery
とknockoutjs
はcontentFiles
指定がないのでobj/~.csproj.g.nuget.props
には何も書かれていない。そのためContent
にはInclude
属性を使う。あとbootstrap
と同じくwwwroot内に入れちゃお、と思ってLink
メタデータを作っておいた。
これでビルド時に必要なファイルがコピーされるようになってくれたわ。
パッケージの更新はできたが、bootstrapは2だか3だかから5に上げたので当然マークアップもめちゃくちゃ変わってて表示がボロボロ。これ直すのがまた大変そうだ。
まああと表示がちゃんとなればひとまずバージョンアップは完了かな。ヘルプのビルドにJekyll使ってるのもnugetで導入できるなんかにしたいけども、キリがないのでそれはまた今度にしよう。
配信時にAzure Speech Serviceで音声認識して字幕生成的なことをやってるんだが、Azure Speech Serviceは結構お金かかってしまって困る1。 月3000円以上かかっちゃってるのでなんとかしたい。
オフラインで音声認識するのにWindowsの機能が使えればいいんだが昔の物は精度がアレだし比較的最近のだとアプリがフォアグラウンドじゃないと動作してくれない。 Windows 11だとライブキャプションという機能が最近あるのでこれが使えればいいんだが、今のところ外から触れるAPIとしては用意されていない。 ただ中身はAzure Speech Serviceのオフライン認識用モデルを使ってるようでそれを触るにはアクセスキーが必要なんだが、なんとライブキャプションの実装あたりのDLLを覗いてみたらキーが普通にぶっこ抜けちゃって試してみたら動いちゃった。 ただまあ一人で試すならこれでいいけども、他人にこれ使いなとは言えないよな……。 あとSpeech Serviceのオフライン認識用のモデルの認証方法は最新版では変更されてたので使えなくなると思われます。簡単にぶっこ抜けるキーではさすがにね。
なんかオフラインで使えてelectronから呼べるのがあったらいいよねということで調べる。 ただAIでとかなるとだいたいPython前提でアプリに組み込むのがなかなか難しくて見つからないねえ……。
一つ見つけたのはVoskてので、これが簡単に導入できそうなのでいろいろいじってた。 Voksはnodejs用のパッケージもあるんだが、ただunacastはelectronなうえにバージョンも古いのでなかなか簡単には導入できず。 途中まで試して思ったよりめんどくせえぞとなっているところで、組み込む前にまずVoskのデモを試してみたら普通に精度悪くて使えねえなこりゃとなった。 まあ50MBのモデルですもんね……。
引き続き調べてたらReazonSpeechのk2てやつがsherpa-onnxといういろんな環境から呼び出せるライブラリ向けのモデルなようだ。ReazonSpeechなら日本語特化だし期待できそうでint8のはサイズも大きくなくて使いやすそう。次これ試してみよう。
unacastにいきなりぶち込もうとすると面倒なことになるのはわかってたのでまず単体で試してみよう。 sherpa-onnxの公式にマイクから入力して音声認識するサンプルもあるのでそれにReazonSpeechのモデルを渡してみる。
sherpa-onnxはいろんなモデルが使えたりするしReazonSpeechもなんかいろんなファイルがぽろっとあってどこに何を指定すればいいのかわからなかったが、そもそもReazonSpeech公式のPythonライブラリがsherpa-onnxを使ってたのでそこを参考にファイルを指定。 あとはすんなり動くはず……って動かねえな。なんかメタデータが無いぞみたいなエラー出されるけど、調べたらそいつはオフライン用のモデルでオンライン(リアルタイム)認識には使えねえぞ、ってことらしい。使えないのかよ!
この場合、音声のしゃべってる部分を切り出してオフライン認識させるのが正道なようだ。でも音声のしゃべってる部分を切り出すのってそう簡単じゃねえぞ……。
と思ったらsherpa-onnxに普通にそのサンプルあるじゃねえか!sherpa-onnxはSilero VADてのを使ってしゃべってる部分を切り出す機能が入ってたようだ。VADって付いてるサンプルあるのは知ってたけどVADてなんだろなと思ってた。Voice Activity Detectorの略だったわ。
今度はオンライン認識じゃなくて、マイク入力でVADしてオフライン認識というサンプルを試したら比較的すんなり動いてくれた。 コードもわかりやすい。 ただいくつか問題があるなあ。
一つは長々としゃべってるとマイク入力のバッファがオーバーフローしたとかで落ちちゃう。まあこれは処理が間に合ってないだけだろう。 マイク入力の設定のところでエラー時に終了しない設定にしたら解決した。
もう一つがよくわからなくて、なんかしゃべり始めがいくらか無視されちゃうんだよね。これはVADが悪いのかなあといろいろパラメータいじってみたが改善せず、そもそもVAD結果を書き出したファイルを聞いてみたら過不足なくしゃべったところだけばっちり切り出されていた。じゃあReazonSpeechの方か~。
しばらくいじってみたが全然わからなくて、ReazonSpeechの公式ライブラリの方で渡してるパラメータを確認してみたがパラメータは同じだった。 ……が、よく見るとなんか音声の頭に0.9秒の空白をくっつける処理やってんじゃん!こんなの必要なのか!現象からしてもばっちりこれじゃんねえ。
0.9秒の空白くっつけるのはまあ簡単で16kHzでサンプリングしてるので16000*0.9
個の0を頭にくっつけて渡してやるだけだ。やってみたら問題なく認識してくれるようになった。やったー!
試した感じは結構精度よくて速度もそんな遅い感じはない。 リアルタイムではないので瞬時にとはいかないし途中経過とかも出ないものの、しゃべったのをログとして出しておきたいだけなので問題ないだろう。 CPUモードで実行してるのにCPU使用率も全然上がらん。メモリは500MB以上食ってるから結構使ってるけど今のPCならまあ厳しいってこともないでしょう。
int8とfp32のモデルがあるので試してみたがint8のでも十分精度良いと感じた。 decoderだけfp32の使うとモデルのサイズはそれほどかわらず精度よくなるからバランス良いかもって感じ。 まあ一回ダウンロードするのが300MB多いか少ないかって程度なんでfp32でもいいのかもだけど。
あとはElectronで動かせるかな~とかマイク入力自分で処理するのめんどくさいな~2というのをやっていかないといけない。 それはそれで大変そうなのでまた今度ね。そのままだとどんどんお金かかるから早めにやっておきたいが。
そういやsherpa-onnxはバックエンドにONNX Runtime使っているということなのでGPUを使うこともできるだろうと調べてみた。DirectMLを指定すると使ってくれそうなので指定してみたが、バイナリがDirectMLを使わない設定でビルドされているようでCPUにフォールバックされちゃった。 まあCPU処理でも全然負荷なかったからGPU処理にする意味全然ないか。むしろVRAMいっぱい持っていかれて困る方があるかもしれない。