SHAMEZO
はじめる

技術仕様

v1.5.1

システム構成

処理を担う3コンポーネント(プロセス)と、共有するデータストアで構成されます。画像処理のような重い処理は専用のコンポーネントへ隔離し、スケールできるように配慮されています。

構成要素役割
web (Node.js)フロントエンドです。レンダリングと軽量なAPIを公開します。
worker-front (Node.js)ジョブ管理です。Web・Bot・メールの各経路からのリクエストを管理し、画像処理ノードへの指示、およびそのためのAPIを公開します。また、一部の定期ジョブのスケジューラーも兼ねています。
worker (Node.js)画像処理です。外部には公開されていません。
PostgreSQL投稿・ユーザー・実績などのデータを保存します。
Object Storage生成した画像やサムネイルを保存します。

画像処理

元画像とパラメーターを受け取ると、即座に画像処理を行い、出力します。処理は、ベータテスト期間中のA/Bテストを通じて、品質を確保しつつより早い処理を実現するパイプライン処理を最適化しています。

画像処理フロー: 回転→HEIC/HEIF判定でJPEG化→長辺2048px判定で縮小→テキスト合成→中間JPEG→AVIF化(上限超過なら品質を下げて再試行)→生成画像
生成リクエスト受信後のサーバー側画像処理フロー(横にスクロールできます)

向きの補正・デコード

スマートフォンなどでは、縦向きで撮影した場合でも、画像自体は横向きに保存されることが多いです。EXIF の Orientation タグを参照したうえで、向きを補正します。

リサイズ

長辺が 2048px を超える入力画像は、あらかじめアスペクト比を保ったまま長辺 2048px へ縮小してから後続の処理に回します。

文字合成

テキストオーバーレイを Canvas 描画し、画像に合成します。パフォーマンスを維持するため、合成画像は一度JPEG(quality 90)で出力します。

最終出力

投稿先にかかわらず AVIF で書き出し、SHAMEZOに保存します。この出力時の再エンコードで、元画像の EXIF メタデータは全て取り除かれます。投稿先ごとに変わるのは、Fediverseへアップロードするときの形式だけです。

投稿先SHAMEZOの保存形式投稿時の形式EXIF
MastodonAVIFJPEG(投稿時に変換)削除
MisskeyAVIFAVIF(変換なし)削除

AVIF出力では、 quality 80, effort 2 で書き出し、サイズを確認します。万が一出力が上限(16MB)を超えた場合、quality を段階的に下げて再エンコードし、上限以下に収まった時点の出力を採用します。

Mastodonは現在AVIFのアップロードを受け付けないため(セキュリティ対応で無効化されたデコーダーがAVIFも担当しているため)、投稿の直前にJPEG(quality 90)へ変換して送信します。変換したJPEGは保存せず、SHAMEZO上の画像はAVIFのままです。なお、MastodonはAVIFを受け取れた頃も自動でJPEGへ変換して配信していたため、連合先に届く画像は変わりません。

派生画像

  • カレンダー用サムネイル: WebP, 128×128px, quality 80 / あらかじめ作成し、ストレージに保存
  • OGP用サムネイル: WebP / 必要に応じてオンデマンドで作成し、ストレージに保存しない

通常、OGP用には本来の生成画像を指定することも可能です。しかし、XはAVIFに対応していないため、他のレガシーなフォーマットへの変換が必要です。そこで、SHAMEZO内ではWebP変換を行わず、管理者が開発・運用している別インフラ(自前のMisskeyメディアプロキシ)でオンデマンド変換を行い、OGP用のサムネイルを作成しています。このサムネイルはストレージには格納されず、主にCDNのキャッシュに頼っています。

タイムアウト

不明なエラーを発生させないため、Tier ごとにカスケードタイムアウトを設定しています。

