Electronを活用したデスクトップアプリケーション、JavaScriptベースのフロントエンド、そしてTypeScriptベースのバックエンドを組み合わせ、VRChatの写真を解析・メタデータを抽出した上で友達ネットワークを可視化するフルスタックアプリケーションを作ってみました。
ダウンロードやSource Codeに関しては下記のリンクを確認してください。
1. 使用技術スタックと開発環境
バックエンド
-
言語/フレームワーク: TypeScript, Node.js, Express
→ バックエンドAPIサーバーはExpress/RESTful APIをベースに構築され、TypeScriptの強力な型検査とモジュールシステムにより、安定したコード構造が保たれています。 -
ファイルシステム操作: Node.js組み込みモジュール(fs, path)および fs-extra
→ 画像ディレクトリの探索、メタデータファイルの読み書き作業を行います。 -
HTTPリクエスト処理および外部連携: node-fetch, form-data
→ 画像アップロード時に外部一時ファイルホスティングサービス(tmpfiles.org)と連携します。 -
リアルタイム進捗状況更新: Server-Sent Events (SSE)
→ メタデータ生成作業の進捗状況をリアルタイムでクライアントにストリーミングします。
フロントエンド
-
言語/ライブラリ: JavaScript, HTML, CSS, d3.js
→ VRChatの友達ネットワークの可視化は、d3.jsのforce simulationを活用して実装され、ユーザーとのインタラクション(検索、スライダー、ズーム、ドラッグなど)をサポートします。 - UIフレームワークとスタイリング: CSS
-
通信: fetch API, EventSource (SSE)
→ バックエンドとのデータ通信や、リアルタイムの進捗状況更新に使用されます。 -
Electronレンダラープロセス
→ Electron内でHTML/CSS/JavaScriptで作成されたフロントエンドが、デスクトップアプリケーションとして動作します。
2. バックエンド:画像メタデータのパースとAPIサーバー
2-1. 画像メタデータのパース
バックエンドの主要機能のひとつは、VRChatの写真(PNGファイル)内に含まれるメタデータを抽出することです。写真には、PNG標準のiTXtチャンクを利用してJSON形式のメタデータが埋め込まれています。
主要な実装ロジックは src/utils/pngParser.ts
に実装され、以下の手順でメタデータを抽出します。
- PNG形式の検証: PNGファイルのシグネチャ(137,80,78,71,13,10,26,10)を確認します。
- チャンクのパース: ファイルデータを順次読み込み、各チャンクの長さ、タイプ、データ、CRC値をパースします。
-
iTXtチャンクの抽出とパース: iTXtチャンクが存在する場合、そのチャンクからテキストデータを抽出し、JSONとしてパースします。
もしiTXtチャンクが存在しない場合は、デフォルトで現在の時刻とファイル名を含む空のメタデータオブジェクトを返します。
pngParser.ts 主要コードスニペット
private extractMetadata(data: Buffer): any { if (!this.isPNG(data)) { throw new Error("Invalid PNG format"); } const chunks = this.parseChunks(data); const iTXtChunk = chunks.find(chunk => chunk.type === "iTXt"); if (!iTXtChunk) { return { timestamp: new Date().toISOString(), filename: path.basename(this.filePath), metadata: {} }; } try { const metadata = this.parseITXtChunk(iTXtChunk.data); return { ...JSON.parse(metadata), timestamp: new Date().toISOString(), filename: path.basename(this.filePath) }; } catch (error) { throw new Error(`Failed to parse metadata: ${error instanceof Error ? error.message : "Unknown error"}`); } }
2-2. メタデータの構造と含まれる情報
メタデータはPNGファイルのiTXtチャンク内にJSON文字列として保存されます。一般的に含まれる情報は以下の通りです:
- timestamp: メタデータ生成時刻(バックエンドでデフォルト値として追加)
- filename: 元のPNGファイル名
-
metadata: ユーザーまたはシステムによって挿入された追加情報
→ 例として、VRChat内での友達関係を表現するためのfriend ID、友達間の相互作用回数、タグ情報などが含まれる可能性があります。
この構造により、バックエンドは各JSONファイルを読み込み、VRChatの友達ネットワークデータに変換し、d3.jsによる可視化に必要なノードとリンクのデータを生成できます。
2-3. APIサーバーとSSEによる進捗状況更新
バックエンドのExpressサーバーは、さまざまなAPIエンドポイントを提供しています。代表的なエンドポイントは以下の通りです:
- /api/metadata/generate: PNGファイルをスキャンし、メタデータを生成する作業を開始し、SSEを通じて "start"、"progress"、"complete" などのイベントをストリーミングします。
- /api/metadata/date-range, /api/metadata/filter: メタデータファイルの日付範囲を計算したり、特定期間内のファイルのみをフィルタリングします。
- /api/upload/image: クライアントから送信されたBase64画像データを外部ホスティングサービスにアップロードします。
SSEを使用したリアルタイム更新により、ユーザーはメタデータ生成作業の進捗状況を確認し、必要に応じて作業を中断することが可能です。
3. フロントエンド:VRChat友達ネットワークの可視化
3-1. データの可視化とd3.js
フロントエンドでは、d3.jsを利用して友達ネットワークを可視化します。主要な実装は public/js/network-visualization.js
にあり、force simulationを通じてノードとリンクの物理的な配置を計算します。
例えば、以下のコードでは各ノードの大きさと色は出現回数(友達関係の頻度など)に基づいて決定され、リンクは関係強度により線の太さと透明度が調整されます。
ネットワーク可視化コードスニペット
const simulation = d3.forceSimulation(filteredNodes) .force('link', d3.forceLink(currentLinks) .id(d => d.id) .distance(d => 200 / (d.strength || 1))) .force('charge', d3.forceManyBody().strength(d => -500 * Math.sqrt(d.count))) .force('collide', d3.forceCollide().radius(d => Math.sqrt(d.count) * 10 + 20)) .force('x', d3.forceX(width / 2).strength(0.03)) .force('y', d3.forceY(height / 2).strength(0.03));
3-2. UIとインタラクション機能
フロントエンドはElectronのレンダラープロセス内で動作し、ユーザーとの多様なインタラクションをサポートします。
- 検索機能: 特定の友達名でノードをハイライトし、関連するリンクのみを強調表示します。
- 日付スライダー: メタデータの日付範囲に基づいてフィルタリングし、特定期間のデータのみを可視化します。
- 共有およびエクスポート: SVGエクスポートやPNG変換を通じて生成されたネットワークグラフを外部に共有可能にします。
- ローディングオーバーレイと進捗状況表示: メタデータ生成作業中は、ローディングスピナーとともに現在の進捗状況をリアルタイムでユーザーに表示します。