にじボイス百合とは何か
全国3億人の関係性オタクの皆さんこんにちは!
本日、DMMボイス改めにじボイスがサービス再開しましたが、さっそくにじボイス百合がわたしのエコーチェンバーの中で流行っていますね。にじボイス百合とは、にじボイスの各モデルの公式キャラ設定から関係性を見出して会話台本を作成し、それをにじボイスで読み上げてキャラ同士の会話を壁になって見守る営みのことです*1。似たような営みはVOICEROID劇場など昔からありましたが、にじボイスはパラメータ調整一切無しで感情が乗った演技をしてくれるので、自分で作っているというより向こうから供給がやって来ます。
現在公開されているキャラ設定は見た目と年齢・性別・誕生日、あとはサンプルボイス1種くらいで、ここから関係性を見出すのは特別な訓練を受けていないと難しそうにも思えますが、以下のようにChatGPTにキャラ設定を教えてやれば適度に設定を膨らませつつ会話劇を作ってくれます。
ChatGPTを使うことで、にじボイスの利用規約にある「公序良俗に反さないコンテンツ」という基準を勝手に守ってくれるという副次的効果もあります。ラインが分からなくなった過激なオタクもこれで安心ですね。(※もちろんChatGPTが書いたからといって必ずにじボイスの利用規約に準拠しているわけではありません)
上記のプロンプトで作った水戸明日菜と遠野澄花の会話劇がこちらです。
DMMボイスのキャラ設定をChatGPTに教えて会話劇を書かせ、それをDMMボイスで読み上げてみた。これは...アリなのでは... #DMMボイス pic.twitter.com/fyet8X4c8x
— Mito Memel (@mito_memel) 2024年11月5日
続きが聞きたくなったらChatGPTに「続けて」とお願いするだけです。まさに百合の永久機関ですね。わたし的には水戸明日菜×遠野澄花が一推しですが、本日追加された新キャラの苔村まりもと深海結涼の組み合わせにも大きな可能性を感じます。
新しく追加された苔村まりもと深海結涼に百合の波動を感じたので会話してもらいました (セリフはChatGPT作) #にじボイス pic.twitter.com/68sFr6Nij9
— Mito Memel (@mito_memel) 2024年11月20日
皆さんもぜひ公序良俗に反さない範囲でお好みの組み合わせを楽しんでみて下さい!
現場からは以上です。
*1:ここでいう百合とは女性同士に限らずその他の性別や人間以外も含む広義の関係性を指します
エウルアとアンバーにChatGPTの仕組みを説明してもらった
全国5億人のエウアン推しのみなさんこんにちは!最近、ChatGPTに百合SSを書かせるのが私のエコーチェンバーの中で流行っていますよね。そこで、GPT-4を使ってエウルアとアンバーにChatGPTの仕組みを説明してもらいました。
やり方
まず、以下のようなプロンプトで各キャラの特徴をChatGPTに教えます。
以下は原神のキャラクター「エウルア」のプロフィールとセリフ例です。 この内容を基に、エウルアが言いそうなセリフを10個挙げてみて。 セリフ例の口調を真似ることを意識して。 # プロフィール (ここにゲーム中のプロフィールを貼る) # セリフ例 (ここにゲーム中のセリフを貼る)
そして、二人の関係性(筆者の私見をやや含む)を教えた後、以下のプロンプトで出力を開始します。
ChatGPTの仕組みについて、エウルアとアンバーの会話劇の形式でなるべく詳しく説明してみて。 二人の関係性がよく表れているやりとりを必ず含めて。
出力結果
いかがでしょうか。やや違和感はあるものの、けっこうエウアンしてるのではないでしょうか。 今回は二人の関係性の説明を200文字程度で入力しましたが、もっと詳しく入力すればより洗練されたエウアンができるのではないかと思います。
現場からは以上です。
MagicOnionをUnity 2019.4 + IL2CPP環境で使う
MagicOnionをUnity 2019.4 + IL2CPP環境でビルドしたらエラーが出てハマったのでメモ。
環境
- Unity 2019.4.26f1
- MagicOnion 4.3.1
- gRPC 2.42.0 (daily builds)
問題
Scripting BackendをIL2CPPにしてビルドするとこのエラーが出る。 github.com
解決法
Assets\Plugins\Grpc.Core\runtimes\grpc_csharp_ext_dummy_stubs.c
の末尾に下記のコードを追加するとビルドが通る。
void dlopen() { fprintf(stderr, "Should never reach here"); abort(); } void dlerror() { fprintf(stderr, "Should never reach here"); abort(); } void dlsym() { fprintf(stderr, "Should never reach here"); abort(); }
上記のIssueのコメントによるとこの方法だと解決しなかったっぽいが、私の環境だとこれだけで大丈夫だった。
ちなみにアプリケーション終了時にフリーズする場合はIStreamingHub.DisposeAsync()
を呼んでいないのが原因なので、MonoBehaviour.OnApplicationQuit()
とかで呼ぶようにする。
async void OnApplicationQuit() { await this.streamingClient.DisposeAsync(); }
参考情報
AWS SDKをUnityで使う方法(2020年度版)
「AWS SDK Unity」でググると、AWS Mobile SDK for Unityの公式ドキュメントがトップに表示されます。 どこの馬の骨かもわからない個人ブログなんか参考にしなくても公式ドキュメント見ればええやんと思うかもしれませんが、残念ながら公式ドキュメントの内容は古いです。
公式ドキュメントからリンクが貼ってある http://sdk-for-net.amazonwebservices.com/latest/aws-sdk-unity.zip のunitypackageは、 まず同梱のサンプルシーンが動かず、エラーメッセージをググりながらなんとか動かせたとしてもAndroidでエラーが出たりします。
どうしてこうなった
AWS SDK for Unityは元々AWS SDK for .NETをフォークして作成されたもので、リポジトリも分かれていました。 しかし、サポートされる機能が増えるにつれ両方のリポジトリをメンテするのが困難になったため、Unity版は.NET版のリポジトリに吸収され、.NET版の一部として提供されるようになりました。 しかし、この時点ではコードを完全に共通化できたわけではなく、プラットフォーム毎にコードが分かれている部分があり、インタフェースも微妙に違っていました。
その状況を解消したのが.NET Standard 2.0です。 .NET Standard向けのビルドではUnityでも共通のコードが使えるようになり、今までUnityでは一部のサービスのSDKしか提供されていなかったのが全てのサービスをサポートするようになりました。
- Unity Custom版
- .NET Standard版
の2つのバージョンがあり、インタフェースも対応サービスも異なります。 そして公式ドキュメントで説明されている導入方法は、Unity Custom版のものです。 Unity Custom版は既にサポートが終了しており、.NET Standard版への移行が推奨されています。じゃあドキュメント直せよ。
.NET Standard版の導入方法
公式ブログに書かれています。
- http://sdk-for-net.amazonwebservices.com/latest/v3/aws-sdk-netstandard2.0.zip からdllをダウンロードしてAssetフォルダにコピー
- IL2CPPを使う場合はlink.xmlを書く
コードに関してはUnity独自のおまじないとかは不要になったので、.NET版のドキュメントを参考に書けば大丈夫です。
UnityInitializer.AttachToGameObject(this.gameObject);
とかも不要です。
注意点
ただし、こちらの記事で書かれているように、 .NET Standard版はコードを共通化した代償として、Unity Custom版でしか提供されていなかった一部機能も削除されてしまいました。これらの機能を使いたい場合は古いコードを参考に自前で実装する必要があります。
例えば、CognitoはUnity Custom版ではPlayerPrefsをキャッシュとして使用しており、認証されていないユーザーのIDはアプリケーションを再起動しても保持されます。 しかし、.NET Standard版ではPlayerPrefsを使ったコードを入れるわけにはいかないので、デフォルトでは認証されていないユーザーのIDはアプリケーションの起動毎に変わってしまいます。 この動作を変更するには、こちらのコードのコメントに書いてあるように、CognitoAWSCredentialsをオーバーライドしてキャッシュの動作を実装する必要があります。
まとめ
ドキュメントはちゃんと更新しよう。
ソーシャルVR「Hubs Cloud」を立ててみた + 日本語化
2020/5/30追記
維持費が高いのでOffline Modeにしました。Aurora Serverless、400PV/月くらいでも$50くらい掛かるよ!
出来上がったものがこちらです。
Hubs Cloudとは、MozillaのソーシャルVR「Mozilla Hubs」を自前のAWSアカウントで立てることのできるCloudFormationテンプレートです。あまり詳しくない人向けに説明すると、マストドンのVR版だと思っとけばだいたいあってます。
Mozilla Hubsはオープンソース化されており、がんばれば自前で1からビルドしてデプロイすることも可能です。しかし、現代のWebサービスのデプロイはなかなか複雑で、Apacheの公開ディレクトリにファイルをコピーすれば終わりというわけにはいきません。Hubs Cloudは、GUIをぽちぽちするだけで冗長化とオートスケーリング対応済みのそのまま本番投入可能なサーバを立ち上げることができます。いい時代になったものですね。
Hubs Cloudをわざわざ立てなくても、本家のMozilla Hubsでも自分のプライベートルームを作って自前アバターやシーンをインポートすることは可能です。ではなぜ人はHubs Cloudを立てるのかというと、自前で立てることでAdmin画面に入ることができるようになります。Admin画面では以下のような項目の変更が可能です。
- デフォルトで選べるアバター・シーンのラインナップ (Mozilla Hubsのデフォルトアバターはちょっとバタ臭いですよね。
アバターだけに) - Title・Description・ロゴなどのブランディング
- トップページのDocs・Communityなどのボタンのリンク先、表示非表示
- Adminユーザー以外がルームを作れないようにするなどの権限設定
- UIの配色
逆に言えば、上記のような項目を変えたいわけではなく単に自分のプライベートルームを作りたいだけであれば本家のMozilla Hubsで十分です。Hubs Cloudは最小構成でも$40/月$100/月くらい掛かります。マストドンを立ててみたものの結局Pawooしかアクセスしてないそこのあなた、本当にHubs Cloudを立てる必要があるかはよく考えましょう。私はよく考えずに立てました。勢いって大事だよね。
立て方
こちらのページに従って作業するだけです。レシピをアレンジしないことが重要です。初心者はまずレシピ通りに作ってみましょう。
特に「2. Register a new domain name on Route 53」を下手にアレンジして失敗している人が多いようです。ドメインの構成についてはこちらのページに詳しく書いてありますが、どのパターンにしても、新規ドメインを2個買う必要があります。はい、2個です。「うそやん、うまいことやれば1個でできるやろ?」とか「既に持ってるドメインのサブドメインで運用したろ」と思うことでしょう。そう思っていた時期が私にもありました。「Route53で管理されている」「未使用のドメインが」「2個」必要です。ここはあきらめてさっさとドメインをポチりましょう。
日本語化について
私が立てたインスタンスは日本語化されていますが、これはどうやっているのかというと、クライアントのリポジトリをフォークしてコードをいじりました。こちらのページに書いてある通り、既にデプロイフローが用意されておりクライアントを簡単に差し替えることができます。
言語ファイルはhttps://github.com/mozilla/hubs/blob/master/src/assets/translations.data.jsonにあります。現状だとこの"en":{}
の中身を直接書き換えてしまうのが日本語化の一番簡単な方法です。"ja":{}
というキーを追加してもうまく日本語化されません。というのも、一部言語を"en"
決め打ちで書かれているコードがあるからです。決め打ちで書かれている部分を修正するプルリクを投げようかとも思いましたが、現在こちらのIssueで議論されているように、多言語対応についてはライブラリをFluentに変更してPontoonを使ったワークフローでやっていくようです。なので、現在のコードベースのまま多言語対応しても無駄になる可能性が高そうです。
まとめ
早く本家が多言語対応しますように。 Fluentに詳しい人、OSSに貢献できるチャンスですよ!!!
CloudSearchのdomainを複数環境で共有したい
この記事は VALU Advent Calendar 2019 20日目のエントリです。
株式会社VALUのバックエンドエンジニアのMito Memelです。前回のエントリで、渾身のギャグ「これでキューが急に詰まっても安心ですね!」が誰にも拾ってもらえなくて悲しいです。
VALUのサーバインフラはAWSですが、AWSで全文検索システムを構築する場合、Elasticsearch ServiceとCloudSearchどちらを使えば良いか迷いますよね。Elasticsearchにしか無い機能を使いたい場合は選択の余地はありませんが、そうでない場合、CloudSearchのオートスケーリングは魅力的です。Elasticsearch Serviceは自動フェイルオーバーはしてくれるものの、インスタンスタイプ・ノード数の計画は自分で立てる必要があります。何も考えずにdomain(CloudSearchクラスタの設定単位)を1個作るだけで良いCloudSearchはとてもお手軽です。
しかし、CloudSearchにも欠点があります。スケールアウトは得意なんですが、性能がそれほど必要では無い場合、スケールダウンしたくても最小構成で1domainにつき$60/月程度(東京リージョンの場合)掛かります。1domainだけならまだ良いんですが、例えば開発環境が複数あったりすると環境ごとにdomainを作らなければならず、リソースは有り余っているのに料金がかさむという状況になりがちです。その点Elasticsearchだと1domain内でもindexで分けることができるので、複数環境でdomainを共有するのも簡単です。残念ながらCloudSearchにはElasticsearchで言うindexのようなものは無いのですが、どうにかして1domainを複数環境で共有できるようにしたいものです。そこで、実際に試してみました。
fieldでがんばる
まず最初に思い付くのは、index
みたいな名前のfieldを追加して、検索時に毎回そのfieldを条件に含めるというやり方です。この方法でもできなくは無いのですが、いくつか欠点があります。
idフィールドの被り
CloudSearchにはMySQLで言うauto_incrementのような自動採番は無く、documentのアップロード時にユニークなidを明示的に指定する必要があります。このidは例えばユーザー検索ならRDB側で振っているユーザーIDを使うなどすれば良いのですが、1domainを複数環境で共有する場合、idが被ってしまう可能性があります。なので、idのprefixに環境名を付けるなどの工夫が必要となります。
field名のルールが増える
「index
みたいな名前のフィールドを追加する」と先ほど書きましたが、documentの検索対象fieldでうっかり同じ名前を使ってしまうみたいな事故が考えられます。なので環境識別のための名前空間として使用するfieldはsuffixに _
を付けて index_
みたいな名前にし、検索対象fieldではそのsuffixを避ける、みたいなルールが必要になります。これでも良いんですが、なんかこう、もうちょっとスマートにやりたいですよね。
idを構造化する
idフィールドの被りを避けるためにidにprefixを付けるなどの工夫が必要なのであれば、いっそのことidフィールドのprefix検索だけで名前空間を分けられないか?と考えました。
idフィールドに使える記号は _ - = # ; : / ? @ です。これを使って、例えば
{環境名}/{データ種別}/{連番}
のような階層構造のidを付ければ、idの被りを防いだ上で、余分なfieldを追加しなくてもidのprefix検索で環境を特定することが可能です。実際のコードは以下のようになります。
documentのアップロード
public function uploadDocuments(CloudSearchDomainClient $client, string $index, string $type, array $documents) { // idを置換 $formatted_documents = array_map(function($document) use ($index, $type){ $document['id'] = "{$index}/{$type}/{$document['id']}"; return $document; }, $documents); $client->uploadDocuments([ 'contentType' => 'application/json', 'documents' => json_encode($formatted_documents), ]); }
documentの検索
public function search(CloudSearchDomainClient $client, string $index, string $type, string $query) { // クエリにidのprefix条件を追加 $formatted_query = "$query _id:{$index}/{$type}/*"; $response = $client->search([ 'query' => $formatted_query, 'queryParser' => 'lucene', ]); // id部分のみを返す return array_map(function($id){ return explode('/', $id)[2]; }, array_column(array_get($response, 'hits.hit', []), 'id')); }
まとめ
idのprefix検索を利用することで、CloudSearchのdomainを複数環境で共有することができました。これで開発環境では料金を抑えつつ、本番環境ではCloudSearchのオートスケールのメリットを享受することができます。
LaravelのキューワーカーをCloudWatchで監視する
この記事は VALU Advent Calendar 2019 5日目のエントリです。
株式会社VALUのバックエンドエンジニアのMito Memelです。FGOの星4配布はぐっちゃん先輩にしました。
皆さん、Laravelのキューワーカーは監視していますか?公式ドキュメントに書いてあるようにSupervisorに登録しただけで監視した気にはなっていないですよね?
Supervisorはプロセスの死活を見ているだけで、実際にキューが消化されているかは見てくれません。キューが詰まってしまってもなかなか気づきにくいのがキューワーカーの怖いところですよね。タイムアウトを設定していていも、そもそもワーカー本体が何らかの原因で固まってしまうこともあり得ます(というか最近本番環境で発生しました)。そこで、実際にキューが消化されているかをCloudWatchで監視してみます。
キューが消化されているかを確認するには、実際にジョブを投げて処理されるかを見るのが確実です。つまり外形監視ですね。というわけで、まずはCloudWatchに値を投げつけるだけのジョブを作成します。
class Heartbeat implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private $created_at; public function __construct() { $this->created_at = now(); } public function handle() { $client = App::make('aws')->createClient('cloudwatch'); $client->putMetricData([ 'Namespace' => config('app.name') . '/' . config('app.env'), 'MetricData' => [ [ 'MetricName' => 'QueueWorkerLatency', 'Value' => now()->diffInSeconds($this->created_at), 'Unit' => 'Seconds', ] ], ]); } }
__construct()
とhandle()
の時間差を測ることで、「ジョブをdispatch()
してから実際に処理されるまでの時間」が測れるわけですね。
そしてこのジョブをdispatch()
するだけのコマンドを作成してタスクスケジュールに登録します。
class DispatchHeartbeat extends Command { protected $signature = 'heartbeat:dispatch'; protected $description = 'Heartbeatジョブをキューに積みます'; public function handle() { Heartbeat::dispatch(); } }
これでCloudWatchでジョブの処理遅延時間をメトリクスとして取得することができるようになりました!
あとはCloudWatchのアラーム設定でデータの欠落or処理遅延時間の増加を検知してアラートメールを送るなり煮るなり焼くなりしましょう。 これでキューが急に詰まっても安心ですね!