パースのついた円とマウスの当たり判定をする

本サイトの姉妹サイト、「妖精⊸ロケット」では、WebGLを使ってウェブサイトを作るという実験を行っています:

これは「季節の歯車」です:

「季節の歯車」って、いったいぜんたい、何なのかって?

季節が毎年毎年一周してるのは、みなさんもご存知でしょう。ちかごろは、もう桜も散ってしまいましたもんね。

ところで。

なんで季節がきっちり正確に毎年一周しているのか、不思議じゃありません?

実はですね、…ここだけの秘密ですよ。

「季節」を動かす歯車、「季節の歯車」が毎年なんとなく一周しているから、季節は毎年一回だけ巡るのです。

じゃあ、その歯車が回るのはなんでなのかって?

それは、春香、夏美、秋葉、冬音の4人の精霊たちが、いつもは仲良く、時にはケンカしながら、「せかいの裏」でくるくる回しているからです。これも秘密ね。

春の次に夏がやってくるのは、春香と夏美が仲良くやってくれたから。

夏の最中に突然秋みたいな日がやってきたり、季節外れの台風がやってきたりするのは、…きっと、夏美と秋葉がたぶんちょっとした事でケンカしたんでしょうね。

でも大丈夫。ケンカしたら、仲直り。いつもそうやって、季節は巡ってきたのですから。

季節の歯車

…という創作神話(?)を元に、この季節を決める歯車である「季節の歯車」と、その結果生まれる「季節の巡り」をWeb上で表現してみようとしたのが、このWebサイトです。

もう少し常識的に言うと、左上の「歯車」の円周上に、それぞれの季節ごとの写真や絵、文章が並んでいます(これを「モーメント(瞬間)」と呼びます):

春はピンク、夏はみどり、秋はオレンジ、冬は青。…うーん、変えてもいいかな。

ここみたいな、いわゆる「ブログ」と違うのは、時系列で並べているわけではないことです。例えば、同じ「10月1日」の「モーメント」は、10年前のものでも今年の物でも、同じ角度のラインの上に並んで表示されます。

さらに、どの「モーメント」が表示されるかは、毎回ランダム(ガチャ)です。10年前の「モーメント」が表示されて今年の「モーメント」が表示されない事もあるし、逆もある。角度は同じでも、歯車からの距離は更新するたびに変わります。この点でも、2018年のページをクリックすれば必ず2018年のページが表示される「ふつうのブログ」とは異なります。

WebGLでウェブサイト作ろうぜ

えー。「季節の歯車」の紹介はこれぐらいで置いておいて。この季節の歯車を表現するための技術について、がこの記事のテーマです。

このウェブサイトは基本的に全部WebGLと、素のECMA Script 6で書かれています。くるくると回る「歯車」も「モーメント」も、全部WebGLで描いていて、HTMLの<img>タグとかは使ってない、と言うことです。もちろん、WebGLはCanvasの中身をOpenGLで描ける、という技術ですので、他のHTMLの技術と、簡単に組み合わせることもできます。「季節の歯車」では、「モーメント」の上にマウスが乗ると、タイトルが表示されるようになっています。この表示に使われているのは、いつもの<div>要素です:

この「モーメント」はこちら

3Dオブジェクトの当たり判定

さて、これで困るのは、マウスの判定です。つまり、マウスがどの「モーメント」の上にあるのか(あるいは無いのか)のチェック関数、です。

HTMLのmouseoverイベントは使えません。「モーメント」は全て、HTMLの要素ではなく、1つのcanvas要素の上にOpenGLで描かれている「絵」に過ぎないからです。

canvas要素のmousemoveイベントを使えば、canvas上でのマウスの座標を得ることはできますが、この座標もそのままでは使えません。というのも、「モーメント」も左上の歯車と同様、「遠近法」が掛かっている立派な「3Dオブジェクト」だからです。

まぁ、わざわざ「遠近法が掛かっています」と上でわざわざ書いたように、事実上ほとんど掛かっていないので、2Dだと思って当たり判定を書いてもほとんど困ることはない(見ている人が気づくことはない)かもしれません。

でも、せっかくだから「正確」に判定してみたい。趣味のプロジェクトですしね。

さて、こういう「3Dのオブジェクトとマウスの当たり判定」じたいは、ゲームではよくることです。3D空間内のアイテムをマウスでクリックして取得したり。このためのテクニックは「Object picking」と呼ばれているそうで、よくある実装は、「当たり判定」のための専用の画面を作ってしまうというものです:

OpenGL Picking Tutorialから引用

まず、オブジェクトごとに相異なる色で塗った「別の画面」を作ります。この画面は、ユーザーには見せません。ただし、あたり判定をする時は、この隠れた画面からマウスの位置にあるピクセルを読んで、「何色」になっているかを調べます。この色が何色かで当たり判定をする、と。例えば上の例だったら、一番明るい灰色だったら右のチェス、真っ黒だったら背景(何とも当たっていない)、といった感じです。

うーむ。たしかに、チェスの駒とか、人間、動物みたいな、複雑な形のオブジェクトがたくさんある中から当たり判定をしたいならわかります。むしろ、この方法を取らざるを得ないでしょう。

でも、ただの「パースのついた丸」のためにこれをやるのは何だか大げさというか、資源の無駄遣いな気がします。単純に2回絵を描かないといけないですし。「相異なる色」を用意したり、半透明になったりしないように配慮した別の描画プログラムをもう一つ用意しないといけないのも大変です。

もっといえば、この「別の画面を用意して、あたり判定を行いたいオブジェクトごとに色を塗る」という力技で当たり判定ができるのは「当たり前」すぎるように感じられます。ちょっと面白くない。

そこで今回は、それとは別のアプローチを考えてみましょう。

「遠近法」のおさらい

その前に、まずコンピューター3Dにおけるいわゆる「遠近法」をおさらいします。

「いわゆる」とつけたのは、「遠近法」と言えそうなものは、このコンピューターの世界の中だけでもたくさんあるからです。わたしの考えでは、ラスタースクロールも遠近法ですし、ゼビウスのゲームシステムも、「空気遠近法」と同じくらいには、りっぱな遠近法だと思います。

とはいえ、「季節の歯車」では、コンピュータ3Dの世界で「遠近法」という言葉で指すものの中では最もおなじみであろう、「透視法射影」を使っています。

これはどんなものだったのか、と言えば、表示したいモデルの頂点の三次元空間上の座標、x y z、そして最後に1を立てたベクトル(x y z 1)に、モデルごとの変換行列Mを掛けて、出来たベクトルをさらに最後の要素( wと呼ばれる事が多い)で割り、出来た(x’/w’ y’/w’)が画面上での最終的な座標となるのでした。

わかりにくいので、ちょっとベクトルがどのように変形されていって最後のxy座標になるまでを図にしてみました:

それぞれの操作を、数学で写像で移される時元の対応を表す、のマークを流用してその処理の流れを表現してみました。えっ、数学の世界でそんなに繋げるような記法は普通しない?まぁまぁ。うまい表記が思いつかなかったので、おもいついた人は教えてくださいな。

行列Mについて少しだけ話をさせてください。このMをうまく作ることで、モデルの平行移動や、回転、拡大縮小を表現できるだけでなく、最後にw’で割るという、行列のかけ算だけでは表現できない非線形な操作と組み合わせることで、遠いものほど小さくなる「遠近法(パース)」が再現できます。上手く作る、といっても、glMatrixなどのいわゆる3D向け行列ライブラリを使えばそんな難しくないです。とりあえず、この話はまた別の機会にいたしましょう。

さて、元々やりたかった事は何かというと、パースの掛かった「丸」とマウスのあたり判定でした。

ふむ。残念ですねぇ。何がって?パースの掛かった「三角形」とのあたり判定だったらすごく楽だったからです。あたり判定の対象が三角形であれば、上の操作で作ったスクリーン上の3つのXY座標で囲まれた三角形の中にマウスの座標があるかどうかを判定するだけで済みます。とはいえ実はその判定はそこまで簡単でもないんですが、やること自体は直感的です。

一方、パースのかかった丸となると、どうすればいいのやら。斜めになってるとぐにゃぐにゃした形になっていて、何をどうすれば内側になるのか判定できるのか、よくわかりません。

スクリーン空間からモデル空間へ

遠近法をつけた丸はぐにゃぐにゃしてて、なんかよくわかりません。となると、遠近法を付ける「前」の丸とマウスの当たり判定を行う作戦を取らざるを得なさそうです。遠近法を付ける前なら、単に「円の中心とマウスの座標の間の距離が円の半径以下か」判定すれば良いだけですから、三角形の内側にあるかどうか判定するよりも更にラクです。まぁ、点と点の距離を調べるのも、実は言うほど簡単じゃあないんですが…。

閑話休題。そうなると、問題は「マウスのポインタは、元のモデル空間では一体どこに存在するのか?」になります。さっきの図はモデル空間からスクリーン空間の話でしたが、今度はその逆、スクリーン空間からモデル空間への話を考えねばなりません。二次元から三次元上の位置を推定しないといけないので、ややこしそうです。

一つずつじっくり追い込んでいきましょう。まず、モデル空間上にあるマウスの点を(xm ym zm)、液晶画面上のマウスの座標を(xs ys zs)とします。マウスの座標にZ軸なんか存在しないのですが、あとで計算するときに便宜上必要になりますので「未知だが、存在する、何かしらの数」として導入します。

