pantomime / AI 이미지 EXIF 뷰어

// ==UserScript==
// @name        AI 이미지 EXIF 뷰어
// @namespace   https://greasyfork.org/users/815641
// @match       https://arca.live/b/aiart/*
// @version     1.1.0
// @author      우흐
// @require     https://cdn.jsdelivr.net/gh/photopea/UPNG.js/UPNG.js
// @require     https://cdn.jsdelivr.net/npm/sweetalert2@11
// @require     https://cdn.jsdelivr.net/npm/clipboard@2.0.10/dist/clipboard.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js
// @grant       GM_xmlhttpRequest
// @description AI 이미지 메타데이터 보기
// @license MIT
// ==/UserScript==

(function () {
  function get(url) {
    GM_xmlhttpRequest({
      method: "GET",
      responseType: "arraybuffer",
      url: url,
      onload: (e) => {
        try {
          const png = UPNG.decode(e.response);

          let prompt;
          let negativePrompt;
          let steps;
          let sampler;
          let cfgScale;
          let seed;
          let size;
          let denoisingStrength;
          let software;
          let rawData;

          if (png.tabs.tEXt.Description) {
            rawData = `"Prompt": ${png.tabs.tEXt.Description} ${png.tabs.tEXt.Comment}`;
            const comment = JSON.parse(png.tabs.tEXt.Comment);

            prompt = png.tabs.tEXt.Description ?? "정보 없음";
            negativePrompt = comment.uc ?? "정보 없음";
            steps = comment.steps ?? "정보 없음";
            sampler = comment.sampler ?? "정보 없음";
            cfgScale = comment.scale ?? "정보 없음";
            seed = comment.seed ?? "정보 없음";
            size = `${png.width}x${png.height}` ?? "정보 없음";
            denoisingStrength = comment.strength ?? "정보 없음";
            software = png.tabs.tEXt.Software ?? "정보 없음";
          } else if (png.tabs.tEXt.parameters) {
            rawData = png.tabs.tEXt.parameters;
            const parameters = png.tabs.tEXt.parameters;
            const data = parameters.split(": ");

            let temp = [];
            data.forEach((el, i) => {
              if (i < 2) {
                temp = [
                  ...temp,
                  el.substr(el.lastIndexOf("\n")).replace("\n", ""),
                ];
              } else {
                temp = [
                  ...temp,
                  el.substr(el.lastIndexOf(",")).replace(", ", ""),
                ];
              }
            });
            temp.pop();

            let dataObj = {};

            temp.forEach((el, i) => {
              if (i === temp.length - 1) {
                const arr = parameters
                  .substring(parameters.indexOf(temp[i]))
                  .split(": ");

                const obj = {};
                obj[`${arr[0]}`] = arr[1];
                dataObj = { ...dataObj, ...obj };
              } else {
                const arr = parameters
                  .substring(
                    parameters.indexOf(temp[i]),
                    parameters.indexOf(temp[i + 1])
                  )
                  .split(": ");
                const obj = {};
                obj[`${arr[0]}`] = arr[1].replace(", ", "");

                dataObj = { ...dataObj, ...obj };
              }
            });

            prompt = data[0]?.replace("Negative prompt", "") ?? "정보 없음";
            negativePrompt = dataObj["Negative prompt"] ?? "정보 없음";
            steps = dataObj["Steps"] ?? "정보 없음";
            sampler = dataObj["Sampler"] ?? "정보 없음";
            cfgScale = dataObj["CFG scale"] ?? "정보 없음";
            seed = dataObj["Seed"] ?? "정보 없음";
            size = dataObj["Size"] ?? "정보 없음";
            denoisingStrength = dataObj["Denoising strength"] ?? "정보 없음";
            software = "Web UI 또는 기타...";
          }
          Swal.fire({
            title: "메타데이터",
            html: `
            <style>
              .modalTable {
                border:1px solid #b3adad;
                padding:5px;
                font-size: 12px;
              }
              .modalTable td {
                border:1px solid #b3adad;
                text-align:left;
                padding:5px;
              }
              .modalTable td.nowrap {
                white-space:nowrap;
                font-weight: bold;
              }
              .copy_btn {
                border: 0;
                border-radius: .25em;
                background-color: #7066e0;
                font-size: 1em;
                color: #fff;
                line-height: 1.5;
                padding: .375rem .75rem;
              }
            </style>
            <table class="modalTable" width="100%">
              <tbody>
                <tr>
                  <td class="nowrap">Prompt</td>
                  <td id="prompt">${prompt}</td>
                  <td class="nowrap">
                    <button class="copy_btn" data-clipboard-target="#prompt">복사</button>
                  </td>
                </tr>
                <tr>
                  <td class="nowrap">Negative prompt</td>
                  <td id="negativePrompt">${negativePrompt}</td>
                  <td class="nowrap">
                    <button class="copy_btn" data-clipboard-target="#negativePrompt">복사</button>
                  </td>
                </tr>
                <tr>
                  <td class="nowrap">Steps</td>
                  <td id="steps">${steps}</td>
                  <td class="nowrap">
                    <button class="copy_btn" data-clipboard-target="#steps">복사</button>
                  </td>
                </tr>
                <tr>
                  <td class="nowrap">Sampler</td>
                  <td id="sampler">${sampler}</td>
                  <td class="nowrap">
                    <button class="copy_btn" data-clipboard-target="#sampler">복사</button>
                  </td>
                </tr>
                <tr>
                  <td class="nowrap">CFG scale</td>
                  <td id="cfgScale">${cfgScale}</td>
                  <td class="nowrap">
                    <button class="copy_btn" data-clipboard-target="#cfgScale">복사</button>
                  </td>
                </tr>
                <tr>
                  <td class="nowrap">Seed</td>
                  <td id="seed">${seed}</td>
                  <td class="nowrap">
                    <button class="copy_btn" data-clipboard-target="#seed">복사</button>
                  </td>
                </tr>
                <tr>
                  <td class="nowrap">Size</td>
                  <td id="size">${size}</td>
                  <td class="nowrap">
                    <button class="copy_btn" data-clipboard-target="#size">복사</button>
                  </td>
                </tr>
                <tr>
                  <td class="nowrap">Denoising strength</td>
                  <td id="denoisingStrength">${denoisingStrength}</td>
                  <td class="nowrap">
                    <button class="copy_btn" data-clipboard-target="#denoisingStrength">복사</button>
                  </td>
                </tr>
                <tr>
                  <td class="nowrap">Software</td>
                  <td id="software">${software}</td>
                  <td class="nowrap">
                    <button class="copy_btn" data-clipboard-target="#software">복사</button>
                  </td>
                </tr>
              </tbody>
            </table>
            `,
            footer: `
            <details>
              <summary style="text-align: center;">원본 보기</summary>
              <p>${rawData}}</p>
            </details>
          `,
            width: "50%",
            confirmButtonText: "확인",
          });
        } catch (error) {
          const Toast = Swal.mixin({
            toast: true,
            position: "top-end",
            showConfirmButton: false,
            timer: 1500,
            timerProgressBar: true,
          });

          Toast.fire({
            icon: "error",
            title: "메타데이터 업는듯?",
          });
        }
      },
      onerror: (error) => {
        console.log(error);
      },
    });
  }
  new ClipboardJS(".copy_btn");

  document.arrive("a > img", function () {
    if (this.classList.contains("channel-icon")) return;
    this.parentNode.removeAttribute("href");

    this.onclick = () => {
      get(`${this.src}?type=orig`);
    };
  });
})();