Tier制限時間
画像処理本体18秒
API22秒
ブラウザ(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上にのみ残ります
カスタム絵文字の一覧をみる

機能: スレッド

自分または他の人の写真に続けて、写真をスレッドとして投稿できます。1枚目→2枚目→3枚目のように何枚でも続けられます。

  • Webでは、写真の詳細ページのメニューから「スレッドに投稿」を選びます。
  • Bot投稿・メール投稿では、オプション「連」を付けると、あなたの直前の投稿に続けて投稿します(続ける写真は選べません。他の人の写真には続けられません)。直前の投稿が無いとき、Bot投稿はエラーで返信し、メール投稿は通常の投稿になります。
  • 続けた写真と続けられた写真の両方がFediverseサーバーに投稿されている場合は、Fediverse上でもスレッド(返信)としてつながります。どちらかが「このサービスのみ」の場合は、SHAMEZO上だけでつながります。
  • Fediverse上で続け先の投稿が見つからない場合(削除済み・連合が未反映など)は、SHAMEZO上だけでつながり、Fediverseには単独の投稿として投稿します。
  • 他の人の写真に続けたとき、Fediverseに送る本文に相手へのメンションは付けません。そのため相手の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です
  • 皆勤賞はユーザー画面のカレンダータブ・実績タブで公開され、誰でも確認できます
  • 皆勤賞は月ごとに計算されるため、毎月獲得することができます

月間投稿

  • 1ヶ月のうち10日・20日投稿すると、その月の「月間投稿」の実績を獲得できます(毎月獲得できます)
  • 数えるのは実際に投稿した日だけです(穴埋めした日は数えません)

穴埋め(2026年10月分から)

  • 投稿できなかった日は、その日より後に1日2枚以上投稿し、カレンダーの「編集」で埋める日を選ぶと、2枚目の投稿で穴埋めできます
  • 穴埋めには「穴埋めポイント」を1日につき1pt使います。穴埋めに使える投稿は1日1枚までです
  • 穴埋めは自動では行われません(穴埋めする日と写真はご自身で選びます)
  • 埋められるのは、穴埋めに使う投稿の日より前の日だけです(未来の日付は埋められません)
  • 締め切り(翌月10日 23:59)までに投稿していれば、翌月の投稿も前月の穴埋めに使えます。月末の日もこれで埋められます
  • 穴埋めに使える投稿は1日1枚までで、これは月をまたいでも同じです(翌月◯日の投稿を前月の穴埋めに使うと、その日は翌月の穴埋めには使えません)
  • その月の穴埋めは翌月10日 23:59 まで行えます。それを過ぎると、穴埋めの指定・解除はできなくなります
  • 穴埋めポイントはその月の穴埋めにだけ使え、翌月には持ち越せません。穴埋めを解除すると、そのポイントは同じ月の別の日に使い直せます

穴埋めポイントがもらえる条件

  • handon.club のユーザー: 毎月1日に+1pt(その月に登録した場合は登録時)
  • 先月中からアカウントがあり、先月の皆勤賞を獲得しなかった場合: 毎月11日に+1pt
  • その月に新規登録した場合: 登録時に「登録日の前日までの日数」ぶん(2日の登録で+1pt、9日以降の登録で上限の+8pt)
  • その月に何かしらの実績を獲得した場合: +1pt(月に1回まで。月間投稿の実績は除く)
  • このほか、イベントなどで付与されることがあります

ポイントの残りと付与の履歴は、カレンダータブ(ご自身のカレンダーのみ)で確認できます。付与されたときは通知でもお知らせします。

機能: カレンダー

ユーザーページの「カレンダー」タブでは、その月の投稿を日ごとのサムネイルで一覧表示します。 編集モードでは、各日に表示するサムネイルを変更できるほか、皆勤賞の穴埋めに使う投稿を選べます (サムネイルを変更後、その画像が削除された場合は、自動で再選出されます。穴埋めに使った投稿を削除して その日の投稿が1枚になった場合は、その穴埋めは解除され、ポイントが戻ります)。