さて、目からマウスポインタを結ぶ線は液晶モニタの平面を貫通し、最終的に液晶モニタの後ろにある(笑)1モデルへとぶつかります。図にするとこんな感じです:

これらの(xm ym zm)と(xs ys zs)の関係を、書いてみます:

この図自体は最初に描いたものとほぼ同じです。

なかなかややこしいので、wsという(未知の)変数を新たに導入した上で(といってもw’mのエイリアス)、まず波線を引いた部分を変形します。

行列を掛けて出てきた、素性のよくわからんx’m, y’m, z’mを、すでに分かっているxsやysなどにできるだけ置き換えいって整理しようという作戦です:

さらに波線のwで割ってる部分も、xs や ws を使って割ってない所まで戻します。

すると、「モデル空間上の座標に変換行列Mを掛け算して移すと、スクリーン空間上の値を使ったよくわからんベクトルができる」、という図になりました(少なくともわたしはそういう意味でこの図を書いてます)。波線の引いた部分は「矢印の左側でベクトルに行列をかけると矢印の右側のベクトルになる」、という意味で使ってますので、おなじみの等号に書き直します:

未知変数の部分に赤線を引いてみました。xm ym zs wsの4つが未知で、Mは4×4の行列ですから、この行列とベクトルの式は実際には4本の連立方程式です。未知数が4つで、式が4本。原理的には解けます。やったね。zmは未知じゃないのかって?あー、言い忘れてました。今回の丸は、モデル空間上ではXY平面上にあることにしましょう。なので、zmはzm=0の定数です。

さて、この式は前述した通り解けるので、xm ym zs wsを求めたら、zsとwsは単に便宜上入れた要らない変数なので捨ててしまいましょう。モデル空間上のマウスの座標である、xmとymを使えば、無事円との当たり判定ができます。

はいおしまい、QED。

と言いたい所だが、この行列は実際一体どう解いたらいいのか?右にも、左にも未知変数があります。逆行列を一発キメたら終わり、みたいな、そういう生ぬるい式ではなさそうなのは確かです。かといって、プログラムに組み込んで自動で解かなければならない以上、「頑張って手動で連立方程式を変形して解く」みたいな高校生的解法は使えません。捻りが必要です。

頑張って式を変形するぞい

まず確認です。行列とベクトルの掛け算とは、行列の各行ベクトルを、もう片方のクトルのそれぞれの値で重み付けをして足し合わせることだと見なすことができます。

左辺

まず左辺をこれを使って変形します。Mを、M1からM4までの4つの縦ベクトルが横に並んだものだとみなすことにしました:

右辺

次は右辺です。右辺は、まずwとx y zが掛かっていて複雑なので、wをくくりだします。

赤線を引いた未知変数がだいぶ減って気楽な感じになりましたが、まだベクトルの中と外に未知変数が入っていて扱い方がわかりません。さらにこれをこんな感じで2つのベクトルの和に変形します。

するとどうでしょう。右辺も左辺も、「「未知のスカラ量 x 既知のベクトル」の和」に変形できました。

なんとなく、これなら扱えそうな気がしてきません?

最後の一捻り

さっき変形した右辺と左辺の式を使って、再度等式を書いてみます:

するとただの和なので、簡単に未知変数を左辺だけに集約することができます:

で、ここで最後の一捻りです。

さっきの

行列とベクトルの掛け算とは、行列の各行ベクトルを、もう片方のクトルのそれぞれの値で重み付けをして足し合わせることだと見なすことができます。

というのを、今度は逆回しにします。つまり、こんな感じでもう一度「行列とベクトルの積」に書き戻します:

ここまでくれば、もう簡単。赤線が引いてある未知変数が詰まってるベクトルの値は、逆行列を掛ければ一発で求められます。

長かったですけど、纏めてみると結構綺麗な解法のような気がする。…どうかな。

JavaScriptで最小のデモ作った

「季節の歯車」だとここで実装してるんですが、流石に他のコードも多くてわかりにくいだろうという事で、この当たり判定の部分だけ切り出したサンプルを作ってみました:

github.com/ledyba/__sample__3d-picking-without-shaders
パースのついた円とマウスの当たり判定をする

実際に動いてるデモのページはこちら

  1. でも、最初にプレステのゲームを遊んだときは、そんな感じがしませんでした? []

ChromeでSVGを背景にすると遅い

ブログのタイトルに合わせて、月と夕焼けをイメージしたテーマを自作してブログの模様替えをしてみました。

ソースコードはgithubで公開中です:

ledyba/wp-lunar-theme: theme of 7io.org

作り方は割と簡単で、次のファイルを作り…

ファイル名用途
function.phpテーマの各テンプレートから参照できる関数
初期化、サイドバーの定義など
index.phpトップページの表示
page.php固定ページの表示
single.php各ブログ記事の表示
archive.phpアーカイブ(月別、カテゴリ別)の表示

さらに、これらから参照されるサブテンプレート用のphpファイルと、CSSを用意すれば完成です。

公式のWikiにあるドキュメントもよくまとまってます。詳細は公式をチェックだ(書くのがめんどくさくなった)。

SVGの背景が遅い、どうしようもなく遅い

問題はここからです。今回のテーマ作成のサブテーマとして「ラスター画像(SVG)しかテーマには使わない」という縛りをなんとなく課してみまして、背景もSVGで用意しました。左に見える月の画像もSVGです。

Firefoxから見ると何の問題もないのですが、Chromeから表示するとどうしようもなく遅い事に気が付きました。Ryzen Threadripperを使ってAMDのDiscrete GPUを使っても、フレームレートが落ちてるのが目で分かるぐらいに遅い。

計測してみると、1フレームを描画するのに100ms弱掛かっってました。これはいけない…。

しかもiPhoneでは背景が表示されすらしない。どうしようもねぇ〜。

mac OSのSafariだと見えるんですけどね、中身違うのかな

CSSで同じことをするとなぜか爆速になる

うーんどうしたもんか、と思って。CSSのlinear-gradient書き換えてみました

とはいえ、

メモ: CSS グラデーションにおける色経由点の描画は、 SVG グラデーションと同じ規則に従います。

linear-gradient() – CSS: カスケーディングスタイルシート | MDN

だそうなので、SVGと一緒ならやっぱ遅いんじゃないかなぁ、と思いながらダメ元で。

しかし…どういう事でしょう、Paintingに掛かる時間が70倍近く速くなり、ほぼ60FPSを達成できるようになったのです…(!?)

iPhoneからも背景がきちんと見れるようになりました:

スマホ、画面ちいさくね?マジでみんなこれ毎日使ってんの?

SVGにはまだChrome/WebKitには早すぎるのかもしれない

たぶんですけど、SVGを背景に設定すると毎フレームSVGからbitmapにラスタライズするんでしょうね。他にもWebKitはSVGは遅いと6年前に言ってる人はいて、とくに改善されないまま現在に至ってるようです。

…はぁ。EdgeもChromiumに乗り換えてしまったし、ラスター画像大好きおじさんには苦しい時代が続くかもしれません。

Firefox頑張ってくれ〜〜〜〜〜このブログはFirefoxとフォクすけくんを応援してます!一番好きなブラウザです(やけくそ)!

ffmpegでコマごとに表示時間が違うアニメを作る

ffmpegで静止画(pngとか)から動画を作る方法といえば、連番画像を使う方法が有名です:

ffmpeg -framerate 10 -i %03d.png -vf scale=784x1024 -vcodec libx264 -pix_fmt yuv420p -r 30 out.mp4

これを使うとすべての画像が同じ間隔で表示されてしまいます。上でいうと、000.pngも001.pngも002.pngも、全部0.1秒ずつ表示されます。

大半の場合はこれで困らないのですが、自作でアニメを作ったりしていると困ることがあります。たとえば、今回こんな感じの動画を友達と作りました:

首をかしげて, pixiv

このアニメは、最初のコマだけ1秒、最後のコマだけ3秒、他のコマは0.25秒表示されています。

こういうコマごとに表示時間が異なる動画を作るにはどうすればいいか、あんまり情報がなかったので備忘録的にメモって起きます。

1.jpegから5.jpegを使って上の動画を作るという設定で説明します。

githubリポジトリにデータ付きでアップロードしておいたので、よろしければそちらも参考にしてください。

タイミング情報を纏めたファイルを作る

file '1.jpeg'
duration 1
file '2.jpeg'
duration 0.25
file '3.jpeg'
duration 0.25
file '4.jpeg'
duration 0.25
file '5.jpeg'
duration 3
file '5.jpeg'

こんな感じのファイルをinput.txtみたいなファイル名で作成します。5.jpegを3秒間表示するために、最後にもう一度5.jpegを書いてる点はちょっと注意です(これを書かないと5.jpegが1フレームしか表示されません)。

適切にコマンドを実行する

ffmpeg -f concat -i input.txt -vf "scale=-1:1024" -vcodec libx264 -pix_fmt yuv420p -r 30 out.mp4

いくつか解説です:

  • -f concat を忘れないでください
  • scale=-1:1024を使うと縦1024ピクセルにした上で横幅はアスペクト比に応じて自動で決定してくれます。
  • -r 30は出力動画のフレームレートです。

「さきゅばす」の開発をやめて悪魔を祓い、苦楽を共にした彼女を追悼する

かつてわたしと苦楽を共にし歩んできた「さきゅばす」は、いまやゾンビとなり、インターネットの地下倉庫で、苦しそうに彷徨っている。

