AI 이미지 EXIF 뷰어

AI 이미지 메타데이터 보기

目前为 2022-10-15 提交的版本。查看 最新版本

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

QingJ © 2025

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