- // ==UserScript==
- // @name AI 이미지 EXIF 뷰어
- // @namespace https://gf.qytechs.cn/users/815641
- // @match https://arca.live/b/aiart/*
- // @match https://arca.live/b/hypernetworks/*
- // @version 1.5.1
- // @author 우흐
- // @require https://gf.qytechs.cn/scripts/452821-upng-js/code/UPNGjs.js?version=1103227
- // @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
- // @require https://gf.qytechs.cn/scripts/421384-gm-fetch/code/GM_fetch.js
- // @grant GM_xmlhttpRequest
- // @grant unsafeWindow
-
- // @description AI 이미지 메타데이터 보기
- // @license MIT
- // ==/UserScript==
-
- (async function () {
- "use strict";
- const url = location.href;
- const write = "https://arca.live/b/aiart/write";
-
- if (url === write) {
- document.arrive(".images-multi-upload", function () {
- document.getElementById("saveExif").checked = true;
- });
- return;
- }
-
- unsafeWindow.toggle = function () {
- const dots = document.getElementById("dots");
- const moreText = document.getElementById("more");
- const btnText = document.getElementById("moreBtn");
- if (dots.style.display === "none") {
- dots.style.display = "inline";
- btnText.innerHTML = " 더 보기";
- moreText.style.display = "none";
- } else {
- dots.style.display = "none";
- btnText.innerHTML = "숨기기";
- moreText.style.display = "inline";
- }
- };
-
- function analyze(png, src) {
- try {
- let prompt;
- let negativePrompt;
- let steps;
- let sampler;
- let cfgScale;
- let seed;
- let size;
- let model;
- let software;
- let rawData;
- let negativePromptCopy;
- const maxLength = 300;
-
- if (png.tabs.tEXt?.Description || png.tabs.iTXt?.Description) {
- rawData = `${png.tabs.tEXt.Description}\n${png.tabs.tEXt.Comment}`;
- rawData = png.tabs.tEXt.Description
- ? `${png.tabs.tEXt.Description}\n${png.tabs.tEXt.Comment}`
- : `${png.tabs.iTXt.Description}\n${png.tabs.tEXt.Comment}`;
- const comment = JSON.parse(png.tabs.tEXt.Comment);
-
- prompt =
- (png.tabs.tEXt?.Description ? png.tabs.tEXt?.Description : png.tabs.iTXt?.Description) ??
- "정보 없음";
- negativePrompt = comment.uc ?? "정보 없음";
- steps = comment.steps ?? "정보 없음";
- sampler = comment.sampler ?? "정보 없음";
- cfgScale = comment.scale ?? "정보 없음";
- seed = comment.seed ?? "정보 없음";
- size = `${png.width}x${png.height}` ?? "정보 없음";
- model = "정보 없음";
- software = png.tabs.tEXt.Software ?? "정보 없음";
-
- negativePromptCopy = negativePrompt;
-
- if (negativePrompt.length > maxLength) {
- negativePrompt = `${negativePrompt.slice(
- 0,
- maxLength
- )}<span id="dots">...</span><span id="more">${negativePrompt.slice(
- maxLength
- )}</span> <button id="moreBtn" onclick="toggle();">더 보기</button>`;
- }
- } else if (png.tabs.iTXt?.parameters || png.tabs.tEXt?.parameters) {
- rawData = png.tabs.tEXt?.parameters ? png.tabs.tEXt.parameters : png.tabs.iTXt.parameters;
- let parameters = png.tabs.tEXt?.parameters
- ? png.tabs.tEXt.parameters
- : png.tabs.iTXt.parameters;
-
- if (!parameters.includes("Negative prompt")) {
- parameters = parameters.replace("Steps", "\nNegative prompt: 정보 없음\nSteps");
- }
-
- parameters = parameters.split("Steps: ");
- parameters = `${parameters[0]
- .replaceAll(": ", ":")
- .replace("Negative prompt:", "Negative prompt: ")}Steps: ${parameters[1]}`;
-
- const commentStr = parameters.substring(parameters.indexOf("Steps"), parameters.length);
- const keyValuePairs = commentStr.split(", ");
- const comment = {};
-
- for (const pair of keyValuePairs) {
- const [key, value] = pair.split(": ");
- comment[key] = value;
- }
-
- prompt =
- parameters.indexOf("Negative prompt") === 0
- ? "정보 없음"
- : parameters.substring(0, parameters.indexOf("Negative prompt:"));
- negativePrompt = parameters
- .substring(parameters.indexOf("Negative prompt:"), parameters.indexOf("Steps:"))
- .replace("Negative prompt:", "");
- steps = comment["Steps"] ?? "정보 없음";
- sampler = comment["Sampler"] ?? "정보 없음";
- cfgScale = comment["CFG scale"] ?? "정보 없음";
- seed = comment["Seed"] ?? "정보 없음";
- size = comment["Size"] ?? "정보 없음";
- model = comment["Model"]
- ? `${comment["Model"]} [${comment["Model hash"]}]`
- : comment["Model hash"] ?? "정보 없음";
- software = "Web UI 또는 기타...";
-
- negativePromptCopy = negativePrompt;
-
- if (negativePrompt.length > maxLength) {
- negativePrompt = `${negativePrompt.slice(
- 0,
- maxLength
- )}<span id="dots">...</span><span id="more">${negativePrompt.slice(
- maxLength
- )}</span> <button id="moreBtn" onclick="toggle();">더 보기</button>`;
- }
- }
- 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;
- }
- #moreBtn {
- border: 0;
- border-radius: .25em;
- background-color: #82C3EC;
- color: #fff;
- padding: .175rem 0.55rem;
- }
- a {
- font-size: 15px;
- }
- #rawData > pre {
- white-space: pre-wrap;
- margin-bottom: 0;
- }
- #more {
- display: none;
- }
- </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<br>Prompt</td>
- <td id="negativePrompt">${negativePrompt}</td>
- <td class="nowrap">
- <button class="copy_btn" data-clipboard-text="${negativePromptCopy}">복사</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">Model</td>
- <td id="model">${model}</td>
- <td class="nowrap">
- <button class="copy_btn" data-clipboard-target="#model">복사</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>
- <a href="${src}" target="_blank">원본 링크</a>
- `,
- footer: `
- <details>
- <summary style="text-align: center;">원본 보기</summary>
- <table class="modalTable" width="100%">
- <tbody>
- <tr>
- <td id="rawData"><pre>${rawData}</pre></td>
- <td class="nowrap">
- <button class="copy_btn" data-clipboard-target="#rawData">복사</button>
- </td>
- </tr>
- </tbody>
- </table>
- </details>
- `,
- width: "50rem",
- confirmButtonText: "확인",
- });
- } catch (error) {
- Swal.fire({
- icon: "error",
- title: "분석 오류",
- html: `
- ${error}<br>
- 오류내용과 이미지를 댓글로 알려주세요`,
- });
- console.log(error);
- }
- }
-
- function deepDanbooru(src) {
- Swal.fire({
- icon: "error",
- title: "메타데이터 없음!",
- text: "Deep Danbooru로 찾아볼까요?",
- footer: `<a href="${src}" target="_blank">원본 링크</a>`,
- showCancelButton: true,
- confirmButtonText: "네",
- cancelButtonText: "아니오",
- showLoaderOnConfirm: true,
- backdrop: true,
- preConfirm: async () => {
- return GM_fetch(`https://deepdanbooru.donmai.us/?url=${src}&min_score=0.4`)
- .then((res) => {
- if (!res.status === 200) {
- Swal.showValidationMessage(`https://deepdanbooru.donmai.us 접속되는지 확인!`);
- }
- return res.json();
- })
- .catch((error) => {
- console.log(error);
- Swal.showValidationMessage(`https://deepdanbooru.donmai.us 접속되는지 확인!`);
- });
- },
- allowOutsideClick: () => !Swal.isLoading(),
- }).then((result) => {
- if (result.isConfirmed) {
- const tags = result.value.map((el) => el[0]).join(", ");
-
- Swal.fire({
- confirmButtonText: "닫기",
- html: `
- <style>
- #tags {
- width: 100%;
- height: 250px;
- padding: 10px;
- background: #FFF;
- color: black;
- border-radius: 8px;
- font-size: 15px;
- resize:none;
- }
- #tags::-webkit-scrollbar{
- display:none;
- }
- .copy_btn {
- cursor: pointer;
- }
- </style>
- <textarea id="tags">${tags}</textarea>
- <span class="copy_btn" data-clipboard-target="#tags">여기 눌려 전체 복사</span>
- `,
- });
- }
- });
- }
-
- document.arrive('a[href$="type=orig"] > img', function () {
- if (this.classList.contains("channel-icon")) return;
- this.parentNode.removeAttribute("href");
-
- this.onclick = async () => {
- const src = `${this.src}?type=orig`;
-
- if (src.includes(".png") || src.includes(".jpg") || src.includes(".jpeg")) {
- Swal.fire({
- title: "로드 중!",
- width: "15rem",
- didOpen: () => {
- Swal.showLoading();
- },
- });
-
- const res = await GM_fetch(src);
-
- const buffer = await res.arrayBuffer();
-
- try {
- const png = await UPNG.decode(buffer);
- console.log(png);
- new ClipboardJS(".copy_btn");
-
- if (
- png.tabs?.tEXt?.Description ||
- png.tabs?.tEXt?.parameters ||
- png.tabs?.iTXt?.parameters ||
- png.tabs?.iTXt?.Description
- ) {
- analyze(png, src);
- } else {
- deepDanbooru(src);
- }
- } catch (error) {
- deepDanbooru(src);
- }
- } else {
- Swal.fire({
- toast: true,
- position: "top-end",
- showConfirmButton: false,
- timer: 1000,
- timerProgressBar: true,
- icon: "error",
- title: "지원되지 않는 형식의 이미지",
- });
- }
- };
- });
- })();