だから、せめて…生みの親であるわたしがシャベルでその息の根を止めて埋葬し、わたしは新天地へと向かわなければならない1

さきゅばす」とは何だったのか

「さきゅばす」は、「ニコニコ動画レコーダー」である。

一言で伝えられる短いキャッチコピーを考えるのにも10年掛かり、最近やっとここに着地した。やめるけど。

えーっとね。ニコニコ動画の動画ビューワは、投稿された動画の上にコメントの文字列を再生するたびに毎回重ね描き2して表示しているんですが、その代わりに既に最初からコメントが描きこんであるような動画を生成するソフトウェアです。

こんな感じの動画が作れます:

レッツゴー陰陽師

きしめん/Nursery Rhyme

12年半ほど間欠泉のように開発してきたこのソフトウェアですが、もういい加減、開発・メンテという形で関わるのは、もうやめようと思います。

わたしは株式会社ニワンゴ・ドワンゴ・カドカワとは雇用契約以外も含めた一切の金銭的な関係を持ったことはなく、決してこれは退職エントリではありません。そもそも、ただのOSSだし。

とはいえ、ある意味では雇用契約よりもはるかに強い、「悪魔と契約純粋な趣味として」して作ったソフトウェア3ですので、強いていうなら「退魔・追悼エントリ」でしょうか。てなわけで、こんなタイトルになった。でも俺は悪魔と契約しちまった以上、死ぬまで魔女はやめねーかんな。なぜなら、悪魔との契約は生涯有効だからだ。

引き継ぎ資料

さてさて。とはいえ、「さきゅばす」をまだ必要としている人はまだいるでしょうし、開発を引き継いでくれる人がいるなら、それが一番なのは間違いありません。そんなわけで、引き継ぎ資料を作りました。

ただしおそらくこのドキュメントを見てコマンドを打つだけでは、ビルドと実行まではできても、最後まで動画を変換しきることはできず、いくつかの箇所でエラーでコケると思います。(引き継ぎたいあなたへ:悪魔と契約するんだ、デバッグとソースコードの修正をする覚悟ぐらいは持っておくれ)

さて以下では、こんな感じで10年間降り積もった同窓会的なよもやま話を延々といたします。どんな気持ちで作ってたのか知りたかったら、読んでみてね。長いよ。

どうしてやめるのか?

他にやりたいことがあるから

やりたい事・作りたいものが、たくさんあるからです。

死ぬまでに作りたいもの、上手になりたいこと、知りたいこと、研究したいこと、描きたいこと、記したいこと、見たいもの、…そんなものが両手で数え切れないほど、たくさんあります。でも、人間には寿命があり、いつかは死んでしまいます。すべてを行うことも、見聞きすることも出来ません。最近ついにそこに気づきました。

そうして天秤に掛けた時、「さきゅばす」からは、…「彼女」からは、もう、知りたいことは全部教わったし、作りたいものは全部作ったし、描きたいものは全部描いたし、研究したいことは研究しつくしたし、見たいものはもう全部見せてもらいました。

…つまるところ、さきゅばすでやりたいことは、わたしには、もう残っていないのかなぁって。

ニコニコ動画を見ていない

ニコニコ動画を(古いお気に入りの動画をふとたまに見たくなった時以外)全く見ていない事に気がつきました。すこし昔は「アニメを見る時ぐらいはニコニコ動画」だったような気がするのですが、最近はそれもやめ、アニメを見る時は1人でじっくり鑑賞し、噛みしめるように見ています。

そんなわけで、ニコニコ動画を使っていないわたしには、ニコニコ動画の作りやすいツールは、流石にもうこれ以上作れないと思いました。…せいぜい動き続けるように、メンテできるだけ。

でもそれって、「ゾンビ」そのものじゃないですか。わたしは、そんな彼女の姿を見たくはありません。

必要性が薄まった

さきゅばすは元々は100万回もダウンロードされるために作ったソフトウェアではなく、2007年6月当時の個人的な欲望と興味と関心を具現化するため、高校にもロクに行かずに引きこもりながら実験的に作ったソフトウェアです:

  • PSPでニコニコ動画が見たかった
    (PSPにはニコニコ動画アプリのようなものが無かった)
  • 2007年当時の環境では、弾幕4が重く、PCの性能不足でプレイヤーがコマ落ちしてしまった

でも、2019年現在では、スマホアプリも、各種ゲーム機向けのプレイヤーもあります(端末別ヘルプ、PSP向けはついぞ出なかったようですが)。お気入りの端末でニコニコ動画が見られない、なんて事は、もう無いと思います。変換した動画ではなく、各アプリなら、コメントを見る見るだけでなく投稿することだってできますし、他の動画の検索だってできます5

性能面に関しても、2019年のスマートフォンでも十二分すぎるほどの性能があります。実はスマホをもう持ってないので分かりかねるのですが、「コメントがたくさん出たら画面がカタカタになった」なんて事態は、おそらく流石にもう無いんじゃないでしょうか。少なくとも聞いたことない。

投稿されたコメントアートを残したい、という需要があるということが、orzさんやそれを通じて知り合ったコミュニティを通してわかったのですが、2019年現在では、2019年に投稿されたコメントアートを保存する最善の方法は、おそらく単に録画する事のような気がします。もっと言うと、2019年のパソコンには、それをやるだけの十分な計算資源があります(逆に言うと、2007年当時は、再生された動画をリアルタイムに録画するのはとーっても大変でした)。もちろん、過去に投稿されたコメントアートを今ここで再生・保存したいなら、当時の挙動を再現するコードを書くしかなく、そこに「彼女」の居場所が生まれうる、とは思います。

デフォルトでは動画全体を通して最大1000コメントしか表示されないところを、無理やり数万コメントを突っ込んでコメントで溢れさせる弾幕動画を作るのは…もういいかな。やりたい人が居たら、わたしの代わりに、彼女を召喚ビルドしてお願いしておくんなし。

うーん。

こうして箇条書きにして当時の「モチベーション」とやらを書き出してみたのですが、いくら書いても、ここからこぼれてしまう部分があるような気がします。人はそれを、「情熱」とか「熱意」と呼ぶのでしょうか…。よくわかりません。

…なんだろ、tcpdumpでdumpしたパケット眺めてさ、ActionScriptを逆コンパイルしてさ、ffmpegのソースコード読んで書き換えてコンパイル通してさ、ニコニコ動画の弾幕コメントを表示させるのは、仮に何の意味もなくても、確かにどうしようもなく楽しかったよ。

誰も認めてくれなくても、それがわたしの青春の1ページだ。

さきゅばすの作り方

「資料なんかどこにもないのに、どうやって作ったんですか?」と以前聞かれた事があるので、文章にして残しておきます。

WebAPIの叩き方→tcpdump

ニコニコ動画のサーバから、動画とコメントをダウンロードする方法は、単にブラウザの挙動を追いかけて推理しただけです。ただし、F12を押すと出てくる「開発者ツール」のような便利なものなど存在しない時代だったので6、tcpdumpを使って、ニコニコ動画のサーバと自宅のブラウザが交換するパケットストリームを眺めて観察しました。観察した結果を元にAPIの使い方(リクエストを投げる時のXMLの書き方とか)の仮説をたて、それを確かめるためには、時にはtelnet nicovideo.jp 80ってやって直接手でHTTP/1.1のリクエストを投げて確かめてた気がします。今ではニコニコ動画もHTTPSに対応してしまい、通信はすべて暗号化されてしまったので、今では絶対できない方法ですね…。懐かしや。

コメントの配置方法→逆コンパイル

コメントの配置の仕方は、FlashのプレイヤーのActionScriptを逆コンパイルして、それをそのまま実装しました。今思うと、ちょっとヤバかったかも。でもそのおかげでそこそこ再現性は高かったんですよ。逆コンパイルすると変数名とかが全部「var1」とかになったソースコードが現れてきて、一般に解読はなかなか大変なのですが、当時はゲームのコピープロテクト外しも夢中だったので、「アセンブリじゃなくて、ちゃんとif文やfor文が出てくる!なんてわかりやすいんだ!逆コンパイラってすげー!」みたいな感じでした。…いまやれって言われたら、できるかなぁ。

コメントのレンダリング→適当 or 逆アセンブル

コメントの描画の仕方は、最初はSDL_ttfを使って適当にMSゴシック使って描いていたのですが、後にFlashそのものを逆アセンブル・解析して、ExtTextOutWというWin32APIを特定のパラメータで叩いてることを突き止め、cairoというライブラリでなるべく同じようにWin32APIが叩かれるように実装しました7。これも、今思うとちょっとヤバかったかな。ただ、この解析結果が実装されているのは、ゼロから書き直したSaccubus2だけで、引き継ぎ資料でビルドしてるSaccubus3は初期のバージョンである1がベースなので実装されていません。やりたかったらやってみてね。

ffmpegの改造方法→素直にソース読む

ffmpegの改造の仕方は…普通に変換してるところのソース読んで追いかけて、その結果をもとに、さきゅばす用にちょびっと改造しただけです。話を整理してみると、実はここが一番簡単かつ素直だったことが分かります。ただ、変化の激しいffmpegを改造して維持つづけるのは中々大変でした。この話はまた後でします。

アーキテクチャ図

ちなみに、全体のアーキテクチャはこんな感じです:

