Stable Diffusion image metadata viewer

显示 Stable Diffusion 生成的图像的元数据

当前为 2023-05-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Stable Diffusion image metadata viewer
  3. // @namespace https://github.com/himuro-majika
  4. // @version 0.2.3
  5. // @description Show Stable Diffusion generated image's metadata
  6. // @description:ja Stable Diffusionで生成された画像の埋め込みメタデータを表示します
  7. // @description:ko 표시 Stable Diffusion 생성된 이미지의 메타데이터
  8. // @description:de Metadaten des durch Stabile Diffusion erzeugten Bildes anzeigen
  9. // @description:es Mostrar los metadatos de la imagen generada por Stable Diffusion
  10. // @description:fr Afficher les métadonnées de l'image générée par la Stable Diffusion
  11. // @description:it Mostrare i metadati dell'immagine generata da Stable Diffusion
  12. // @description:zh-CN 显示 Stable Diffusion 生成的图像的元数据
  13. // @description:zh-SG 显示 Stable Diffusion 生成的图像的元数据
  14. // @description:zh-TW 顯示 Stable Diffusion 生成圖像的元數據
  15. // @description:zh-HK 顯示 Stable Diffusion 生成圖像的元數據
  16. // @author himuro_majika
  17. // @match http://*/*.png
  18. // @match http://*/*.jpg
  19. // @match http://*/*.jpeg
  20. // @match http://*/*.webp
  21. // @match https://*/*.png
  22. // @match https://*/*.jpg
  23. // @match https://*/*.jpeg
  24. // @match https://*/*.webp
  25. // @match file:///*.png
  26. // @match file:///*.jpg
  27. // @match file:///*.jpeg
  28. // @match file:///*.webp
  29. // @require https://cdn.jsdelivr.net/npm/exifreader@4.11.0/dist/exif-reader.min.js
  30. // @license MIT
  31. // @grant none
  32. // ==/UserScript==
  33.  
  34. (function() {
  35. 'use strict';
  36.  
  37. const img = document.images[0];
  38. if (!img) return;
  39. readExif(img);
  40.  
  41. async function readExif(img) {
  42. const fileBuffer = await fetch(img.src).then(response => response.arrayBuffer()).catch(() => {return;});
  43. if (!fileBuffer) return;
  44. const tags = ExifReader.load(fileBuffer, {expanded: true});
  45. getComment(tags);
  46. }
  47.  
  48. function getComment(tags) {
  49. // console.dir(JSON.parse(JSON.stringify(tags)));
  50.  
  51. let com = ""
  52.  
  53. // Exif
  54. if (tags.exif && tags.exif.UserComment) {
  55. com = decodeUnicode(tags.exif.UserComment.value);
  56. extractPrompt(com);
  57. return;
  58. }
  59. // iTXt
  60. if (!tags.pngText) return;
  61. // A1111
  62. if (tags.pngText.parameters) {
  63. com = tags.pngText.parameters.description;
  64. extractPrompt(com);
  65. return;
  66. }
  67. // NMKD
  68. if (tags.pngText.Dream) {
  69. com = tags.pngText.Dream.description;
  70. com += tags.pngText["sd-metadata"] ? "\r\n" + tags.pngText["sd-metadata"].description : "";
  71. extractPrompt(com);
  72. return;
  73. }
  74. // NAI
  75. if (tags.pngText.Software && tags.pngText.Software.description == "NovelAI") {
  76. const positive = tags.pngText.Description.description;
  77. const negative = tags.pngText.Comment.description.replaceAll(/\\u00a0/g, " ").match(/"uc": "([^]+)"[,}]/)[1];
  78. let others = tags.pngText.Comment.description.replaceAll(/\\u00a0/g, " ") + "\r\n";
  79. others += tags.pngText.Software.description + "\r\n";
  80. others += tags.pngText.Title.description + "\r\n";
  81. others += tags.pngText.Source.description;
  82. const prompt = {
  83. positive: positive,
  84. negative: negative,
  85. others: others
  86. }
  87. makeButton();
  88. makeData(prompt);
  89. return;
  90. }
  91.  
  92. Object.keys(tags.pngText).forEach(tag => {
  93. com += tags.pngText[tag].description;
  94. });
  95.  
  96. // console.log(com);
  97. extractPrompt(com);
  98. return;
  99. }
  100.  
  101. function decodeUnicode(array) {
  102. const plain = array.map(t => t.toString(16).padStart(2, "0")).join("");
  103. if (!plain.match(/^554e49434f44450/)) {
  104. // console.log(array);
  105. return;
  106. }
  107. const hex = plain.replace(/^554e49434f44450[0-9]/, "").replace(/[0-9a-f]{4}/g, ",0x$&").replace(/^,/, "");
  108. const arhex = hex.split(",");
  109. let decode = "";
  110. arhex.forEach(v => {
  111. decode += String.fromCodePoint(v);
  112. })
  113. return decode;
  114. }
  115.  
  116. function extractPrompt(com) {
  117. const positive = extractPositivePrompt(com);
  118. const negative = extractNegativePrompt(com);
  119. const others = extractOthers(com);
  120. if (!positive && !negative && !others) return;
  121. const prompt = {
  122. positive: positive,
  123. negative: negative,
  124. others: others
  125. }
  126. makeButton();
  127. makeData(prompt);
  128. }
  129.  
  130. function makeButton() {
  131. const button = document.createElement("button");
  132. button.innerHTML = "Show SD metadata";
  133. button.addEventListener("click", showModal);
  134. document.body.insertBefore(button, img);
  135. }
  136.  
  137. function makeData(prompt) {
  138. const positive = prompt.positive;
  139. const negative = prompt.negative;
  140. const others = prompt.others;
  141. const container = document.createElement("div");
  142. container.id ="_gm_sipv_container";
  143. container.style.display = "none";
  144. container.style.width = "100%";
  145. const copybutton = location.protocol == "http:" ? "" : `<button class="_gm_sipv_copybutton" type="button" style="cursor: pointer; opacity: 0.5;">copy</button>`;
  146. container.innerHTML = `
  147. <div style="color: #eee; width: 800px; max-width: 100%; margin-left: auto; margin-right: auto; z-index: 2; position: fixed; inset: auto 0; margin: auto; background: #000a; border-radius: 6px; box-shadow: #000 0px 0px 2px;">
  148. <div style="display:flex; justify-content: space-between; padding: 0px 10px;">
  149. <h5>Stable Diffusion image metadata</h5>
  150. <button id="_gm_sipv_closebutton" type="button" style="cursor: pointer; height: 4em; opacity: 0.5; padding: 1em; background: #0000; border: 0; width: 3em;">❎</button>
  151. </div>
  152. <div style="padding: 10px;">
  153. <div>
  154. <div style="display:flex; justify-content: space-between;">
  155. <label>Prompt</label>
  156. ${copybutton}
  157. </div>
  158. <textarea rows="6" style="display: block; width: 774px; max-width: 100%; background: #cccc; border: 0px none; margin: 10px 0;">${positive}</textarea>
  159. </div>
  160. <div>
  161. <div style="display:flex; justify-content: space-between;">
  162. <label>Negative Prompt</label>
  163. ${copybutton}
  164. </div>
  165. <textarea rows="6" style="display: block; width: 774px; max-width: 100%; background: #cccc; border: 0px none; margin: 10px 0;">${negative}</textarea>
  166. </div>
  167. <div>
  168. <div style="display:flex; justify-content: space-between;">
  169. <label>Other info</label>
  170. ${copybutton}
  171. </div>
  172. <textarea rows="3" style="display: block; width: 774px; max-width: 100%;background: #cccc; border: 0px none; margin: 10px 0;">${others}</textarea>
  173. </div>
  174. </div>
  175. </div>`;
  176. document.body.insertBefore(container, img);
  177. document.getElementById("_gm_sipv_closebutton").addEventListener("click", closeModal);
  178. document.querySelectorAll("._gm_sipv_copybutton").forEach(item => {
  179. item.addEventListener("click", copyText);
  180. });
  181. }
  182.  
  183. function extractPositivePrompt(text) {
  184. try {
  185. let matchtext =
  186. text.match(/([^]+)Negative prompt: /) ||
  187. text.match(/([^]+)Steps: /) ||
  188. text.match(/([^]+){"steps"/) ||
  189. text.match(/([^]+)\[[^[]+\]/);
  190. return matchtext[1];
  191. } catch (e) {
  192. console.log(text);
  193. return "";
  194. }
  195. }
  196.  
  197. function extractNegativePrompt(text) {
  198. try {
  199. let matchtext =
  200. text.match(/Negative prompt: ([^]+)Steps: /) ||
  201. text.match(/"uc": "([^]+)"}/) ||
  202. text.match(/\[([^[]+)\]/);
  203. return matchtext[1];
  204. } catch (e) {
  205. console.log(text);
  206. return "";
  207. }
  208. }
  209.  
  210. function extractOthers(text) {
  211. try {
  212. let matchtext =
  213. text.match(/(Steps: [^]+)/) ||
  214. text.match(/{("steps"[^]+)"uc": /) ||
  215. text.match(/\]([^]+)/);
  216. return matchtext[1];
  217. } catch (e) {
  218. console.log(text);
  219. return text;
  220. }
  221. }
  222.  
  223. function showModal() {
  224. document.getElementById("_gm_sipv_container").style.display = "block";
  225. }
  226.  
  227. function closeModal() {
  228. document.getElementById("_gm_sipv_container").style.display = "none";
  229. }
  230.  
  231. function copyText() {
  232. const value = this.parentNode.parentNode.querySelector("textarea").value;
  233. navigator.clipboard.writeText(value);
  234. }
  235.  
  236. })();