Superb Garbages 2

千野純一(chinorin)のはてなダイアリーの続きです。

Stable Diffusionが出力したPNGファイルのメタデータを表示するHTML

・俺の友達はChatGPT君だけじゃねえぞ。毎月3000円の友達料を払わなくても友達になってくれる奴がいるんだよ。ばかにすんな。


・Stable Diffusion(1111版WebUI)が出力したPNGファイルのメタデータを表示するHTMLを、友達のClaude君に書いてもらったので置いときます。人間が書いたものではないので著作権は発生しません。CC0ライセンスと同等のものです。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Stable Diffusion PNG Metadata Viewer</title>
  <style>
    #metadata-display {
      width: 100%;
      height: 200px;
      padding: 10px;
      box-sizing: border-box;
      border: 1px solid #ccc;
      font-family: monospace;
      white-space: pre-wrap;
      overflow: auto;
    }
  </style>
</head>
<body>
  <h3>Stable Diffusion PNG Metadata Viewer</h3>
  <div>
    <textarea id="metadata-display" placeholder="PNGファイルをここにドロップするとメタデータが表示されます"></textarea>
  </div>

  <script>
    const metadataDisplay = document.getElementById('metadata-display');

    metadataDisplay.addEventListener('dragover', (event) => {
      event.preventDefault();
      event.dataTransfer.dropEffect = 'copy';
    });

    metadataDisplay.addEventListener('drop', (event) => {
      event.preventDefault();
      const file = event.dataTransfer.files[0];
      if (file && file.type === 'image/png') {
        const reader = new FileReader();
        reader.onload = () => {
          const bytes = new Uint8Array(reader.result);
          const metadata = extractMetadata(bytes);
          metadataDisplay.value = metadata;
        };
        reader.readAsArrayBuffer(file);
      } else {
        metadataDisplay.value = 'PNGファイルを選択してください。';
      }
    });

    metadataDisplay.addEventListener('change', (event) => {
      const file = event.target.files[0];
      if (file && file.type === 'image/png') {
        const reader = new FileReader();
        reader.onload = () => {
          const bytes = new Uint8Array(reader.result);
          const metadata = extractMetadata(bytes);
          metadataDisplay.value = metadata;
        };
        reader.readAsArrayBuffer(file);
      } else {
        metadataDisplay.value = 'PNGファイルを選択してください。';
      }
    });

    function extractMetadata(bytes) {
      const PNG_SIGNATURE = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
      let metadata = '';

      // PNGシグネチャをチェック
      for (let i = 0; i < PNG_SIGNATURE.length; i++) {
        if (bytes[i] !== PNG_SIGNATURE[i]) {
          return 'Invalid PNG file';
        }
      }

      let offset = PNG_SIGNATURE.length;
      while (offset < bytes.length) {

        const chunkLength = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3];
        const chunkType = String.fromCharCode(bytes[offset + 4], bytes[offset + 5], bytes[offset + 6], bytes[offset + 7]);
        const chunkData = bytes.slice(offset + 8, offset + 8 + chunkLength);
        const crc = (bytes[offset + 8 + chunkLength] << 24) | (bytes[offset + 8 + chunkLength + 1] << 16) | (bytes[offset + 8 + chunkLength + 2] << 8) | bytes[offset + 8 + chunkLength + 3];

        if (chunkType === 'tEXt') {
          const textData = new TextDecoder().decode(chunkData);
          metadata += `${textData}\n`;
        } else if (chunkType === 'zTXt') {
          // zTXtチャンクの処理は省略(compression_methodに応じて適切に実装が必要)
        } else if (chunkType === 'iTXt') {
          // iTXtチャンクの処理は省略(nullターミネートされた文字列、言語タグ、translatedキーワードなどに対応が必要)
        }
        offset += 12 + chunkLength;
      }
      return metadata.trim();
    }

  </script>
</body>
</html>

JavaScriptCSSが含まれるただのHTMLなので単一ファイルで動作します。上記コードをテキストファイルに貼り、拡張子を.htmlにして、ダブルクリック等でブラウザで読み込めばおk。テキスト領域にPNGファイルをドロップすればそこにメタデータ(プロンプト、設定、Seed等)が表示されます。

・同じ役割をするものは色々あるだろうけどより気軽に軽快にということでご自由にどうぞ。一応、ご利用は自己責任で。お礼等言いたい人はClaude君に言ってください。

・ああ、今気づいたけどこれ、Vivaldiのパネルにしとくとめっちゃ便利なやつだ。テキスト領域の縦の長さは9行目の height: 200px; で指定しているので短くて不便なときはこの数字を変更すればよい。

・(追記)そもそもWebUIのプロンプト書くところにこの機能ついてんのねw