您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
AI 이미지 메타데이터 보기
当前为
// ==UserScript== // @name AI 이미지 EXIF 뷰어 // @namespace https://gf.qytechs.cn/users/815641 // @match https://www.pixiv.net/artworks/* // @match https://arca.live/b/aiart/* // @match https://arca.live/b/hypernetworks/* // @match https://arca.live/b/aiartreal/* // @match https://arca.live/b/aireal/* // @version 1.6.0 // @author 우흐 // @require https://gf.qytechs.cn/scripts/452821-upng-js/code/UPNGjs.js?version=1103227 // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/exif-library.min.js // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @require https://cdn.jsdelivr.net/npm/[email protected]/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 // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @description AI 이미지 메타데이터 보기 // @license MIT // ==/UserScript== (async function () { "use strict"; try { if (typeof GM_registerMenuCommand == undefined) { return; } else { GM_registerMenuCommand("Pixiv 뷰어 사용 토글", function () { if (GM_getValue("usePixiv", false)) { GM_setValue("usePixiv", false); Swal.fire({ toast: true, position: "bottom", showConfirmButton: false, timer: 2000, icon: "error", title: "Pixiv 비활성화", }); } else { GM_setValue("usePixiv", true); Swal.fire({ toast: true, position: "bottom", showConfirmButton: false, timer: 2000, icon: "success", title: "Pixiv 활성화", }); } }); } } catch (err) { console.log(err); } if (location.href.match("/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(exif, src) { try { let prompt; let negativePrompt; let steps; let sampler; let cfgScale; let seed; let size; let model; let modelHash; let software; let rawData; let negativePromptCopy; const maxLength = 350; if (exif.tabs?.tEXt?.Description || exif.tabs?.iTXt?.Description) { rawData = `${exif.tabs.tEXt.Description}\n${exif.tabs.tEXt.Comment}`; rawData = exif.tabs.tEXt.Description ? `${exif.tabs.tEXt.Description}\n${exif.tabs.tEXt.Comment}` : `${exif.tabs.iTXt.Description}\n${exif.tabs.tEXt.Comment}`; const comment = JSON.parse(exif.tabs.tEXt.Comment); prompt = (exif.tabs.tEXt?.Description ? exif.tabs.tEXt?.Description : exif.tabs.iTXt?.Description) ?? "정보 없음"; negativePrompt = comment.uc ?? "정보 없음"; steps = comment.steps ?? "정보 없음"; sampler = comment.sampler ?? "정보 없음"; cfgScale = comment.scale ?? "정보 없음"; seed = comment.seed ?? "정보 없음"; size = `${exif.width}x${exif.height}` ?? "정보 없음"; model = "정보 없음"; modelHash = "정보 없음"; software = exif.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 { let parameters = exif.replaceAll("<", "<").replaceAll(">", ">"); rawData = 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"] ?? "정보 없음"; modelHash = comment["Model hash"] ?? "정보 없음"; software = "Stable Diffusion 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>`; } } new ClipboardJS(".copy_btn"); Swal.fire({ title: "메타데이터 요약", html: ` <style> table{ border-collapse: collapse; } .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; cursor: pointer; } #moreBtn { border: 0; border-radius: .25em; background-color: #82C3EC; color: #fff; padding: .175rem 0.55rem; cursor: pointer; } #modelBtn { border: 0; border-radius: .25em; background-color: #228be6; color: #fff; padding: .175rem 0.55rem; line-height: 1.5; font-size: 0.8em; cursor: pointer; } 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} <a href='https://civitai.com/?query=${modelHash}' target='_blank'><button id="modelBtn">CIVITAI</button></a></td> <td class="nowrap"> <button class="copy_btn" data-clipboard-text="${modelHash}">복사</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> `, }); } }); } function blobToBase64(blob) { return new Promise((resolve, _) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); } async function extract(src, Referer) { if ( src.includes(".png") || src.includes(".jpg") || src.includes(".jpeg") || src.includes(".webp") ) { Swal.fire({ title: "로드 중!", width: "15rem", didOpen: () => { Swal.showLoading(); }, }); const res = await GM_fetch(src, { headers: { Referer }, }); const blob = await res.blob(); try { const exif = exifLib.load(await blobToBase64(blob)); console.log(exif); if (exif.Title === "AI generated image") { const novelAi = await UPNG.decode(await blob.arrayBuffer()); console.log(novelAi); analyze(novelAi, src); } else if (exif.parameters) { const png = exif.parameters; console.log(png); analyze(png, src); } else if (exif.Exif[37510]) { const jpgWebp = exif.Exif[37510].replace("UNICODE", "").replaceAll("\u0000", ""); console.log(jpgWebp); analyze(jpgWebp, 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: "지원되지 않는 형식의 이미지", }); } } if (GM_getValue("usePixiv", false) && location.href.match("pixiv.net")) { let isAi = false; document.arrive("footer > ul > li > span > a", function () { if (this.href === "https://www.pixiv.help/hc/articles/11866167926809") { isAi = true; } }); document.arrive("a > img", function () { if (this.alt === "pixiv") return; if (isAi) { this.onclick = async () => { const src = `${this.parentNode.href}`; extract(src, "https://www.pixiv.net/"); document.arrive("div[role=presentation]:last-child > div > div", function () { this.click(); }); }; } }); } if (location.href.match("arca.live")) { 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`; extract(src, "https://arca.live/"); }; }); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址