AI 이미지 EXIF 뷰어

AI 이미지 메타데이터 보기

目前为 2022-12-29 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name AI 이미지 EXIF 뷰어
  3. // @namespace https://gf.qytechs.cn/users/815641
  4. // @match https://arca.live/b/aiart/*
  5. // @match https://arca.live/b/hypernetworks/*
  6. // @version 1.5.1
  7. // @author 우흐
  8. // @require https://gf.qytechs.cn/scripts/452821-upng-js/code/UPNGjs.js?version=1103227
  9. // @require https://cdn.jsdelivr.net/npm/sweetalert2@11
  10. // @require https://cdn.jsdelivr.net/npm/clipboard@2.0.10/dist/clipboard.min.js
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js
  12. // @require https://gf.qytechs.cn/scripts/421384-gm-fetch/code/GM_fetch.js
  13. // @grant GM_xmlhttpRequest
  14. // @grant unsafeWindow
  15.  
  16. // @description AI 이미지 메타데이터 보기
  17. // @license MIT
  18. // ==/UserScript==
  19.  
  20. (async function () {
  21. "use strict";
  22. const url = location.href;
  23. const write = "https://arca.live/b/aiart/write";
  24.  
  25. if (url === write) {
  26. document.arrive(".images-multi-upload", function () {
  27. document.getElementById("saveExif").checked = true;
  28. });
  29. return;
  30. }
  31.  
  32. unsafeWindow.toggle = function () {
  33. const dots = document.getElementById("dots");
  34. const moreText = document.getElementById("more");
  35. const btnText = document.getElementById("moreBtn");
  36. if (dots.style.display === "none") {
  37. dots.style.display = "inline";
  38. btnText.innerHTML = " 더 보기";
  39. moreText.style.display = "none";
  40. } else {
  41. dots.style.display = "none";
  42. btnText.innerHTML = "숨기기";
  43. moreText.style.display = "inline";
  44. }
  45. };
  46.  
  47. function analyze(png, src) {
  48. try {
  49. let prompt;
  50. let negativePrompt;
  51. let steps;
  52. let sampler;
  53. let cfgScale;
  54. let seed;
  55. let size;
  56. let model;
  57. let software;
  58. let rawData;
  59. let negativePromptCopy;
  60. const maxLength = 300;
  61.  
  62. if (png.tabs.tEXt?.Description || png.tabs.iTXt?.Description) {
  63. rawData = `${png.tabs.tEXt.Description}\n${png.tabs.tEXt.Comment}`;
  64. rawData = png.tabs.tEXt.Description
  65. ? `${png.tabs.tEXt.Description}\n${png.tabs.tEXt.Comment}`
  66. : `${png.tabs.iTXt.Description}\n${png.tabs.tEXt.Comment}`;
  67. const comment = JSON.parse(png.tabs.tEXt.Comment);
  68.  
  69. prompt =
  70. (png.tabs.tEXt?.Description ? png.tabs.tEXt?.Description : png.tabs.iTXt?.Description) ??
  71. "정보 없음";
  72. negativePrompt = comment.uc ?? "정보 없음";
  73. steps = comment.steps ?? "정보 없음";
  74. sampler = comment.sampler ?? "정보 없음";
  75. cfgScale = comment.scale ?? "정보 없음";
  76. seed = comment.seed ?? "정보 없음";
  77. size = `${png.width}x${png.height}` ?? "정보 없음";
  78. model = "정보 없음";
  79. software = png.tabs.tEXt.Software ?? "정보 없음";
  80.  
  81. negativePromptCopy = negativePrompt;
  82.  
  83. if (negativePrompt.length > maxLength) {
  84. negativePrompt = `${negativePrompt.slice(
  85. 0,
  86. maxLength
  87. )}<span id="dots">...</span><span id="more">${negativePrompt.slice(
  88. maxLength
  89. )}</span> <button id="moreBtn" onclick="toggle();">더 보기</button>`;
  90. }
  91. } else if (png.tabs.iTXt?.parameters || png.tabs.tEXt?.parameters) {
  92. rawData = png.tabs.tEXt?.parameters ? png.tabs.tEXt.parameters : png.tabs.iTXt.parameters;
  93. let parameters = png.tabs.tEXt?.parameters
  94. ? png.tabs.tEXt.parameters
  95. : png.tabs.iTXt.parameters;
  96.  
  97. if (!parameters.includes("Negative prompt")) {
  98. parameters = parameters.replace("Steps", "\nNegative prompt: 정보 없음\nSteps");
  99. }
  100.  
  101. parameters = parameters.split("Steps: ");
  102. parameters = `${parameters[0]
  103. .replaceAll(": ", ":")
  104. .replace("Negative prompt:", "Negative prompt: ")}Steps: ${parameters[1]}`;
  105.  
  106. const commentStr = parameters.substring(parameters.indexOf("Steps"), parameters.length);
  107. const keyValuePairs = commentStr.split(", ");
  108. const comment = {};
  109.  
  110. for (const pair of keyValuePairs) {
  111. const [key, value] = pair.split(": ");
  112. comment[key] = value;
  113. }
  114.  
  115. prompt =
  116. parameters.indexOf("Negative prompt") === 0
  117. ? "정보 없음"
  118. : parameters.substring(0, parameters.indexOf("Negative prompt:"));
  119. negativePrompt = parameters
  120. .substring(parameters.indexOf("Negative prompt:"), parameters.indexOf("Steps:"))
  121. .replace("Negative prompt:", "");
  122. steps = comment["Steps"] ?? "정보 없음";
  123. sampler = comment["Sampler"] ?? "정보 없음";
  124. cfgScale = comment["CFG scale"] ?? "정보 없음";
  125. seed = comment["Seed"] ?? "정보 없음";
  126. size = comment["Size"] ?? "정보 없음";
  127. model = comment["Model"]
  128. ? `${comment["Model"]} [${comment["Model hash"]}]`
  129. : comment["Model hash"] ?? "정보 없음";
  130. software = "Web UI 또는 기타...";
  131.  
  132. negativePromptCopy = negativePrompt;
  133.  
  134. if (negativePrompt.length > maxLength) {
  135. negativePrompt = `${negativePrompt.slice(
  136. 0,
  137. maxLength
  138. )}<span id="dots">...</span><span id="more">${negativePrompt.slice(
  139. maxLength
  140. )}</span> <button id="moreBtn" onclick="toggle();">더 보기</button>`;
  141. }
  142. }
  143. Swal.fire({
  144. title: "메타데이터 요약",
  145. html: `
  146. <style>
  147. .modalTable {
  148. border:1px solid #b3adad;
  149. padding:5px;
  150. font-size: 12px;
  151. }
  152. .modalTable td {
  153. border:1px solid #b3adad;
  154. text-align:left;
  155. padding:5px;
  156. }
  157. .modalTable td.nowrap {
  158. white-space:nowrap;
  159. font-weight: bold;
  160. }
  161. .copy_btn {
  162. border: 0;
  163. border-radius: .25em;
  164. background-color: #7066e0;
  165. font-size: 1em;
  166. color: #fff;
  167. line-height: 1.5;
  168. padding: .375rem .75rem;
  169. }
  170. #moreBtn {
  171. border: 0;
  172. border-radius: .25em;
  173. background-color: #82C3EC;
  174. color: #fff;
  175. padding: .175rem 0.55rem;
  176. }
  177. a {
  178. font-size: 15px;
  179. }
  180. #rawData > pre {
  181. white-space: pre-wrap;
  182. margin-bottom: 0;
  183. }
  184. #more {
  185. display: none;
  186. }
  187. </style>
  188. <table class="modalTable" width="100%">
  189. <tbody>
  190. <tr>
  191. <td class="nowrap">Prompt</td>
  192. <td id="prompt">${prompt}</td>
  193. <td class="nowrap">
  194. <button class="copy_btn" data-clipboard-target="#prompt">복사</button>
  195. </td>
  196. </tr>
  197. <tr>
  198. <td class="nowrap">Negative<br>Prompt</td>
  199. <td id="negativePrompt">${negativePrompt}</td>
  200. <td class="nowrap">
  201. <button class="copy_btn" data-clipboard-text="${negativePromptCopy}">복사</button>
  202. </td>
  203. </tr>
  204. <tr>
  205. <td class="nowrap">Steps</td>
  206. <td id="steps">${steps}</td>
  207. <td class="nowrap">
  208. <button class="copy_btn" data-clipboard-target="#steps">복사</button>
  209. </td>
  210. </tr>
  211. <tr>
  212. <td class="nowrap">Sampler</td>
  213. <td id="sampler">${sampler}</td>
  214. <td class="nowrap">
  215. <button class="copy_btn" data-clipboard-target="#sampler">복사</button>
  216. </td>
  217. </tr>
  218. <tr>
  219. <td class="nowrap">CFG scale</td>
  220. <td id="cfgScale">${cfgScale}</td>
  221. <td class="nowrap">
  222. <button class="copy_btn" data-clipboard-target="#cfgScale">복사</button>
  223. </td>
  224. </tr>
  225. <tr>
  226. <td class="nowrap">Seed</td>
  227. <td id="seed">${seed}</td>
  228. <td class="nowrap">
  229. <button class="copy_btn" data-clipboard-target="#seed">복사</button>
  230. </td>
  231. </tr>
  232. <tr>
  233. <td class="nowrap">Size</td>
  234. <td id="size">${size}</td>
  235. <td class="nowrap">
  236. <button class="copy_btn" data-clipboard-target="#size">복사</button>
  237. </td>
  238. </tr>
  239. <tr>
  240. <td class="nowrap">Model</td>
  241. <td id="model">${model}</td>
  242. <td class="nowrap">
  243. <button class="copy_btn" data-clipboard-target="#model">복사</button>
  244. </td>
  245. </tr>
  246. <tr>
  247. <td class="nowrap">Software</td>
  248. <td id="software">${software}</td>
  249. <td class="nowrap">
  250. <button class="copy_btn" data-clipboard-target="#software">복사</button>
  251. </td>
  252. </tr>
  253. </tbody>
  254. </table>
  255. <a href="${src}" target="_blank">원본 링크</a>
  256. `,
  257. footer: `
  258. <details>
  259. <summary style="text-align: center;">원본 보기</summary>
  260. <table class="modalTable" width="100%">
  261. <tbody>
  262. <tr>
  263. <td id="rawData"><pre>${rawData}</pre></td>
  264. <td class="nowrap">
  265. <button class="copy_btn" data-clipboard-target="#rawData">복사</button>
  266. </td>
  267. </tr>
  268. </tbody>
  269. </table>
  270. </details>
  271. `,
  272. width: "50rem",
  273. confirmButtonText: "확인",
  274. });
  275. } catch (error) {
  276. Swal.fire({
  277. icon: "error",
  278. title: "분석 오류",
  279. html: `
  280. ${error}<br>
  281. 오류내용과 이미지를 댓글로 알려주세요`,
  282. });
  283. console.log(error);
  284. }
  285. }
  286.  
  287. function deepDanbooru(src) {
  288. Swal.fire({
  289. icon: "error",
  290. title: "메타데이터 없음!",
  291. text: "Deep Danbooru로 찾아볼까요?",
  292. footer: `<a href="${src}" target="_blank">원본 링크</a>`,
  293. showCancelButton: true,
  294. confirmButtonText: "네",
  295. cancelButtonText: "아니오",
  296. showLoaderOnConfirm: true,
  297. backdrop: true,
  298. preConfirm: async () => {
  299. return GM_fetch(`https://deepdanbooru.donmai.us/?url=${src}&min_score=0.4`)
  300. .then((res) => {
  301. if (!res.status === 200) {
  302. Swal.showValidationMessage(`https://deepdanbooru.donmai.us 접속되는지 확인!`);
  303. }
  304. return res.json();
  305. })
  306. .catch((error) => {
  307. console.log(error);
  308. Swal.showValidationMessage(`https://deepdanbooru.donmai.us 접속되는지 확인!`);
  309. });
  310. },
  311. allowOutsideClick: () => !Swal.isLoading(),
  312. }).then((result) => {
  313. if (result.isConfirmed) {
  314. const tags = result.value.map((el) => el[0]).join(", ");
  315.  
  316. Swal.fire({
  317. confirmButtonText: "닫기",
  318. html: `
  319. <style>
  320. #tags {
  321. width: 100%;
  322. height: 250px;
  323. padding: 10px;
  324. background: #FFF;
  325. color: black;
  326. border-radius: 8px;
  327. font-size: 15px;
  328. resize:none;
  329. }
  330. #tags::-webkit-scrollbar{
  331. display:none;
  332. }
  333. .copy_btn {
  334. cursor: pointer;
  335. }
  336. </style>
  337. <textarea id="tags">${tags}</textarea>
  338. <span class="copy_btn" data-clipboard-target="#tags">여기 눌려 전체 복사</span>
  339. `,
  340. });
  341. }
  342. });
  343. }
  344.  
  345. document.arrive('a[href$="type=orig"] > img', function () {
  346. if (this.classList.contains("channel-icon")) return;
  347. this.parentNode.removeAttribute("href");
  348.  
  349. this.onclick = async () => {
  350. const src = `${this.src}?type=orig`;
  351.  
  352. if (src.includes(".png") || src.includes(".jpg") || src.includes(".jpeg")) {
  353. Swal.fire({
  354. title: "로드 중!",
  355. width: "15rem",
  356. didOpen: () => {
  357. Swal.showLoading();
  358. },
  359. });
  360.  
  361. const res = await GM_fetch(src);
  362.  
  363. const buffer = await res.arrayBuffer();
  364.  
  365. try {
  366. const png = await UPNG.decode(buffer);
  367. console.log(png);
  368. new ClipboardJS(".copy_btn");
  369.  
  370. if (
  371. png.tabs?.tEXt?.Description ||
  372. png.tabs?.tEXt?.parameters ||
  373. png.tabs?.iTXt?.parameters ||
  374. png.tabs?.iTXt?.Description
  375. ) {
  376. analyze(png, src);
  377. } else {
  378. deepDanbooru(src);
  379. }
  380. } catch (error) {
  381. deepDanbooru(src);
  382. }
  383. } else {
  384. Swal.fire({
  385. toast: true,
  386. position: "top-end",
  387. showConfirmButton: false,
  388. timer: 1000,
  389. timerProgressBar: true,
  390. icon: "error",
  391. title: "지원되지 않는 형식의 이미지",
  392. });
  393. }
  394. };
  395. });
  396. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址