この図にして見ると結構複雑なアーキテクチャはどう思いついたのか?…うーん、思い出せません。でも結構素直ではあると思うんですが、みなさんはどう思います?

さきゅばすの思い出

ここからはさらに個人的な思い出話です。ガンガン回顧するぜ。

自宅サーバから配布しすぎてISPからアカウントを永久凍結された回

さきゅばすを開発した当初、このウェブサイトは自宅サーバの上でホスティングされていました。なので、開発したソフトもその自宅サーバから配信してました(当時のブログ記事)。

公開してしばらく経ったある日、とある「個人ニュースサイト(これも懐かしい響きだ)」で取り上げてもらって突発的にすごい人気が出て(今で言うとバズって)一週間で数万回とかダウンロードされるようになりました。

…それはそれでとーっても嬉しい話なのですが、5MBぐらいあるソフトウェアが1万回ダウンロードされると、50GBのアップロード・トラフィックが発生します。当初使っていたプロバイダは(安い代わりに)月々5GBまでしかアップロード・トラフィックを出してはならないという規約になってまして、そのうち見事にアカウントが凍結されてインターネットに繋げなくなり、ウェブサイトも公開できなくなってしまいました。そしてアップロード・トラフィックなんか気に掛けた事無かったので、理由もわからないままある日突然繋げなくなってすごい焦った…(そしてプロバイダからの紙のお手紙が後に届いた)。

当時は「なぜアップロードが…?」と思っていましたが、10年後ぐらいにデータセンターの運営をやるようになって納得しました。インターネットでは、電話と同じで、アップロードする側がお金を払う

つまりISPは、ユーザーがアップロードすればするほど損をするんですね。昔、ISPがP2Pを嫌がって規制したりしてたのも、結局最終的にはそこ(アップロードの増加による経済的損失)だったのか、とかも芋づる式に完全に理解しました。

毎月パソコン雑誌がなぜか家に届く回

人気が出た結果、複数のパソコン雑誌に定期的に収録してもらえるようになりました。収録されるとソフトの開発者は無料で献本してもらえるという習わしがあったので、ほぼ毎月のように様々なパソコン雑誌が届いてた時期がありました。しかし毎月毎月同じソフトが別の雑誌とはいえ収録されるというのも、今思うと不思議な話だ…。

Flash黄金期である2000年代前半に「ぶっこ抜き」という標語と共に生きていた世代だったので、「ネトラン」に収録されたときはちょっとした謎の達成感(?)はありました。

…何もかもが、懐かしい。

ユーザーは説明を読んでくれない

「さきゅばす」は上にも書いた通り「実験」として作ったソフトウェアでしたので、当初のUIは限りなく適当でした。どれぐらい適当かというと、文字を描くためのフォントのパスを自分で打ち込まないと変換できないぐらい、適当なUIのソフトだったのです。

そんなソフトですから、人気が出ると、すぐに「変換できません」という投稿でコメント欄があふれてしまいました。対応策は分かっていて、WindowsのMSゴシックのパスをデフォルトでハードコードしておけば、ぶっちゃけ大半のケースでは困りません。

が、せっかくJavaで書いたのにそれで乗り切るのは負けな気がして8、かといってWindows/Mac OSX/Linuxのすべてで適当なフォントを探すためのJavaコードを書くほどの気力もなく9、「Windowsの人は次にあるMSゴシックのフォントを打つか、フリーのフォントをダウンロードしてそのパスを入力してください」とダウンロードリンクのすぐ上に赤字でデカデカと書くという、今になって冷静に考えると一番ダサい解法で乗り切ろうとしたのですが、デカデカと書いたのにも関わらず、「動きません」というコメントの勢いが衰えることは一切ありませんでした。読んでくれないんだね。町中でダサいテプラを見るたびに、いつもこの事を思い出して、テプラ貼った人に心の中で同情してしまいます。

結局、最終的には腹をくくってWindowsの時はWindowsのシステムフォルダの中にあるMSゴシックのフォントのパスを発見してくるコードを書きました。

とにかく自動でとりあえず大半のケースでは動くようにすることがすごい大事で、人はとにかくログは読まないし、エラーメッセージも読まないし、ドキュメントも読まないし、でもコミュニケーションはする、という、人間へ対するある種の諦観を、このとき手にいれました。この諦観は、この後、他のフリーソフトを書いたり、仕事としてソフトを開発・運用をしたりする中で、何度も何度も強化されていったような気がします。

特定のCPUでクラッシュする

一時期、Crusoeの載ってるVAIO(まだSONYが作ってた頃!)を併用していたのですが10 、さきゅばすはこのPCでだけ、ときたまクラッシュすることに気づきました。トラブルシュートした結果、最終的にSDL_ttfのgccのコンパイルオプションでSSE2かなにかを無効にすると再現しなくなったので、おそらくSIMD命令の互換性がCrusoeでは怪しいのだろう、という結論に。

ただそれだけなんですが、なんというか、パソコンの裏側の、そのまた裏側を覗いてしまったような…見てはいけないものを見てしまったような…何か隠れていたものを掘り起こしてしまったような、そんな気持ちになったエピソードです。

cygwinとmingwの思い出

Windows上でPOSIX互換のプログラムを書くためのソフトウェアツールキットとして、MinGWcygwinがあります。前述したように互換性厨だったので、C言語で書いてあるコメントを描画する部分については、Win32APIを叩くのではなく、こちらを叩いて実装しました。MinGWだとpthreadが無いとかで(詳細は忘れた)、もっと楽なcygwinで当初は開発・配布してました。

cygwinを使ってコンパイルしたバイナリは、cygwin-1.dllに必ず依存するようになります。それ自体は別に問題ではなく、一緒にcygwin-1.dllを配布すればよい…のですが、ただ、他のソフトと同時で起動した時に、そのソフトが別のバージョンのcygwin-1.dllに依存していたりすると、異なるバージョン間で何かがぶつかりあってクラッシュしてしまうらしく、定期的に「動かないのですが」のコメントが来ていたので、頑張ってMinGWで動くように改修した記憶があります。もっというと出来る限り全部をstatic linkするようになっていった記憶が…あります…。

ソフトウェアのバイナリを配布して動かしてもらう場合、相手の環境は前もっては分からないので、かなり想定外な…ともすれば理不尽なエラーに出くわしたりします。…スマホの時代になっても、そのへんはあんまり変わらないかもしれませんねぇ。

ffmpegを改造しながら4〜5年毎日Jenkinsでビルドした

ffmpegの改造自体は結構簡単だったのですが、ffmpegは頻繁にアップデートされ、内部構造も結構大胆に変わります11。それに毎日追従して改造しつづけながら何年間も毎日Jenkinsでビルドし続ける、というのをしてました。ぶっちゃけ、この作業が一番大変だったな。

今思うと、別にそこまでする必要無いよな〜。でもDaily buildとかContinuous Integrationって文字列が、なんか、こう、かっこよくて…やってみたくてさ…。

コメント描画フィルタを実装する3つの方法

最初は、コメントを描画するために「vhook」という、画像から画像に変換するフィルタをDLLで注入できる機能を使っていたのですが、これはすぐになくなりました12

しょうがないので、avfilterでvhook相当の機能を足すvhextというfilterを書いてffmpegのソースに入れました。使うためのドキュメントはあっても作るためのドキュメントがなくてよく分からず、とにかくソースを読みながら手探りで入れたような記憶があります。

が、libavfilterを改造する方法だとかなり頻繁にコミットがコンフリクトしてその度に解決するのが辛かったので、Saccubus2ではまた方法を変え、libavdeviceにsaccubusという名前の仮想入力デバイスを作って、その仮想デバイスが動画のデコードとコメントの描画をした上で、後段のffmpegのエンコーダに渡す、という実装に変えました。

仮想デバイスの追加先であるlibavdeviceはそんなに頻繁に更新されないようで、あんまりconflictに悩まされなくなったような記憶があります。

さらに仮想入力デバイスにしたおかげで、動画のfpsを自在に変えられるようになったり、結構柔軟性は上がりました。ffmpegのAPIたくさん叩かないといけないから、大変だったけどね。

動画の長さをDLLへ渡すための、いくつかの方法

そうそう、コメント描画部分にわたさなければならない情報として、動画の画像とその時間やコメントそのもの以外に、「動画全体の長さ(秒数)」があります。

というのも、ニコニコ動画のコメントは投稿されたタイミングの1秒前から3秒間画面を流れるのですが、その3秒後が動画終了より後だった場合、コメントの描画開始タイミングを前倒しして必ず3秒間表示するようになっています13

この挙動を再現するには、コメントを描画しはじめる前に動画の長さを知っておかなければなりません。

そのために、当初はffmpeg.cの中で動画の長さが取得できるようになったタイミングの箇所を見つけて、そこに動画の長さを後で使うために記録しておくコードを挿入していた記憶があるのですが、ffmpeg.cはあんまりにも書き換えられまくってコミットがコンフリクトしまくるので、結局諦めて変換のためにffmpegを叩く前にffprobeコマンドを使って長さを調査する実装に変えたような…。

「ffmpegのプロセスを実行する回数はできるだけ減らしたい」みたいな、(今思うと無意味かもしれない)妙なこだわりがありました。その後に作ったSaccubus2でも仮想入力デバイスが長さを取得・計算していて、やっぱりffmpegのプロセスは変換ごとに一回しか起動しないようになってます。

