Repository navigation
センシティブ判定を外部サービス扱いにする #16804
Description
Activity
- added🐢PerformanceEfficiency related issue/PREfficiency related issue/PR✨FeatureThis adds/improves/enhances a featureThis adds/improves/enhances a featurepackages/backendServer side specific issue/PRServer side specific issue/PR
on Nov 17, 2025 ご意見を募集しています
nsfwjsはスクリプトサイズ的にもメモリ使用量的にもヘビーなため、外部化できれば本体の必要要件を緩和できる
nsfwjsに起因するトラブル(Misskeyのインストールに失敗する、メモリリークする等)に影響を受けなくなる一応確認ですが、Misskey本体から完全に切り離す方向性です?
yes
Reacted by おさむのひとKisaragiEffective commented
on Nov 17, 2025 CollaboratorMore actions良さそう、例のDockerコンテナが膨れるのも割と解決できそう
やるか
Reacted by おさむのひとReacted by 饺子w (Yumechi)実装を進める傍ら、下記も併せて考えたい気持ちがある(今使ってる人が混乱しないようなフォロー)
- Misskey本体コンパネのセンシティブ判定設定部分にMkInfoを置いて外部化されたこと、分離した機能のセットアップがべと必要なことを明記
- CHANGELOG.mdのNote項目への記載を厚めに
あと、分離先でも…
- 分離したリポジトリ側のREADME拡充(セットアップ方法、Misskey本体への設定方法など)
eternal-flame-AD commented
on Nov 18, 2025 SponsorContributorMore actionsI suggest if possible this be done with an asynchronous API. I.e. one for pulling a list of media without a classification, one API for setting the results of the classification, both are easy to do with DB and decouples heavy computation of classification from the state management loop.
Otherwise it might be a little .. shifting load to something "external" but not really reducing latency or increasing flexibility.
Reacted by おさむのひとeternal-flame-AD commented
on Nov 18, 2025 SponsorContributorMore actionsI think I meant partly considering relaxing the consistency requirement here, if the design is such that every single media has to come in, block the current federation job, get a result from a likely computationally heavy function (whether built into misskey or an external synchronous API), it likely does not reduce the process latency and causes more availability problem (what to do if the API call fails?).
Since there is no 100% recall algorithm for detecting "sensitive media" nor will there ever be in the near future, I think it is okay to let a post go through first and then later we can update its media to be sensitive if needed asynchronously, like most moderation work is - the goal is be scalable and minimize disruption.
センシティブフラグの付与を非同期的にするかどうかは外部サービス化とは異なる関心なので、別Issueの方が良さそうです
ここで非同期化の懸念について少し書くと、非同期の場合判定が完了する前にユーザーが投稿できてしまうことにならないかしらという点ですeternal-flame-AD commented
on Nov 18, 2025 SponsorContributorMore actionsIt's okay we can leave asynchronous on hold first but I believe fault tolerance should be a concern here - what happens if the API call fails? Should we just stop everything or have some other strategy?
5 remaining items
MPがない
https://github.com/misskey-dev/sensitive-detector
まだ何もありませんが、↑を作りました。ここにnsfw検出用のサーバ実装を生やしていきます。Reacted by syuilo良さそう
と思っていたけど、センシティブメディア判定を行う場合は対象ファイルを一個アップロードするたびに外部にHTTPリクエストが発生することになるので、そのオーバーヘッドが大丈夫そうかをやや懸念している例えばnsfwjsの採用をやめてネイティブバイナリ(napi)のセンシティブ判定ライブラリを作って入れるとか
オーバーヘッドがあることは確実ですが、非常に小さいと思っています。
- 転送データは299x299の小さいpng(おおよそ100KBくらい?多くても300KBくらいなはず)
- 仮に同じマシンに外部サービスを置くとして、1回のHTTPレスポンスで数ミリ秒ほどだと思われる
- 判定対象のファイルはノート投稿と比べて多くないはず
という所から…
画像の判定だけでなく動画の判定もあるからそっちをどうするかという問題もある
あー、前提を書いていなかった気がする
FileInfoServiceが持っているロジックも外部化する前提であれば、動画の転送コストは考慮する必要があると思います。
(どう迂回するか、迂回方法は安全かも考えないといけない)自分が考えているのは、動画からのフレーム抽出・ピックアップと画像の正規化はMisskey側に残し、外部サービス化するのは正規化された画像から判定する箇所のみ…という構成でした。
なので、送られてくるのは画像オンリー -> #16804 (comment) という意見となった次第です(冒頭の課題をコンパクトに解消するというのがコンセプト)動画の画像を切り出して送信してnsfw判定をするというのは私も同じ認識だけど、そうなると動画については切り出されたフレーム分だけリクエストが発生するよね、ということを言いたかった
なるほど。
misskey/packages/backend/src/core/FileInfoService.ts
Lines 209 to 272 in 666f78e
const [outDir, disposeOutDir] = await createTempDir(); try { const command = FFmpeg() .input(source) .inputOptions([ '-skip_frame', 'nokey', // 可能ならキーフレームのみを取得してほしいとする(そうなるとは限らない) '-lowres', '3', // 元の画質でデコードする必要はないので 1/8 画質でデコードしてもよいとする(そうなるとは限らない) ]) .noAudio() .videoFilters([ { filter: 'select', // フレームのフィルタリング options: { e: 'eq(pict_type,PICT_TYPE_I)', // I-Frame のみをフィルタする(VP9 とかはデコードしてみないとわからないっぽい) }, }, { filter: 'blackframe', // 暗いフレームの検出 options: { amount: '0', // 暗さに関わらず全てのフレームで測定値を取る }, }, { filter: 'metadata', options: { mode: 'select', // フレーム選択モード key: 'lavfi.blackframe.pblack', // フレームにおける暗部の百分率(前のフィルタからのメタデータを参照する) value: '50', function: 'less', // 50% 未満のフレームを選択する(50% 以上暗部があるフレームだと誤検知を招くかもしれないので) }, }, { filter: 'scale', options: { w: 299, h: 299, }, }, ]) .format('image2') .output(join(outDir, '%d.png')) .outputOptions(['-vsync', '0']); // 可変フレームレートにすることで穴埋めをさせない const results: ReturnType<typeof judgePrediction>[] = []; let frameIndex = 0; let targetIndex = 0; let nextIndex = 1; for await (const path of this.asyncIterateFrames(outDir, command)) { try { const index = frameIndex++; if (index !== targetIndex) { continue; } targetIndex = nextIndex; nextIndex += index; // fibonacci sequence によってフレーム数制限を掛ける const result = await this.aiService.detectSensitive(path); if (result) { results.push(judgePrediction(result)); } } finally { fs.promises.unlink(path); } } sensitive = results.filter(x => x[0]).length >= Math.ceil(results.length * sensitiveThreshold); porn = results.filter(x => x[1]).length >= Math.ceil(results.length * sensitiveThresholdForPorn);
↑を見る限り、- キーフレームが対象になっている
- 暗すぎるフレームは除外している
- 抽出されたキーフレーム一覧に対し、判定対象のフレーム数に制限をかけている
という点から、膨大な量がリクエストされることはないだろうと踏んでいます。
また、1リクエストに1ファイルのみではなく、複数のフレーム画像ファイルをまとめて送るようにするという方向での緩和も出来ると思います(コネクションのオーバヘッドという観点であれば、keep-aliveもあるし…)まとめて送るのは良さそう
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone


Summary
summalyみたいにURLを設定してそこにリクエストする形式にする
Purpose
Do you want to implement this feature yourself?