Chienomi

私の書いたプログラムの技術的ポイントの解説

開発::info

最近、私の作品のこだわりポイントというか、どういう考えで作ったのか、何が魅力なのかみたいなのことを聞かれる機会があったのだけれど、そういえばそういう話をまとめてしたこはないなと思った。

私のソフトウェアの話をまとめたものはあるのだけど、それはどういうソフトウェアかという紹介の面が強く、技術的な話ではない。 また、バージョンアップごとに個々のソフトウェアの記事はあったりするけれど、バージョンごとの話が中心になっている。

だから、一度まとめて話そうと思う。

うまくいっている系

当初の想定以上に成功し、私としても手放せないソフトウェアになっているもの。 私自信の利用頻度も高く、開発も継続している。

PureBuilder Simply

概要

PureBuilder Simplyは、現代的な言葉で言うと静的CMSである。 Pandocを中心に据えていることで強力な構文サポートと多彩なソース言語サポート、非常に強力なテンプレートを利用可能である。 現在はPandoc以外のエンジンも色々と選択可能。

メタデータを駆使し、さらにその生成過程がプログラマブルであることで、静的CMSとしては驚くほど動的な要素を組み込むことができる。 近年はSPAとの連携などもサポートされている。

成功の要因

Pandocの採用だ。

PureBuilderという名前になってからは3代目となるPureBuilder Simplyだが、前2作はそれぞれにいまいちなところがあった。

初代はZshスクリプトであり、所定のZshスクリプトを書くことで変換を動的にやろうというものだった。 PureBuilderそのものは非常にコンパクトなスクリプトだが、ビルドシーケンスはユーザーがZshで書くようになっているから、普通にプログラミングできる人でないと書けない、という指摘があった。

こうした指摘を踏まえて作られたPureBuilder NGはすべての機能を内包するように作られ、Rubyが書かれた。 従来が任意の実行ファイルで良いような仕組みだったものを、Rubyの枠組みでRubyで書くになったことでとにかくめんどくさくなった。 あと全部の機能をPureBuilder NGが持つようになったことで、「PureBuilder NGにない機能は使えない」という形になり、自由度も下がった。

そうした「いまいちうまくいっていない」ソフトウェアを経てPandocを知ったことがブレイクスルーになった。

Pandocは様々なドキュメントフォーマットに対応しているし、PandocのMarkdownは非常に表現力が高い。 つまり、PureDoc (Z), PureDoc2といった独自フォーマットにおける問題を抱える必要がない。

加えて、Pandocは強力なテンプレート機能を持っている。 テンプレート自体はちょっと強力な展開機能がある程度だが、メタデータを扱う方法が色々あるので、実際は相当強力だ。

ドキュメント処理をPandocに任せ、テンプレートもPandocのものを使うことを前提にすれば、メタデータを操作する機能があればPandocを拡張する形でさらに強力にできる。

現在はPandoc意外もサポートしているし、そのためにPandoc任せというわけではなくメタデータ処理機能はPureBuilder Simply自体にも組み込まれている。 だが、

  • ドキュメントメタデータ
  • ドキュメント生成
  • インデックスデータベース

という3要素を立てる形で設計されたことが、今に至る成功の鍵になった。

Blessing

当初のPureBuiler Simplyは名前通りシンプルさを優先していた。 つまり、なるべくPandocに任せることで手出しを最小限にする方向だった。 言い換えれば、従来のPureBuilderの流れを汲んで、プログラマブル=ユーザーはプログラムするという前提、という考え方だった。

version 2は単に機能追加だけでなく、この考え方自体を見直したものだと言える。 そして、アップデートリリースの少なかったversion 2の目玉機能がBlessingだ。

Blessingは引数としてメタデータ(frontmatter)を受け取る関数を定義できるものだ。 frontmatterを破壊的に変更すると、それはメタデータの更新として反映される。

というと単純だが、実は挙動と値、そしてタイミングが鍵になっている。

まず、そもそものfrontmatterはドキュメント自体に書かれているものである。 version 2からは分離して書くこともできるようになったが、いずれにせよあらかじめstaticに書かれたものなのは変わりない。

そして、PureBuilder Simplyはfrontmatterを統合的されたメタデータとして扱い、様々な値を追加する。 特に役に立つのがpage_html_escaped_externalのようなパラメータで、これによりSNSシェアのURLをテンプレートでの展開で静的生成してしまうことが可能になっている。

そして、blessingが呼ばれるのはこのようなPureBuilder Simplyのシステムがfrontmatterを更新した後である。 つまり、システム的に生成された情報を含むメタデータを参照した上でメタデータを更新できる。 さらには、ドキュメント自体も参照できる。

用例としてわかりやすいところだと、ChienomiはLinuxカテゴリとビジネスカテゴリは特別なバナーが出るが、Pandocテンプレートで判定するために

if url.include?("linux")
  frontmatter["linux_article"] = true
elsif url.include?("business")
  frontmatter["business_article"] = true
end

としている。 (urlは配列)

使い心地へのこだわり

ソフトウェアの使い心地・触り心地は私のとても重視するところだ。 それを実現するために重視しているのは、一貫性があることと受容的であること。

ソフトウェアのあり方として必要なことについては一貫性を保たねばならないが、そうでないことについては可能な限りユーザーの選択を尊重し、不要な強制をしない、というのがその実現方法だと考えている。

だから、PureBuilder Simplyは必要なものが少なく、制約が少なく、選択肢が多い。

本質的にPureBuilder Simplyが要求するのは、.pbsimply.yamlをドキュメントソースルートに配置することと、そのファイルで指定したパスにテンプレートファイルを置くことだけだ。 他に推奨事項はあるが、必ずしなければならないことはない。

ファイル配置の強制は私はとても嫌いなのでしないようにしている。

また、必要なものを増やすと作者の意図から離れることが難しくなるので、それも避けている。 あくまで使いたい機能があるのなら、その機能のためのファイルを置くだけでいい。

ドキュメントフォーマットも、世の中の大勢としてはMarkdownだけで良いとするだろう。 だが、私はMarkdownではないフォーマットを好む人がいることを知っている。 だから、最初からReSTがサポートされていたのだ。

当初はメタデータを分離して書けなかったため、メタデータを解釈するためにメタデータのフォーマットが決まっている必要があり、そのためにドキュメントフォーマットが指定されていたが、分離して書けるようになったことでその制限は必要なくなったことから、現在ではPandocを使う場合はPandocがサポートする任意のフォーマットを使えるようになっている。

さらに、PureBuilder SimplyはPandocありきのソフトウェアだが、Pandocを好まない人や、Pandocのインストールを困難に感じる人がいた。 このため、Pandoc以外のドキュメントプロセッサを選択できるようにした。

こうした使い心地・触り心地の追求は「配慮」であるとされることが多い。だが、私は「尊重」だと考えている。 例えば、私はソースドキュメントにt2tやwiki記法を使いたいとは思わない。 だが、それを望む人がいることを知っている。つまりは私と異なる考え方を持っている人がいるということだが、きっとその人と私はわかり合えないだろう。 それでも、その人にとって価値のあることだというのを、可能な限り受け入れ、認める。 「配慮」のように想定することは、結局のところ自分の器だ。それは、考え方の違う人にとっては窮屈なものになる。 だから尊重こそがソフトウェアに優しい肌触りを与えるのだと私は考えているのだ。

My Browser Profile Chooser

概要

My Browser Profile Chooserはブラウザプロファイルを使い分けるための支援をするスクリプトである。 当初はZshで書かれており、現在はRubyで書かれている。

ブラウザの種別にもよるが、Chromium系とFirefox系のウェブブラウザはプロファイルを使い分けるためのCLIオプションがある。 Chromium系の場合は--user-data-dirで、Firefox系の場合は-P(プロファイル選択)と--profile(ファイルパス)だ。

プロファイルを使い分けることは、XSSの抑制、トラッキングの軽減、利便性の向上に寄与する。 例えばweb ytと打つことでYouTube用にチューニングした設定になっているプロファイルでYouTubeをスタートアップページとして開く、といったことが可能なわけだ。

地味な利点としてこの起動がfork&execになっているというのがある。 このため、端末から起動した場合でもシェルは拘束されず、SIGHUPに悩まされることもない。 これは端末ベースで実行することになるsystemd-nspawn環境やWSL環境で結構便利な要素だ。

このソフトウェアは、私が最も使用頻度の高い自作プログラムである。

設定の配慮

My Browser Profile Chooserの設定ファイルはYAMLが採用されている。 これは書き方の自由度と可読性という点でかなり利点がある。

書き心地は見れば分かる。例えばこれ

---
Profiles:
  chienomi: {type: viv}

これでweb chienomiとするとVivaldiをchienomiというプロファイルで起動する。 Vivaldiは自前でプロファイル機能を持っているが、このプロファイルはちゃんと分離して機能しない1ため、Chromium由来の--user-data-dirを使用するようになっている。

プロファイル名はpstrと呼ばれている。これは、プロファイルがどのような形式で取られるかによらずプロファイル名を指定する。 Firefoxの場合は-Pオプションを使うのでわかりやすいが、Chromium系の場合はファイルパスが必要なので、基準パスを指定できるようになっている。 デフォルトは~/.config/reasonset/browsers

さらに、pstrは省略可能で、省略した場合はプロファイル名と同じになる。 指定できることで便利な面もあるのだが、ほとんどの場合指定する必要がないため、設定ファイルを書くのが非常に容易というメリットがある。

そして、optsだ。 名前に反してこれはブラウザに対するCLI引数になるので、オプション以外も指定できる。 例としては

---
Profiles:
  void:
    type: viv
    opts: [--incognito]

とすれば、web voidvivaldi --user-data-dir=$HOME/.config/reasonset/browsers/void --incognitoとして起動される。

やたらUIがちっっさいサイトのために特定のプロファイルではズーム倍率を変更したい、なんてこともできるので、普通にはなかなかやりづらいプロファイル機能の活用が可能だ。

そして、これがあるからpstrが活用できる。 こんな感じで

---
Profiles:
  chienomi: {pstr: hrkm, opts: [https://chienomi.org/]}
  jlinuxers: {pstr: hrkm, opts: [https://jlinuxer.org/]}

と、複数の名前でプロファイルを共有しつつ、開くページを変えるということが可能になるのだ。

ブラウザサポート

現在、My Browser Profile Chooserがサポートしているのは

  • Firefox (stable/beta/developer)
  • Waterfox
  • Seamonkey
  • Liberwolf
  • Cachy-browser
  • GNU IceCat
  • Floorp
  • Midori
  • Falkon
  • Chromium (stable/beta/developer)
  • Opera (stable/beta/developer)
  • Vivaldi (stable/snapshot)
  • Edge (stable/beta/dev)
  • Brave
  • Slimjet
  • Iron
  • Epiphany
  • mybrowse
  • Weaver

である。

基本的に、「コマンドラインでプロファイルを指定できるもの」という制約はつくが、それを満たすもので私が知る限りのものを採用するようにしている。

私が実際に使うことがあるかどうかに関わらず、だ。 もちろん、尊重のために。

使い心地のために

使い心地をよくする工夫の多くは設定ファイルに詰まっているが、コマンドライン自体にもある。

My Browser Profile Chooserのコマンド形式は

web <profile> [args...]
web - [args...]
web

である。

まず、args...はウェブブラウザのCLIでそのまま渡せるものなので、多くのウェブブラウザでページを開くのにそのまま使える。 .desktopファイルが付属するので、リンクを開くためにも使えるということだ。

webだけの場合、プロファイルを選択する画面が出る。 これは簡略化のためで私は使わないが、プロファイル名を打つのが面倒な場合にwebだけ打ってメニューから選ぶという使い方が可能だ。

この選択も、デフォルトはZenityだが、yad, kdialog, rofi, dmenu, bemenuがサポートされており、好みに合わせて選べる。 個人的にはrofiが便利だと思う。デフォルトにしてもいいくらい。

で、webだけ打つのはおまけのようなもので、重要なのはこの「プロファイルを選択する機能をMy Browser Profile Chooserが持っている」ということ。

引数がない場合という条件だとURLを与えるときに使えないので、この選択ダイアログを出すための方法としてプロファイル名に-を指定することができるようになっている。 そして、web.desktopファイルのほうでは

Exec=web - %U

としている。

結果として、これをデフォルトブラウザにするとリンクを開くときにそのリンクを開くのに使いたいプロファイルを指定できる。デフォルトブラウザで勝手に開かれて履歴が汚れるといった体験から解放される。

効率化されたコード

例えば、Vivaldiのコマンドラインオプションは基本的にChromiumと同じだ。 だから、プロファイルの指定処理は共通化したい。

というわけで効率化されているので、My Browser Profile Chooserの大部分はブラウザの定義だけで、めちゃくちゃコンパクト。

どうなっているかというと、まず

STYLES = {
  #...
  ->(profile, env, bin, arg, conf) {
    profile = File.join(conf[], profile)
    exec(env, bin, "--user-data-dir=#{profile}", *arg)
  },

  #...
}

という形でProcが定義されていて、

BROWSERS = {
  #...
  "ch" => {"chromium", proc: STYLES[]},
  #...
  "viv" => {"vivaldi-stable", proc: STYLES[]},
}

という形でどのProcを使うかを指定し、コマンド名だけが変数要素になっている。 さらにエイリアスも

BROWSERS["chi"] = BROWSERS["ch"]

という感じで指定されている。

ちなみに、Procを呼び出すProcではなくコマンド名が文字列になっていることで、コマンド名を環境に合わせて調整するOverrideが機能するようになっている。

Altpostlocal

概要

AltpostlocalはPostfixのlocal(8), virtual(8)を置き換えるMDAである。

あまりに大量のバウンス攻撃に悩まされ、Postfixでバウンスの完全封殺が困難だと悟った結果、「もう自分で作るしかねぇ」となって作ったもの。 ついでにalias関連機能の大幅拡張をしている。

以前にMDAでMailDeliverを作っているけど、あれはプログラマブルフィルタとして使うものでlocal(8)のpipeとかで使う後段のものなのでちょっと違う。

考え方

AltpostlocalはPostfixのpipe(local(8)のpipeではない)で呼ばれるもの。 バウンス攻撃を防ぐことを第一にしているので、外部にはキャッチオールに見える。 これは、PostfixでpipeでMDAに渡してしまうと、SMTPセッションで拒否することができないという都合もある。このあたりはMilterにしたほうが便利な部分。

Altpostlocalはメールを必ず受け取る。 そのメールをどうするのかはAltpostlocalがコントロールする。 ただ゜し、スパムチェックのようなフィルタ処理はAltpostlocalの領分ではないので、local(8)同様にpipe機能もある。

必ず受け取ったあとの処理の効果を上げる意味でもaliases機能の強化は重要。 拡張アドレスの柔軟性はPostfixとは比べ物にならないほど高い。 ……ただし、Eximはもっとやれるらしい。

ほぼそれが全て

正直、ソフトウェアデザインで全てが決まっている感じで、挙動と設定のしやすささえ押さえておけばこうなる、という感じ。

逆に言えば何を備えるかというところがソフトウェアとしての出来を決めている。

ただ個人的には自然な実装というか、達成したい目的としてどのような機能・挙動が望ましいかということを考えて、その上で必要なものを実装しただけという感じなので、「想像力をはたらかせました」意外のことは言いづらい。

DLsite Voice Utils

概要

音声作品を対象にして評価・検索をできるようにしたもの。 一般的にはカタログ、ライブラリと呼ばれるタイプのソフトウェア。

ローカルHTMLファイルとして動作するSPAになっている。

データベース

これは他のところでもやっているやつだけれど、データベースはoriginとassembledに分けられるようになっている。 そして、assembledはJavaScriptのファイル(.js)で、script要素からロードする。

これは結構重要なこと。ローカルファイルとして読みたいのならscript要素から読むのが楽だ。

JavaScriptファイルを構成する上ではJSONが完全に閉じたリテラルであるということ、そしてJavaScriptがホワイトスペースをほとんどの場合等価に扱うというのを利用している。 だから、

["var database =", json].join("\n")

でJavaScriptファイルが作れる。 そしてこうしておけば1行目を削除すればJSONとして読める。

ウェブアプリケーションだとユーザーに全データは見せられないのと、通信サイズ的に全データは渡せないというのがあるけれど、ローカルで動作する前提のものであればそんな配慮はいらない。 かなりのサイズがあっても現代のIOスピードとメモリ容量の前には大した話ではないし、JavaScriptのリテラル構築は(ChromiumでもFirefoxでも)非常に速いのでかなり有効な方法だ。

直接編集するのには巨大なJSONファイルは向いていないが、プログラムがロードするだけのものであれば気にならない。 だから分けるのも非常に有効だ。

自分が使うためのものだから、データ更新があったら再度assembleしてブラウザリロードすればいいだけ。とてもシンプル。

連携

カスタムスキーマによるディレクトリを開く動作は快適性に直結するし、現在はLWMPとも連携できるのも結構大きい。

ウェブアプリは作品情報の編集には対応していないので、全体のフローとNemoありきの設計は全体を構成する重要な要素だ。

ベースとなるデータは探索で新規タイトルを検出して作り、作品評価はNemoの右クリックから行う。手動と自動の配分は、プログラムを書くことと運用すること両方を楽にする上で重要なこと。

カスタム検索エンジン

整頓で足りなくなった世界が検索だ、ということは常々言っている。

検索エンジンの自作が、Googleを作るような話に聞こえるのか気後れする人が多いのだけど、検索する対象、存在するデータ、検索したい軸を全部知っている状態でスタートするので、自分が使うための検索エンジンを作るのは非常に簡単だし、ちゃんと特定の目的のために特定のものを対象として作ったほうがずっと使いやすい。

大事なのはちゃんと対象に合ったものを作ること。

ローカルウェブアプリ

現代的な視点から言えばローカルHTMLとして動作するSPAは結構制約があるのだけど、逆に制約がかかるものはそもそも使えないから色々な配慮をしなくていいが通用する。

HTML/CSSで作れる部品はGUIフレームワークと比べても結構リッチだったりすることがあるので、ローカルSPAはかなり手っ取り早く書ける方法。

外部と通信したいとかで制約が面倒な場合はウェブサーバーで配信するようにしても、ローカルだけの話ならそんなにしんどくない。http-serverとか使ってもいいし。

ローカルファイルの参照も必要なら、Nautralinojsに可能性を感じてる。

LocalWebMediaPlayer

概要

LAN向けにメディアファイルの再生とファイルブラウザを持たせたもの。 PC上の動画や音楽をスマホで楽しむというのが動機だった。

実現方法を試行錯誤した上で、こちらも「もはや自作するしかない!」となったやつだ。

簡易なウェブアプリケーションの形をしていて、Vanilla JavaScript SPAとCGIアプリケーションで構成されている。 サーバーとしてはLighttpdを前提にしている。

現在は画像ビューワ/ブックリーダーも追加し、テキストファイルも読めるようになった。

なおLAN向けなのは、認証機能がないこと、マルチユーザーには対応していないこと、悪意に耐えるように作っていないことからだ。

CGI

これも割と何度も言っていることだけど、小さなアプリを動かす環境としてはCGIがベストである場合は全然ある。

が、今回の場合そんなに重要でもなかった。 静的ファイル配信の問題を考えたときに、ThinやPumaでは不満だった。 このため構成しやすいLighttpdを考えたのだけど、サーバーを2つ起動してリバースプロキシを構成するのはとても面倒だ。この面倒を低減するためにCGIにした。

サーバーアプリケーションが呼ばれるのはディレクトリリストの取得のためなので対話的かつ頻度も少ない。特にCGIで困ることはない。 まぁ、メタデータ処理を追加したときはオンメモリにできなかったが。

ファイル配信

動画データの配信を考えるとちゃんとパーシャルに送ってくれて、コントロールも良好であることが望ましい。 こういうのはwebサーバーの領域なので、ちゃんとしたwebサーバーを使うのが一番だ。

だからLighttpdで配信する。 ファイル配信にアプリケーションを絡ませようとするのは余計なことでしかない。

けれど、ここに認証が入るとその余計なことが必要となる。 ローカル向けに割り切る効果はここに大きく出ているのだ。

メタデータ問題

別にファイル名が見えていて特別困るかというとそんなことはないんだけど、完成度を高めるためにメタデータに対応したかった。 あと、気分の問題もあった。

なので、メタデータ機能はオプショナルなものにした。 なおかつ、キャッシュして可能な限り軽くなるようにした。

キャッシュはファイルになるので、クリアしたいときはファイルを消すだけ。 最高効率ではないけれど一番手軽。そして、必要なのはその手軽。

メタデータの取得タイミングもひと工夫で、再生時はどんな形であれプレイリストを構成するので、メタデータが有効ならそのときに取りに行く。 キャッシュにないとプレイリストの反映が固まるような挙動になってしまうけれど、これが一番自然。Audaciousとかがこの挙動。

ウェブブラウザの標準機能

最近はウェブブラウザに載っている機能を再発明するケースが多いけれど、ウェブブラウザは別にHTMLだけを理解するわけじゃなく、メディアファイルサポートはそれなりにある場合が多い。

特に音声と動画のサポートはそれなりに充実しているので、ブラウザまかせで十分快適。

PDFに関してはobjectで埋めることはできるけど、決して快適ではないので外部で開くことにした。スマートフォンだとサポートはまちまちだし。

画像に関しては画像表示そのものはブラウザの機能としてあるけれど、画像ビューワ、あるいはブックリーダーとしてはだいぶ機能不足。 なのでここは手出しが必要。

テキストに関してはボックスを作ってそこに入れ込むだけの話なので、自作しても負担は少ない。

ステート管理

ここが一番つらい部分。フレームワークに逃げたくなる。 popStateがもう少しなんとかなったらいいのにね。

地道にがんばった。 長々とした苦労話は過去の記事で。

スパイシー系

MultiMachine Utils

概要

ローカルネットワーク内のクラスタ動作向けのユーティリティ集。 実際のところ、主にmmffrシリーズとmmfft9シリーズが中心である。

mmffrとmmfft9はともに動画エンコーディングを分散処理するためのキューシステムだが、その作りと目的が違う。 mmffrはRindaを使っており、任意のエンコーダで処理する。 mmfft9はUnixサーバーで、vpx-vp9またはsvt-av1を使用する。

mmffrの場合はRindaなので、takeするアイテムを選別しない。 また、エンコーダの動作としてシングルスレッドで動作させることができ、なおかつシングルスレッドで動作させることにメリットがあるエンコーダとそうでないエンコーダが存在するため、並列数の最適化はできない。基本的にホストが処理できる限界までエンコードを回す。

mmfft9はエンコーダが固定であり、なおかつシングルスレッドでの動作を期待している。 さらに、動画はゲームのキャプチャであることを想定している。 これに利用してキューアイテムの取得に色々な工夫を加えている。

注目はmmfft9である。

power

動画ソースが同種である場合、基本的に処理fpsはほとんど変わらない。 そして、動画ソースが同種である場合、ビットレートもほとんど変わらない。

結果として、動画ソースごとに時間あたりで処理できるバイトサイズはおおよそ同じである。

という前提にmmfft9は基づいている。 ここでいう動画ソース種別は、ゲームタイトル、グラフィックス設定、ジオメトリの組み合わせを指している。

そこで、ホストが処理できている時間あたりのバイトサイズを “power” という概念にしている。 全体から平均に対する割合で外れ値を除外した平均がhost power、タイトルごとのものがtitle powerである。

このhost powerにより傘下しているホストごとの「処理能力の順位」がつけられる。 これが分配に使用される。

なお、代わりにtitle powerを使って特定の動画ソースに対する処理能力をもとに順位をつけることもできる。 こっちのほうが精度は高い。

先に前提として、バイトサイズはソース動画種別で大きく変動しうる。 特にジオメトリは影響が大きい。キャプチャはVBRのビットレート指定で行われることが多いからだ。 また、フィルタ設定などをしている場合も大きく変動してしまう場合がある。

この問題の補正のために、タイトルごとにパワーレートという係数を設定できる。 これは単純にソース動画のファイルサイズを、その係数をかけた値とみなすというものである。

パワーレートは分配には影響するが、記録には影響しない。 記録に影響すると補正するごとに自然と1に近づいてしまい、行ったり来たりすることになるからだ。

パワーレート補正までしたソースファイルサイズは分配と所要時間推測に使われる。

分配はかなり工夫がある。

まず、大量の動画エンコードを分散して行う場合、終了タイミングを考えると一番最悪なのは大きい動画を最後に取ることである。 こうすると、最後に並列化できないものを取って1つの動画のために延々と待たされるということが発生する。 Ryzen9 5950Xで4kソースのエンコードはだいたい1〜2fpsくらいなので、場合によっては1週間とか待つことになり、かなりしんどい。 このため、分配は基本的には大きいものから処理させることで終了時刻を早くしている。

が、単に分配を大きいほうからするだけだと非力なホストが大きい動画を抱えて、すべての処理が終わってもまだまだ終わらない、といったことが起こりうる。 この対策として、ホストが予約する数を強いホストから順に割り当てるようにしている。

例えば、強い順にA, B, Cのホストがあり、それぞれ10, 6, 4の予約数を持っているとする。 この場合、Aは最も大きいほうから配られるが、Bは11番目が配られる。4は17番目が配られる。 なお、オプション指定で小さいほうから取ったり、推測所要時間の上限を指定したりもできる。

非力なホストに大きい動画が分配されるのはかなり大変なことになるケースが多いので、そうならないような挙動になっている。 なお、予約数は実際のプロセス数と同じにするとタイミングの問題で非力なホストが残ることがあるので、2〜4程度多めに設定することが推奨される。

プレフィックス/タイトル

さて、ここまで「タイトル」と言ってきたが、mmfft9の用語としては本当は「プレフィックス」が正しい。

mmfft9では、ソース動画のディレクトリが「プレフィックス」で、出力動画のディレクトリが「タイトル」である。 これが分かれているのは、エンコード結果としてはひとつのディレクトリにまとめてほしいが、ソース動画としては別の種別にしたいというケースがあるからだ。

典型的にはジオメトリ。私の手元で実際にあるやつだと、wuwa_FHD, wuwa_UWQHD, wuwa_4kがある。 そして、これらのタイトルは共通して鳴潮である。

title powerはプレフィックス単位で計測するべきであり、実際プレフィックス単位で行われる。

これは単にパワー計算のためだけではなく、同一タイトルだが異なる設定を適用したいという場合のためでもある。

音ゲー専用

ファイル名処理に工夫のあるmmfft9だが、音ゲー専用の設定がある。

基本的には-ss, -toを設定できるというだけだが、通常はいらないものなのでソースファイルリストの形式が変わるというひと工夫。

もちろん、私が音ゲーをやるし、記録を残したプレイを切り抜いて残したりするからという理由だ。

手触り良く

ReLiveで録ったり、Color OSの機能で録ったりするからソースファイルのファイル名は柔軟に。

ソースファイルリストを作るのは大変なので、print -lで簡単に作れるように。

場合によっては何ヶ月も処理が続くものだから、不安にならないように進捗状況をこまめに確認できるように。 そして、残り時間の目安も出せるように。

分配の工夫でなるべく早く完了できるように。

何ヶ月もかかる場合のために、ホストが現在の処理完了後に離脱できるように。

処理を中断してキュー戻しができるように。

自分が使うツールだから、こういう工夫をたくさん盛り込んでいる。 自分がこうだったらいいなというものはそのようにできる、というのは、自作する大きなメリットだ。

DLsite Voice Utils

スパイシーポイント

作品ごとメタデータと作品の配置をアッセンブルしてデータベースにする、というのも多少はスパイシーではあるけれど、最大のスパイシーは付属ユーティリティであるvoicestat.rbvoicestat2.rbにある。

これらはデータベースから統計情報を作り、それをもとに分析するツールである。 meta.jsのファイル構造を利用している部分でもある。

注目はこの分析方法が普通ではないということだ。 そして、驚くほど単純なロジックで、驚くほど良い精度が出ている。

voicestat.rbは統計情報が中心になっているものである。

voicestat2.rbはtrendingの分析が中心。

余人には作れまい系

orbital queue

概要

Gemで公開している、Orbital Designに基づくキュー実装。 ChienomiのActivityPub実装のために作られた。

ActivityPub実装のほうははいつできるのかというと……

ActivityPubの仕様を読み解くのが大変なのでCopilotに頼っていたのだけど、Copilotがいい加減なことしか返さなくなったので進捗していないのだ。

いかにもリファレンス実装

orbital queueの動作はとてもシンプルで、Orbital Designの主張通り。

書く者は唯一であることを想定し、ファイルに出力する。 ファイルはユニークであるように生成されるため、競合を心配する必要はない。

ワーカーは何種類いてもよく、存在するアイテムをどれかひとつ取得する。

他のデータや他のプロセスの状態には干渉できない。

クセが強いように思うかもしれないが、シンプルなキューとしてかなり気軽に扱える。 そもそもがシンプル化するためのデザインパターンなので、使う側も考慮しなければならない事項がとても少ないのだ。

require 'orbitalqueue'

queue = OrbitalQueue.new("/path/to/queue")
data = {123}

queue.push(data)
require 'orbitalqueue'

queue = OrbitalQueue.new("/path/to/queue")
item = queue.pop
data = item.data

# Something, something...

item.complete

競合要素がないため、パフォーマンス面でも非常に優れている。 I/Oがボトルネックにならなければ、ほぼリニアにスケールするプログラムが簡単に書ける。

さらにひと工夫

原理としては単純だけど、使いやすくするなら工夫が必要な要素をライブラリに持たせたい。

orbital queueではdeferとかできるようになっている。

queue.each_item do |item|
  begin
    # Something...
  rescue
    item.defer(Time.now + 300)     # Retry after 5 minutes.
  end
end

#deferしたやつは#resumeで戻す必要があるのだけど、これが分かれているのはライブラリ側の実装をシンプルにするためでもあるけれど、「ひとつのことを正しくやる」というUnixismに対する忠実さでもある。 deferredキューの面倒を見るのは、deferredキューの面倒を見るプロセスがやるべきだ。

ripcd/normalize-wav4cd-halspl.rb

概要

ripcd自体はCDのリッピングの補助ツール。

caraudio-2000-jp-toolsはCDプレイヤーしかない私の愛車のために作られたユーティリティだ。

まずデジタルミュージックライブラリをCDにする、というところからして、まず余人はやらない。 そして、その中でもnormalize-wav4cd-halspl.rbは音楽的な知識が詰め込まれているため、余人にはそうそうできない。

処理内容

これは独立して記事になっている。

音楽を単純なLUFSベースの音量下げでノーマライズできるという考えはかなり幻想寄りで、そもそも放送業界のかなり狭い前提で機能するものだからうまくいくはずのない話。 normalize-wav4cd-halspl.rbは音楽家の経験として、簡易的、かつ一律に適用できる方法で聴きやすくするための手順をコードにしている。

dmarc-report-tools

DMARCの分析というの自体がニッチすぎるのか、DMARC用のCLIツール、ほんと見つからない。

ないなら作ってしまえというパターン。

ただ、私の環境を前提にして作られているのと、そもそもDMARCを分析しても打てる手は相当限られているということから完成度はあまり高くない。 高くはないが、希少なツールで、かつ実用性のあるものではある。

ひとつポイントとしては、unarにアーカイブファイルじゃないものを食わせても問題ないので、とりあえず全部食わせていること。


  1. Vivaldiのプロファイルはデータの保存につけるプレフィックスのような扱いで、例えば履歴のようなものは独立した形になるのだが、プロファイル込みでひとつの設定として扱われているため、プロファイルを個別に扱いにくい。また、そもそも起動時にCLIオプションでプロファイルを指定できるようになっていないため、リンクを開くときに指定したプロファイルを使ってほしいといった操作ができない。あくまでブラウザの中で使い分ける、パーティションのような存在なのだ↩︎