追従し続けるにはmergeではなくrebaseするしかなかった

途中からgitを使ってffmpegの改造ソースも管理するようになったのですが(それまでは…どうしてたっけ…)、forkしたさきゅばす用ffmpegのリポジトリを本家に追従させるために、mergeするのではなく、定期的に毎回本家のmasterブランチからrebaseするという方法に落ち着きました。

「rebase絶対許さないマン」がいるのは知ってるし、その理由も頷けるんですが、1年に数千〜万コミットされるソフトにパッチをちょこっとだけ当てるというケースでは、rebaseにしておかないと、自分たちが加えた変更のコミットは大量のコミットログの中に埋もれていき、conflictを解消するだけのmergeコミットが数千コミットに一回ずつ現れるようになり、コミットIDも段々とぐちゃぐちゃになり、最終的に訳がわからなくなったので…。

歴史の管理をする、メタ・歴史管理ソフトがほしいです。

ニワン語の実装

わたしにとっての、初めてのプログラミング言語処理系の実装。それがニワン語の互換処理系(インタプリタ)である「ねこまた」でした。

ニワン語のsyntaxとsemanticsをまとめた不思議なサイトがあったので、だいたいそのとおりにC++でパーサとインタプリタを実装して、細かい部分に関しては、ニワン語で書かれた音ゲーを実際に動かしながら「文法エラーにならないように」syntaxを調整し、「実行した見た目が同じ挙動になるように」semanticsを調整していく、という感じで細部を詰めていきました。音ゲーが最後まで動いた時は嬉しかったな。

ココロの肩書きは一生有効です」と昔CPUの本の人は書きましたが、「初めて書いた本格的なインタプリタが、仕様書もなければ処理系のソースもないプログラミング言語の互換処理系であり、ゲームがちゃんと一本動いて遊べた」という、中々レアな肩書き(?)は、わたしのココロの中で、誰にも理解してもらえないかもしれないけれど、それ故に誰からも曇らせることができない、きれいで澄んだ輝きを、いまでも放っています。ええ、この文章を書いている瞬間もです。

ニワン語は、未踏で作ったシステムの一部であるプログラミング言語「ど〜なっつ」のアイデアの原型にもなっていますし、それは更に、今友達と書いてる小説と、そのゲームにもつながっています。

間違いなく、ニワン語とそのアイデアは、わたしの血肉の一部です。

そういえば元々のニワン語を作った人もなかなかのカワリモノで、結局ドワンゴを辞めてしまったと聞いたのですが、今はなにをしてるんだろう。一回一緒にご飯を食べながらお話したいなぁ。もしもこれ読んでたらTwitterでもMastdonでもメールでもこのコメント欄でもなんでもよいので、連絡してくだされ、頼む。

追悼の一環として、動いた音ゲーのTAS動画(フルコンプ)を再掲します:

このゲームの実装されていた元動画を今見たら、ふつうのPVに戻ってしまっていました。もちろん、もうニワン語は公式にサポートしなくなったのでそれが正しい挙動…なのですが。諸行無常。

このゲームだけでなく、他のニワン語を使った動画のプログラムも動いてました:

こっちも今みたら、普通の動画になってしまった…寂しい。

ニコニコ学会βとニコニコ超会議

