技術仕様
v1.4.7システム構成
処理を担う3コンポーネント(プロセス)と、共有するデータストアで構成されます。画像処理のような重い処理は専用のコンポーネントへ隔離し、スケールできるように配慮されています。
| 構成要素 | 役割 |
|---|---|
| web (Node.js) | フロントエンドです。レンダリングと軽量なAPIを公開します。 |
| worker-front (Node.js) | ジョブ管理です。Web・Bot・メールの各経路からのリクエストを管理し、画像処理ノードへの指示、およびそのためのAPIを公開します。また、一部の定期ジョブのスケジューラーも兼ねています。 |
| worker (Node.js) | 画像処理です。外部には公開されていません。 |
| PostgreSQL | 投稿・ユーザー・実績などのデータを保存します。 |
| Object Storage | 生成した画像やサムネイルを保存します。 |
画像処理
元画像とパラメーターを受け取ると、即座に画像処理を行い、出力します。処理は、ベータテスト期間中のA/Bテストを通じて、品質を確保しつつより早い処理を実現するパイプライン処理を最適化しています。
向きの補正・デコード
スマートフォンなどでは、縦向きで撮影した場合でも、画像自体は横向きに保存されることが多いです。EXIF の Orientation タグを参照したうえで、向きを補正します。
リサイズ
長辺が 2048px を超える入力画像は、あらかじめアスペクト比を保ったまま長辺 2048px へ縮小してから後続の処理に回します。
文字合成
テキストオーバーレイを Canvas 描画し、画像に合成します。パフォーマンスを維持するため、合成画像は一度JPEG(quality 90)で出力します。
最終出力
投稿先に応じた形式・サイズ上限で書き出します。この出力時の再エンコードで、元画像の EXIF メタデータは全て取り除かれます。
| 投稿先 | 形式 | サイズ上限 | EXIF |
|---|---|---|---|
| Mastodon | AVIF | 16MB | 削除 |
| Misskey | AVIF | 250MB | 削除 |
AVIF出力では、 quality 80, effort 2 で書き出し、サイズを確認します。万が一出力がサイズ上限を超えた場合、quality を段階的に下げて再エンコードし、上限以下に収まった時点の出力を採用します。
派生画像
- カレンダー用サムネイル: WebP, 128×128px, quality 80 / あらかじめ作成し、ストレージに保存
- OGP用サムネイル: WebP / 必要に応じてオンデマンドで作成し、ストレージに保存しない
通常、OGP用には本来の生成画像を指定することも可能です。しかし、XはAVIFに対応していないため、他のレガシーなフォーマットへの変換が必要です。そこで、SHAMEZO内ではWebP変換を行わず、管理者が開発・運用している別インフラ(自前のMisskeyメディアプロキシ)でオンデマンド変換を行い、OGP用のサムネイルを作成しています。このサムネイルはストレージには格納されず、主にCDNのキャッシュに頼っています。
タイムアウト
不明なエラーを発生させないため、Tier ごとにカスケードタイムアウトを設定しています。
| Tier | 制限時間 |
|---|---|
| 画像処理本体 | 18秒 |
| API | 22秒 |
| ブラウザ(Web投稿の場合) | 25秒 |
アップロード処理は全体のタイムアウト値に含まれません。アップロードのタイムアウト値はなく、回線が遅い場合でも原則として永遠に待ち続けます。ただし、アップロードが20秒以上まったく進まなくなった場合のみ、中断されます。
入力制限
画像
- 枚数: 1枚
- フォーマット: JPEG, PNG, WebP, AVIF, HEIC, HEIF
- 最大ファイルサイズ: 20MB
- 最大ピクセル数: 200MP
合成テキスト
- 文字数: 1〜140 文字
代替テキスト(ALT)
- 文字数: 0〜1,500 文字
Misskeyへ投稿する場合、代替テキストの上限は 512 文字に自動で切り詰められます。ただし、その場合でも SHAMEZO 上には最大 1,500 文字が保存されます。
テキスト合成
文字位置
- 方向: 上下は横書き、左右は縦書き
- マージン: 全方向に画像短辺の5%または10pxの大きい方のマージンを設けて文字を描画(文字サイズ・文字位置により変動しない)
- 折り返し: マージンを除いた描画領域で、超過した位置で改行
文字サイズ
サイズ指定(小/中/大/特大)は、画像に対する相対的なサイズです。画像の短辺に14文字が納まる文字サイズを基準(中)とし、指定した倍率で描画します。
| 設定 | 基準に対する倍率 | 短辺に納まる文字数 |
|---|---|---|
| 小 | 0.75倍 | 約18文字 |
| 中 | 1.0倍 | 約14文字 |
| 大 | 1.4倍 | 約10文字 |
| 特大 | 2.35倍 | 約6文字 |
文字サイズには下限14px・上限500pxのハードリミットがあり、上記よりも優先されます。
文字幅
- Noto Sans JP(プロポーショナルフォント): フォントのもつ幅で描画
- ふい字・ラノベPOP(等幅フォント): 等幅で描画
等幅フォントの場合でも、ASCII文字列 U+0020–U+007E と半角カナ U+FF61–U+FF9F は半分の幅になるよう調整します。また、後述する Emoji は別処理となります。
縦書き
- 括弧類(「」、()、【】など)・長音記号(ー〜など)・リーダー(…‥)は90度回転して描画
- 句読点(、。)は右上寄せ
縁取りとカラー
視認性を高めるため、すべての文字に縁取りを追加して描画します。薄い色(白・緑・黄・桃・橙)には黒い縁取り、濃い色(赤・青・茶)には白い縁取りが付きます。縁取りは文字サイズのおよそ0.1倍(ただし最小は2px)となります。
| カラー | 文字色 | 縁取り色 |
|---|---|---|
| 白 | #FFFFFF | #000000 |
| 赤 | #FF0000 | #FFFFFF |
| 青 | #0000FF | #FFFFFF |
| 緑 | #00FF00 | #000000 |
| 黄 | #FFFF00 | #000000 |
| 茶 | #8B4513 | #FFFFFF |
| 桃 | #FFC0CB | #000000 |
| 橙 | #FFA500 | #000000 |
Emoji
ユーザーのフォント設定によらず、絵文字は常に Noto Emoji を用いて描画します。このフォントは本来プロポーショナルフォントのため、等幅フォントのふい字・ラノベPOPとともに使う場合は、ラインがずれないよう、 Noto Emoji 側の表示位置を調整しています。
EXIFに関する追加処理
位置情報・撮影日時・カメラ機種などが含まれるEXIF(Exchangeable image file format)情報は、個人情報が含まれる可能性があるため、常に削除してから投稿・アップロードします。 また、受け取った画像のEXIFは通常解析しません。
しかし、ユーザーが明示的に希望した場合に限って、EXIFを解析し、希望した範囲の情報を保存・投稿することもできます。 この場合でも、撮影日時・撮影方向・GPS座標は保存しません。
サーバーに保存される内容
| 投稿形式 | カメラ機種 | カメラ撮影情報 | 位置情報 | EXIF全体 |
|---|---|---|---|---|
| Web | 希望時のみ | 希望時のみ | 希望時のみ | × |
| Bot | × | × | × | × |
| メール | 希望時のみ | 希望時のみ | 希望時のみ | × |
- カメラ機種: カメラの機種および製造元
- カメラ撮影情報: F値・シャッター速度・ISO感度・焦点距離・レンズ名・露出補正・フラッシュの有無
- 位置情報: 国内の場合、都道府県または市町村レベル(GPS座標は含まない)
解析方法
- Web投稿: 画像を選択した時点で、お使いのブラウザ上で EXIF 解析を行います(サーバー上では解析しません)
- Bot投稿: Fediverse サーバー投稿時に EXIF が除去されてしまうため、EXIF 解析はサポートしていません
- メール投稿: メール受信時、サーバー上で元画像の EXIF 解析を行います
位置情報の変換
ユーザーが希望した場合のみ、国土地理院の逆ジオコーディング API に GPS 座標を送信し、市区町村コードを取得します。 この場合でも、保存されるのは都道府県または市町村までの情報で、GPS 座標は詳細なデバッグ用のログを含め、一切記録されません。
ブラウザの制限についての補足
スマートフォンのプライバシー保護機能により、Web投稿の場合、位置情報のみが自動的に除去されることがあります。
- iOS: 写真選択時、画面下部のオプションにて位置情報を含める設定に変更することで、位置情報をアップロードできます
- Android: 必ず回避できる方法はありませんが、機種・OSバージョンによって以下いずれの方法かで成功することもあります
- (1) 「写真を撮る」でその場で撮影
- (2) OS標準の共有機能で、SHAMEZOのPWAアプリに写真を送信
なお、Web投稿では、写真に位置情報が含まれない場合、過去に一度投稿したことのある都道府県または市町村に限って、手動で設定するオプションがあります。
レート制限
画像生成を短時間に連続して行った場合や、投稿数が普段より急増した場合など、いくつかのレート制限を設けています。詳細はセキュリティ上の理由から非開示とします。
Webブラウザ以外の投稿方法
Bot投稿(メンション投稿)
Bot宛にメンション付きで画像とコメントを送信して投稿します。BotはWebSocketでメンションを監視しており、ほぼ即座に処理が開始されます。定期的なポーリングで取りこぼしを補完します。処理が正常に完了した場合、元投稿は削除される仕組みとなっています(設定で変更可能)。
使い方・稼働状況をみるメール投稿
メニューで確認できる専用のメールアドレスに画像を添付して送信して投稿します。メールは Cloudflare Email Routing により Cloudflare Workers で処理され、ほぼ即座に処理が開始されます。セキュリティ対策(リスト型攻撃対策・MTAのバウンス防止)のため、投稿の成功・失敗によらず、サービス側からは一切のメール返信を行いません。
使い方をみる機能: リアクション
公開・未収載の投稿には好きな絵文字でリアクションできます。Fediverse上(Mastodonサーバー・Misskeyサーバー)でのお気に入り・リアクションとSHAMEZOのリアクションは自動で同期され、合算して表示されます。
- Fediverseサーバー上の情報が元データです
- SHAMEZO側から投稿元のサーバーに対し、リアクション件数などを取得します(結果はSHAMEZO側にキャッシュされており、投稿日時からの経過時間によりキャッシュTTLが変化します)。
- Fediverseサーバー上でお気に入り・リアクションをした場合、一定期間後にSHAMEZO側にも反映されます。取り消した場合も同様に反映されます
- SHAMEZO上でリアクションした場合、あなたの所属するFediverseサーバー上でもお気に入り・リアクションを行うため、Fediverse上・SHAMEZO側の両方にほぼ即時に反映されます(連合による遅延が発生する場合があります)
- Misskeyサーバーのユーザーは、ご自身のサーバーのカスタム絵文字が使えます。
- Mastodonサーバーのユーザーは、SHAMEZO独自のカスタム絵文字が使えます。ただしMastodonは連合上「お気に入り」しか送れないため、どの絵文字を選んでもFediverseサーバーへはお気に入りとして伝わり、リアクションした絵文字はSHAMEZO上にのみ残ります
機能: サーバーで開く
Fediverseでは、同じ投稿でもサーバーごとに別々のローカルID(Mastodonのステータス ID/Misskeyのノート ID)が割り当てられます。そのため、投稿者のサーバー上のURLをそのまま開いても、閲覧者は自分のアカウントで返信・ブースト(リノート)・お気に入りといった操作ができません。「サーバーで開く」は、投稿のActivityPub URIを閲覧者自身のサーバーで解決し、閲覧者のアカウントで操作できる状態にして開く機能です。
閲覧者が自分のFediverseサーバーのWebにログインしており、かつ元投稿が実際にFediverseへ投稿されている場合にのみ利用できます。
ローカルID解決方法
- Mastodon: 閲覧者のサーバーの authorize_interaction に URI を渡し、サーバー側で解決
- Misskey: クリック時にSHAMEZO内部で URI を解決し、閲覧者のサーバー上のローカルなノートIDを特定してから開く
機能: 皆勤賞
皆勤賞は、SHAMEZOにおける最も栄誉のある実績です。
- その月に毎日投稿することで獲得できます
- 1日の基準は日本標準時間の0:00~23:59です
- 投稿できなかった日が月3日以下(handon.club のユーザーは月4日以下)であれば救済措置があり、同月中の後日2枚以上投稿すると、投稿できなかった日の穴埋めとして処理されます
- 穴埋めは古い未投稿日から1日につき1回(1日のダブル投稿で1日分)で、未来の日付や月末以降に残った未投稿日は埋められません
- 皆勤賞はユーザー画面のカレンダータブ・実績タブで公開され、誰でも確認できます
- 皆勤賞は月ごとに計算されるため、毎月獲得することができます
機能: カレンダー
ユーザーページの「カレンダー」タブでは、その月の投稿を日ごとのサムネイルで一覧表示します。 編集モードでは、各日に表示するサムネイルを変更できるほか、皆勤賞の穴埋めに使う投稿も選び直せます (サムネイルを変更後、その画像が削除された場合は、自動で再選出されます)。