そんな感じでだらだらソフトを書いていたところ、ニコニコ学会βというところで喋らないか、というお話を頂いて、2回(第二回最終回発表させていただきました。最初はさきゅばすの簡単な紹介とver2.0の自慢(笑)、次は、その後の近況報告みたいな感じかな。

…かわいいお洋服を着て好きなことが喋れたので、余は満足じゃ。

今では在宅勤務して働いてるぐらいには根っからの「ひきこもり」なんですが、大勢の前では意外と喋れるなぁと思って、そこが面白かったかも。

orzさんの貢献とコメントアート

途中から、orzさんという方がコミットしてくれるようになりました。なかなかシャイな人でどんな方かは分からないまま、いつのまにか見なくなってしまったのですが…お元気ですか。

orzさんが貢献してくれたのは主にコメントアートへの対応で、ソースコードを読んでバイナリを解析してるだけでは到底できない、コメントアートコミュニティの知見(例えばこういう知見)を踏まえた改修(や、ニコニコ動画のWeb APIの変更への追従)をしていただけました。ニコニコ学会βやorzさんを通じてコメントアートコミュニティの方とも知り合いになったり…ほんと、フリーソフト開発って何があるかわからないです。

ただ、正直に言うと、最終的にコメントアートはどうしても理解しきれませんでした。色々な意味で。ニコニコ動画への興味が失われかけてた頃に知ったからかなぁ。とはいえ、こういう事に真剣に取り組んで表現・実践・議論している人たちが眼の前にいるんだなぁ、というところを目撃できたのは、いい経験だったと思います。

ところでなんで「さきゅばす」なんですか

これも、

  • 「そもそも「さきゅばす」ってなんですか」

って聞かれるパターンと、

の2パターンがあります。ソシャゲの流行に伴ってえっちなサキュバスの知名度がむちゃくちゃ上がったので、近年は後者のパターンが増えました。10年以上も変わった名前のソフトを作ってると、そういうこともあります。

ではなぜ「さきゅばす」なのか。辞めるわけだし、そろそろ文章として残しておいても良い頃でしょう。

初期のバージョンを作ってた時(2007〜2008年ごろ)に、ニコニコ動画で違法にアップロードされた「ひぐらしのなく頃に」と「涼宮ハルヒの憂鬱」のコスプレAV14を見るのがお友達の間で流行っていて、その時にひらめいた連想ゲームです:

味のある演技15のコスプレAV→「えっち(?)だけど、なんかこわくて、夢の中に出てきそうな、おんなのひと」→サキュバス→「さきゅばす16」。

…そういえば気がつくとニコ動はホモビデオばっかになってノンケは完全に閉め出されてしまったな。あくしろよ。おっそうだな?

ちなみにアプリのアイコンがポケモンのムウマだった17のは、サキュバスは夢魔で、ムウマも夢魔だからです。

こういう連想ゲームは、今でもずっとやってますね。働いてる会社で流石にやりすぎって戒められた事もあるぞい。人間、そうそう変わるものではない。

最後に

これを書いている間、ずっとsm9「レッツゴー陰陽師」を聞いていました。当時は「電波ソング」とゲラゲラ笑いながら聞いていたのですが18、今聞いてみると…結構いい曲だなって思っちゃった。となると、このブログ記事も「電波ゆんゆん」でしょうか?

というわけで、最後に「レッツゴー陰陽師」の気に入ってる一節を引用して終わります。矢部のピコ麻呂に「さきゅばす」も成仏させてもらうか、そこまでは許してもらうか、わたしの中ではまだ結論はついておりません。

辛い時、悲しい時、人はそんな時心の隙間に闇が出来る。
その心の闇に”魔物”達は容赦なく入り込んでくるのだ。
だから、苦しくても、くじけるな。落ち込むな。くよくよするな。
何事にも屈しない強じんな心こそが、最強の武器なのだから。

カドカワ、最近は元気がないようですが頑張って。陰ながら、応援しています。

  1. 元ネタ: https://gakkougurashi.com/ []
  2. 初期はFlash、今はWebはPixi.JS、スマホやゲーム機は、またそれぞれ別の技術 []
  3. あとで書きますが、サキュバスは悪魔の名前です []
  4. たくさんのコメントが一画面上に同時に表示されること []
  5. 「あえて出来ないのがよい」という価値観も、わたしの中に無いではないんですが []
  6. JavaScriptのデバッグのためにalert()使ってた時代といえば伝わるかな []
  7. どうしてWin32APIを直接叩かなかったのかはちょっと思い出せない []
  8. 当時は移植性厨だったので []
  9. そもそもmac持ってなかったし []
  10. バッグに入れてイヤホンつないで音楽聞きながらちゃりんこ乗ってたぜ。スマホで聞きながら走ってる連中より10年早くな! []
  11. libavというフォークもこの間に生まれた…まだ仲直りしてないのかお前ら []
  12. 最初のリリース時点ですでにdeprecatedだったのでしょうがない []
  13. なので最後の3秒だけは特にコメントの密度が高い []
  14. 流石にR18のところはカットされてた気がする []
  15. 穏やかな表現 []
  16. 英語表記のsaccubusも、普通の表記のsuccubusからちょこっと捻ってます []
  17. 今思うと著作権的に完全にアウト []
  18. この言い回し、最近聞かなくなりましたね []

LinuxでvkEnumerateInstanceLayerPropertiesが何も返さないのをなんとかした

ちなみに環境はUbuntu 19.04 + Radeon RX 580です。

OpenGLに疲れた

最近OpenGLに疲れはて、Vulkanに乗り換えようかと思っています。

  • メインスレッドからしか叩け無い制限。
  • bindというグローバル変数より邪悪な概念。
  • 関数を呼び出した後にglGetError()を毎回叩かなけばならない苦痛(叩き忘れるといつエラーになったのか、わからん)。
  • シェーダーへ変数へ渡すためのあの無限の関数群。
  • OpenGLのバージョンによって今では利用が必須だったり、逆に使えなかったりする「拡張機能」。
  • IndexBufferObjectだのElementArrayBufferだのVertexArrayObjectという意味不明な命名(これ…何だと思う?)。
  • 同じ関数でもOpenGLバージョンによってたまに意味が違ったりするあのややこしさ。
  • ググって出てきた情報の「今では使えなさ」(だいたいそのままコピペしても動かない)
  • GLSLのバージョンごとの気まぐれさ。
  • 結局何が起こるのかよくわからん関数のドキュメント。
  • Core ProfileだのCompat Profileだの

…ぼくもう疲れたよパトラッシュ。

そんな感じで最近Vulkanでなんとかする方向でやってこうと思ってまして、で最近引っかかった問題の解放を備忘録代わりにメモっておきます。

VulkanにはLayerなるものがあるらしい

Vulkanではある種の拡張機能を「Layer」といいます1

たとえば、標準ではこんなLayerがあるんだってさ(LunarGのサイトから引用。CC=BY=ND):

Layer Name Layer Type Description
VK_LAYER_LUNARG_api_dump utility print API calls and their parameters and values
VK_LAYER_LUNARG_assistant_layer utility highlight potential application issues that are not specifically prohibited by the Vulkan spec, but which can still create problems
VK_LAYER_KHRONOS_validation validation the main, comprehensive Khronos validation layer — this layer encompasses the entire functionality of the deprecated layers listed below, and supercedes them. As the other layers are deprecated this layer should be used for all validation going forward.
VK_LAYER_LUNARG_device_simulation utility allows modification of an actual device’s reported features, limits, and capabilities
VK_LAYER_LUNARG_monitor utility outputs the frames-per-second of the target application in the applications title bar
VK_LAYER_LUNARG_screenshot utility outputs specified frames to an image file as they are presented

VK_LAYER_KHRONOS_validation、便利そうだなー使いたいなー。

環境によってはこれ以外のLayerも使えるらしく、実際にどんなレイヤーが使えるか列挙して調べるためのvkEnumerateInstanceLayerPropertiesという関数があります。

で、これを使って実際に列挙してみました。

std::vector<VkLayerProperties> vk::enumerateInstanceLayerProperties() {
  uint32_t numProps = 0;
  {
    VkResult const result = vkEnumerateInstanceLayerProperties(&numProps, nullptr);
    if(result != VK_SUCCESS) {
      return std::vector<VkLayerProperties>();
    }
  }
  std::vector<VkLayerProperties> props;
  props.resize(numProps);
  VkResult const result = vkEnumerateInstanceLayerProperties(&numProps, props.data());
  if(result != VK_SUCCESS) {
    return std::vector<VkLayerProperties>();
  }
  return std::move(props);
}

が、これを動かしても空配列しか返ってこない。エラーではなく、成功した上で0個です。たぶん呼ぶタイミングを間違ったりはしてない。

うーん、おかしい。さっきの6つのレイヤーは、Vulkanの仕様上必ず入っているはずなのです。

ためしにググって見た所、こんな感じだったので、みんな困ってんだろなーと思って解法を書いておきます:

解法:sudo apt install vulkan-validationlayers-devしろ

apt search vulkanと入れると

libvulkan-dev/disco,now 1.1.101.0-2 amd64 [installed]
  Vulkan loader library -- development files

が出てくるのでこれだけいれときゃOK、Vulkanが全部使えるようになるんだぜ!と思いきや、実はこれだけではUbuntu 19.04ではLayerは入りません。

正解はこちらも入れることです:

vulkan-validationlayers-dev/disco,now 1.1.101.0-1 amd64
  Vulkan validation layers -- development files

要するに黙ってsudo apt install vulkan-validationlayers-devしろ。

結果

ちゃんと6つ返ってきました:

[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_LUNARG_parameter_validation (spec=4198501, impl=1) :: LunarG Validation Layer
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_LUNARG_object_tracker (spec=4198501, impl=1) :: LunarG Validation Layer
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_GOOGLE_unique_objects (spec=4198501, impl=1) :: Google Validation Layer
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_LUNARG_standard_validation (spec=4198501, impl=1) :: LunarG Standard Validation
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_LUNARG_core_validation (spec=4198501, impl=1) :: LunarG Validation Layer
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_GOOGLE_threading (spec=4198501, impl=1) :: Google Validation Layer

あれ、ちょっとまって、名前全然ちがくない?GOOGLEってなんや??

今日のところはVK_LAYER_LUNARG_standard_validation使って勘弁しといたるわ。

Vulkan難しい

Vulkanとの対話は続く…。

  1. この時点で若干OpenGL時代の命名規則センスを感じヤバみは感じる… []

(機械仕掛けの不完全な)誕生日占い

その1

占い師「わたしは本物の霊能力者です。あなたの誕生日をあててさしあげましょう」

「ほんとに?」

占い師「ほんとです。あなたの誕生日の、月の十の位と一の位の差を教えてください」

「6です」

占い師「日の十の位と一の位の差は?」

「5です」

占い師「よろしい。それでは、月の十の位と日の十の位の差は?」

「5です」

占い師「最後に、月と日、十の位と一の位、すべての合計は?」

「13。」

占い師「6月16日ですね?」

「次の行列が正則ってだけじゃん」

その2

占い師「略」

「ほんとに?」

占い師「ほんとです。あなたの誕生日の、月の十の位と一の位の差を教えてください」

「6です」

占い師「次に、あなたの誕生日の、日の十の位と一の位の差に…さらに月の一の位を足したものを教えてくださいな」

「1です」

占い師「最後に、月と日、十の位と一の位、すべての合計は?」

「13。」

占い師「6月16日ですね?」

「次の行列が正則ってだけじゃ…」

「…あれ…正則どころか、そもそも3行しかない…。ほんものの霊能力者は、いるんだ…」

占い師「だから、そう言いましたでしょう?」

その3

占い師「略」

「略」

占い師「あなたの誕生日の、月の十の位と一の位の差を教えてください」

「2です」

占い師「次に、あなたの誕生日の、日の十の位と一の位の差に…さらに月の一の位を足したものを教えてくださいな」

「-5です」

占い師「最後に、月と日、十の位と一の位、すべての合計は?」

「13。」

占い師「Traceback (most recent call last):
File “/src/github.com/ledyba/tanjoubi-uranai/scratch.py”, line 92, in <module>
main()
File /src/github.com/ledyba/tanjoubi-uranai/scratch.py”, line 88, in main
  print(ndict[str(v2.tolist())].transpose())
KeyError: ‘[[13], [-2], [13], [-5]]’

「やっぱな。ちなみにわたしの誕生日は2月29日です。」

ソースコード

https://github.com/ledyba/tanjoubi-uranai

南極にもIPアドレスはある(???)

諸事情で国別IPv4アドレスの一覧を眺めていたのですが、なんと南極にもIPアドレスがあるらしいことに気が付きました。その数、256個。北朝鮮でも1024個あるので、それより輪をかけて少ないのですが、そもそも存在するとは思っていませんでした。だって、南極には国という概念はなじまないし…。

調べて見ると、ARIN(主にアメリカのIPを管理しているところ)が南極に割り当てたようです

arin|AQ|ipv4|23.154.160.0|256|20181017|allocated|b2a4180607264562d56fa4cb02fbc9a4

6つ目の要素は、このIPアドレス範囲が2018年10月17日に予約された事を示しているらしい。すんごく最近ですね。

使われているのか調べる

たった256個となると総当りしたくなるよね…ということで、pingで総当りしてみました:

for i in $(seq 0 255); do
  echo -n "23.154.160.${i}: "
  if ping -c 5 23.154.160.${i} > /dev/null 2>&1; then
    echo 'ok'
  else
    echo 'ng'
  fi
done

結果としては、全てのIPに対して、pingは一切通りませんでした。使われてなさそうです。もちろん、pingが通らないからといって使われていないとは限らないのですが。

南極のインターネット事情を想像する

そういえば、南極の観測基地の人たちはどうやってインターネットやってるんでしょう。データセンターおじさんとしては気になる所です。海底ケーブルが敷設されている…わけないですよね。観測基地はたくさんあるけれど、もちろん、都市に比べたらほとんど無人です。とてもじゃないけど、海底ケーブルなんて採算つきませんもん。でも観測データのやりとりや、連絡を考えるとインターネットは繋がってそうな気はします。衛星通信だろうか。

それとも、実は今まで本当にインターネットが使えなかったのが、最近使えるようになったから、割り当てられたのかな。実際に使えるIPアドレスは254個しかありませんから、昭和基地に割り当てられているIPはせいぜい1個だろうか。うーん、NATを使ってなんとかしのぐつもりか?まぁ、対外向けサーバを運用するわけじゃないから、それでもなんとかなるか。

…などと想像するのも楽しいのですが、ためしに検索してみたところ、昭和基地に関してはKDDIの人が毎年1人派遣されてネットワークの維持管理をしているそうです。衛星経由で南極とKDDI山口衛星通信所を3Mbps(3G回線より遅い)で繋いでいて、(観測優先だけど)隊員はLINEもFacebookも使えるそうな。速度はともかく、意外と現代的だ…。構成的には、LINEやFacebookからは日本のIPアドレスとして認識されるのかな。

まぁ、単に登録を間違えただけかもしれない

whoisをIPアドレスに対して行うと、どの団体に割り当てられたのかをチェックすることができます。南極のIPのうちの1つに対してwhoisした結果はこちら:

% whois 23.154.160.100

#
# ARIN WHOIS data and services are subject to the Terms of Use
# available at: https://www.arin.net/resources/registry/whois/tou/
#
# If you see inaccuracies in the results, please report at
# https://www.arin.net/resources/registry/whois/inaccuracy_reporting/
#
# Copyright 1997-2019, American Registry for Internet Numbers, Ltd.
#


NetRange:       23.154.160.0 - 23.154.160.255
CIDR:           23.154.160.0/24
NetName:        ANYCAST-TEST1
NetHandle:      NET-23-154-160-0-1
Parent:         NET23 (NET-23-0-0-0-0)
NetType:        Direct Allocation
OriginAS:       
Organization:   Windscribe (WL-242)
RegDate:        2018-10-17
Updated:        2018-10-17
Ref:            https://rdap.arin.net/registry/ip/23.154.160.0


OrgName:        Windscribe
OrgId:          WL-242
Address:        9251 Yonge St #8901
City:           Richmond Hill
StateProv:      ON
PostalCode:     L4C 9T3
Country:        AQ
RegDate:        2016-03-07
Updated:        2018-12-21
Ref:            https://rdap.arin.net/registry/entity/WL-242


OrgTechHandle: SAKYE-ARIN
OrgTechName:   Sak, Yegor 
OrgTechPhone:  +1-647-725-2536 
OrgTechEmail:  yegor@windscribe.com
OrgTechRef:    https://rdap.arin.net/registry/entity/SAKYE-ARIN

OrgAbuseHandle: ABUSE5829-ARIN
OrgAbuseName:   Abuse Manager
OrgAbusePhone:  +1-647-727-8859 
OrgAbuseEmail:  abuse@windscribe.com
OrgAbuseRef:    https://rdap.arin.net/registry/entity/ABUSE5829-ARIN

「南極」を示す「Country: AQ」以外の部分の住所は、カナダにある「Windscribe Limited」というVPNを提供する会社の住所と一致しています。…だから、まぁ、申請するときに国コードを間違えて、そのまま誰も気づかないままIPアドレスが割り当てられただけかもしれませんね。

…いや?わかりませんよ。ひょっとすると、国家権力に影響されない最強のVPN…データーヘイブン…を実現するために、南極に拠点を作ろうとしているのかもしれません。イギリスのすぐそばにあった、シーランド公国よりは厄介で、そして頼りになるサービスになるかもしれませんね。知らんけど〜

WordPressの検索機能を悪用するSPAMが押し寄せてきてつらい

最近、分散SNS「Pleroma」のインスタンスを立ち上げたのですが(マストドンからもリモートフォローできます: @psi@sabbat.hexe.net)、デバッグのために、nginxのログをtail -fで眺めていて気づきました。ここ二週間ほど、このブログの検索結果のページへ対するリクエストが異常なまでにやって来ていることに。しかもUserAgentは検索エンジンです。クロールしている検索エンジンの会社は様々で、よく知らないドイツの会社?の検索エンジンなどからも来ていました。IPアドレスを逆引きした結果を見る限り、どれも本物の検索エンジンと思われます。

そんなに何を熱心に検索しているのやら、と思って、URLをクリックせずにコピーしてdecodeしてみると、謎の韓国語。Google翻訳に入れると、よくわからないけどなにやらアダルトな雰囲気。

もしやと思って、検索結果が出なかったときに表示される”Nothing found”でこのサイト内をしてみると…:

…これは検索エンジンSPAMですねぇ。間違いない。なんだこれは…。たまげたなぁ。

必ずドメイン名が含まれている事、あと翻訳した文章の内容を見る限り、この韓国語のメッセージを見て、何か期待を膨らませた人がドメイン名を手打ちしてアクセスしてくれることを期待しているのでしょうか。いろんなことを考えるなぁ。

書かなくても分かると思いますが、良い子のみんなはこの画像中のドメインにアクセスしてはなりません

たぶん、これらの長い検索クエリが含まれるURLがずらっと並んだページを、業者?の人がどこかに一生懸命つくって、検索エンジンのbotにクロールを指示しているんだと思います。

今年に入ってのリクエストがほぼSPAMでつらい

% cat access.log | grep "GET https://7io.org\(/?s=\|/search/\)" | wc -l 
1989392
% cat access.log | grep "GET https://7io.org/" | wc -l 
2355234

今年に入ってから処理している235万件ほどリクエストのうち、この検索結果へのリクエスト(ほとんどがこのSPAMだと思われる)は198万件。せっかくCPUをぶん回してリクエストを処理しても84%がSPAMとな。ビットコインのマイニングより虚しいCPUの使い方なんじゃないか。

検索エンジンにインデックスしないようにお願いした

とりあえず、使っているテーマのhead.phpに、検索結果に関してはインデックスしないようにお願いするmetaタグを書きました

でも、これは対症療法にすぎません。検索結果は汚染されなくなりますが、クロールのリクエストは際限なく飛び続けるでしょう(検索エンジンの裏にいる「AI」ってやつがよしなに判断して、アクセスする前に止めてくれるようになる可能性は、無いとは言えませんが)。SPAMをやってる人たちがいつかインデックスされない事に気づいてくれたら止むかもしれませんが、それを期待するのは違和感があります。かといって、検索エンジンを全部ブロックするのもおかしいし。

どうしたもんか。

もはやDOS攻撃に近い

いまのサーバはそれなりの性能があるからあんまり困っていませんけど1、もしラズベリーパイとか、昔使ってた玄箱のような非力な自宅サーバだったら間違いなくCPUのリソースを使い切っていたに違いない。こんなん実質DOS攻撃やんけ

なんとなく、UDP Amplification攻撃にも似ています。UDP Amplification攻撃では、攻撃者はIPアドレスを隠しつつ様々なサーバに元の数倍のトラフィックを流すことが可能なわけですが、このSPAMもどこの誰なのかを隠しつつ様々な検索エンジンを動員して膨大なHTTPリクエストを発生させています。UDP Amplification攻撃と同じように、パケット自体は第三者からやってくるのでブロックするわけにもいかないし、するにしてもキリがない、という点でも似ています。もちろん、本物のDOS攻撃と違ってサーバがダウンしたらSPAMをやってる人たちは目的が達成できなくなるわけですが、まぁダウンしたらその人たちは別のブログで同じような事やるだけですよね、きっと。

インターネット…どうしてこんな事に…

かなしい

2019/03/16 追記

robots.txtに検索ページのURLのパターンを記載することで、検索エンジンからの無意味なアクセスはなくなりました。ページ毎の指定だと一回はやってくるわけですが、この方法ならロボットはアクセスする前に判断してくれるようです。

User-agent: *
Disallow: /feed/
Disallow: /comments/feed/
Disallow: /search/*
Disallow: /*.php$
Disallow: /*?*
Disallow: /*?

 

  1. うそ。nginxのログが肥大化するのでちょっとだけ困ってます。消すからいいけど。 []

HTTPSをやめたらChromeから接続できなくなった話

ブンブンハロー、はいどーも。

HTTPSからHTTPへ

最近、わたしと友達がやっている、妖精⊸ロケット(hexe.net)というウェブサイトをHTTPSからHTTPへ移行しました。HTTPSへの移行ではなく、HTTPへの移行です。暗号化するのを、やめました。

んん??時代に逆行しているような気がしますな。Let’s encryptで無料のSSL証明書が簡単に手に入る時代だというのに、お前は一体なにをやっているんだ!

実をいうとHTTPSにも対応してます。HTTPにリダイレクトするけどな!

「保護されていない通信」ですが、何か?

常時https化が叫ばれて久しいですね!

…たったそれだけの理由で、このサイトも含めてなんとなく流されてHTTPSに対応していましたが、…これ、本当に必要なんでしょうか?

そもそも、一体HTTPSで何を保護しているというのか。わたしたちのウェブサイトには公開鍵で署名をしないといけないような、誰が書いたか(あるいは、書いていないか)が極めて重要な、そんなソーシャルな内容はありません。ついでに、このサイトは等しく公衆に公開されているものですから、盗聴されても困らないはずです。まぁ、パケット書き換えられて勝手に改竄されるのは嫌ですけど。でも、ソーシャルな内容でもないのに、わざわざ改竄する人なんか、いるのだろうか?

っていうか、実は常時HTTPSなんかしてもしょうがないから「検索エンジンでの検索結果の順位を上げます(SEOのためにHTTPS対応してね!)」とかいってHTTPSにするモチベーション作ってるんじゃないの。

疑い始めると止まらない!

うぉー、人間が憎い!

「信用できるHTTPSを売りつけてくる人間」が信用できない!

…というわけでHTTPSからHTTPにしてみました。

なぜかChromeでアクセスできない(Firefoxはできる)

設定自体は簡単です。表のnginxが、裏はgoで書かれたバックエンドに対してリバースプロキシしているという構成なので、表のnginxの設定をHTTPとHTTPSでごそっと入れ替えるだけです。

これで際限なく暗号化する潔癖症から逃れられたと思ったのもつかの間。いつも使っているFirefoxでは何の問題もなくHTTPでも表示できたのですが、Chromeでhexe.netを開いてみるとなぜか全く表示されないことに気づきました:

HTTPの方を開いても、勝手にHTTPSへなぜかリダイレクトされてしまいます。サーバはHTTPへリダイレクトし返すので、そこで堂々巡りが発生して「リダイレクトが繰り返し行われました」となってしまったようです。

元々は301でHTTPからHTTPSへ投げていたわけだから、それがChromeのキャッシュに残っていたのだろうか?と思って消してみても、効果はありませんでした。

開発者ツールの「ネットワーク」で確認してみると、301ではなくて「307 Internal Redirect」だそうな:

な、なんじゃそりゃ。307はTemporary Redirectだったはずですが…。そしてもちろん、サーバーでは307を返す設定なんかしていません。

HSTSってのが悪いのか?

そこで思い至ったのがヘッダに書かれているHSTS、一度HTTPで繋いだ時に、次以降は常にHTTPSを利用するよう要請できるという機能でした。HSTSを設定した覚えは一切ないんですが、ひょっとすると、ほら、まぁ、ね、nginxの設定をコピペし間違えるなどして昔設定したことが無いとは言えない…じゃん?

chrome://net-internals/#hstsからHSTSの設定キャッシュは削除できるはずなのですが、やはり効果はありませんでした。変だなぁと思いながら状態を確認してみると:

dynamicなんとかとstaticなんとかの値があって、staticの方にだけ意味のありそうな値が入ってます。ん…static…?

と思って調べていくと、HSTS Preload Listというものに突き当たりました。

ChromeとFirefoxで中身が違うHSTS Preload List

HSTSを指定する方法には、HTTPリクエストへ対するレスポンスのヘッダで指定する方法の他、HSTS Preloadingという方法もあるそうです。

HTTPで最初に接続した時にHSTSを要請する方法の場合、その定義から初回のリクエストは安全でないHTTPで通信しなければなりません。この初回時に改竄でもされたらオシマイです。そこでHSTS Preload Listの出番なのであります。HSTS Preload List Submissionというサイトに「わたしのサイトはHSTSに対応していますよ」と登録しておくと、なんとブラウザの中にその情報が書き込まれて、最初から安全なTLSで通信できるようになりますよ…と。段々宣伝みたいになってきたのでこの辺でやめておきますが、HSTSのRFCの著者の1人がGoogleの中の人なこと、HSTS Preload List Submissionもいかにも「公共」な雰囲気を纏ってはいますがChromium(実質Google)がやってることは指摘しておきましょう。

ここで登録されたリストは最終的に週一の頻度Chromiumのコードに反映されるようです。見てみると…:

ありました。ここでHSTSが有効とマークされているせいで、ブラウザ側でhttpからhttpsへの自動リダイレクトが行われているのでしょう。原因がわかってすっきり。…身に覚えがないけど…。

git blameしまくった結果、この行は2016年11月18日のコミットで追加されたことがわかりました。その時はまだhexe.netは持って無かった気がするので、前の持ち主の人が設定したのかもしれません。前にも使われていた事があるような短いドメイン名はこういう事もあるのでしょう…。

さて、上記のHSTS Preload List SubmissionのサイトではFirefoxでも使われていると書いてありますが、Firefoxのソースコードを参照する限り、hexe.netは入っていません。これで、FirefoxからはHTTPでも見れることが説明できました。…でも、なんで入ってないんでしょう?管理系統が別なんでしょうか。Mozillaのページには特に説明はなく、コミットが3日おきくらいに粛々となされているのは分かるのですが、この変更のソースがどこから来ているのかはコードレビューを見てもよくわかりませんでいた。コードは読めても社会がわからーーーーん!

HSTS Preload ListはWeb標準じゃないけど、Chromeのシェアは大きいから…

このHSTS Preload Listなんですが、Mozillaのページに書いてあるとおり、実はRFCとかW3C勧告のようなWeb標準ではなく、各々のブラウザが勝手に実装したりしていなかったりする「独自拡張」にすぎません。

ところで、こんなニュースがあります:

グーグル、完全HTTPS接続で安全なアプリ用ドメイン「.app」–早期登録を受付開始 – CNET Japan

.appドメインは、アプリ開発者のウェブ運営向けとして用意するTLD。例えば、「アプリ名.app」「開発者名.app」「開発会社名.app」といったURLでアプリの最新情報やダウンロード用リンク、アプリ内コンテンツなどを提供すれば、ユーザーに覚えてもらいやすい、といったメリットがあるという。

最大の特徴は、.appドメインのウェブサイトが必ずHTTPS接続となること。個別にHSTSなどの設定をする必要がなく、.appドメイン内のサイトは自動的にすべてHTTPS接続される。これにより、アクセスするユーザーの安全を手間なく確保できる。

このニュースに対応するように、Chromeの方でもFirefoxの方でも”app”というエントリが含まれています:

Chrome

# https://cs.chromium.org/codesearch/f/chromium/src/net/http/transport_security_state_static.json?cl=0193585e14d2baf5c9ae96a767c532a13973b011

    // gTLDs and eTLDs are welcome to preload if they are interested.
    { "name": "android", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "app", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "bank", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "chrome", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "dev", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "foo", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "gle", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "google", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true, "pins": "google" },
    { "name": "insurance", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "new", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "page", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "play", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },
    { "name": "youtube", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true },

Firefox

# https://hg.mozilla.org/mozilla-central/file/4d15e90af575/security/manager/ssl/nsSTSPreloadList.inc#l3893

...
apothes.is, 1
app, 1
app-at.work, 1
...

ソースコードは非公開だけどInternet ExplorerとかEdgeとかSafariとかでも似たような設定がされてるでしょう。きっと、たぶん。そうにちがいない。

curlではHTTPでアクセスできるけどきっとこれは何かの間違いでしょう:

% curl -vvv get.app
*   Trying 216.239.32.29...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0xe52310)
* Connected to get.app (216.239.32.29) port 80 (#0)
> GET / HTTP/1.1
> Host: get.app
> User-Agent: curl/7.64.0
> Accept: */*
> 
< HTTP/1.1 302 Found
< Location: https://get.app/
< Cache-Control: private
< Content-Type: text/html; charset=UTF-8
< X-Content-Type-Options: nosniff
< Date: Wed, 13 Mar 2019 03:26:39 GMT
< Server: sffe
< Content-Length: 213
< X-XSS-Protection: 1; mode=block
< 
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="https://get.app/">here</A>.
</BODY></HTML>
* Connection #0 to host get.app left intact

…うーん、なんか胡散臭いと感じてしまう。

今後どうしようか

  • HSTS Preload List SubmissionRemoval formから削除してもらう
    • 数カ月後のChromeのリリースで、たぶん接続できるようになる
  • HTTPSに戻す
  • すべてを放置し、Chromeからは接続できないウェブサイトを運営していく(自称ダークウェブ)

一番最後でも、良いかなぁ。それぐらいで、ちょうどいいのかもしれない。「地獄少女」みたいで面白くない?(どうかな…)

GoogleのクローラーはHSTS Preload Listを使っていない

ちなみに、GoogleのクローラーはたぶんHeadless Chromeを動かしているけれど、HSTS Preload Listは使っていないと思います。

なんでそんなことが分かるのかというと、次の検索結果からです:

この「WebGL not supported.」はJSを実行した結果DOMに追加される文字列なので、おそらくクローラーはHeadless Chromeを動かしていると思われます。が、このメッセージが出せるということはリダイレクト地獄に陥いっていないということなので、HSTS Preload Listは使ってないと思われます。…ってことは検索結果は改竄されてるかもしれないって事だな!(?)

you can also see

世界で一番美しい容姿の美少女Vtuberおじさん

はいどうもー。
世界で一番美しい容姿の
美少女
Vtuber
おじさん
ナニモ・ナイでーす。

えー。あのぉ、
よく知らないんですけど最近vtuberというのが流行ってるらしくてですね、
かわいい少女とか少年の絵とかCGに声を当てて喋る遊びらしいんですよ。

でー、まぁ、人の仔…?
人間はそういうの(かわいい少女とか)見ると「世界一」とか「ナンバーワン」を決めたくなるじゃないですか。

ミスユニバース、みたいな? アイドル総選挙、とか。

でも、でもですよ。

人間の容姿ってやっぱり人によって趣味が違うからNo.1とか決められないと思うんですよね。

美少女にしたって、俺はロリコンだー、とか、俺はボンキュボンが良いとか、
そもそも俺は少年の方が好きだ、とか、女装がいいとか。

キリがない。そうキリが無いわけですよ。

そこで、そこでですよ、

真っ暗なスタジオで収録してるって設定にして画面を真っ黒にしておいたので、
こう、各々の視聴者のみなさんに自分の一番好きな容姿を思い描いてもらおうかなと。

あの、声と容姿は一致していないっていうのは暗黙の了解になっていると思うので、声はそのままでいいのかなと。

キャラクター設定も人によって理想は違うし、正直考えるのも面倒くさいので自分で考えてもらえればなと。

どんな事をやってどんな番組コンテンツを配信してもらいたいかも人によって違うと思うので、
これから次の、2回目以降の配信もみなさんの想像に任せようかなと。

それではーまた。
世界で一番美しい容姿の
美少女
Vtuber
おじさん

世界で一番面白くて刺激的で扇情的な配信を次回からお楽しみに。

ナニモ・ナイでしたー。

動画の生成について

真っ黒なbg.png(640×360)を用意してから、

ffmpeg -loop 1 -i bg.png -i input.wav -c:v libx264 -pix_fmt yuv420p -profile:v baseline -level 3 -tune stillimage -acodec aac -b:a 128k -shortest tmp.mp4
ffmpeg -i tmp.mp4 -c:v libx264 -preset slow -crf 22 -pix_fmt yuv420p -c:a copy nanimo-nai.mp4

二行目が無いとFirefoxで上手く読み込めませんでした。

参考: