Stable Diffusion image metadata viewer

Show Stable Diffusion generated image's metadata

当前为 2023-02-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Stable Diffusion image metadata viewer
  3. // @namespace https://github.com/himuro-majika
  4. // @version 0.1.2
  5. // @description Show Stable Diffusion generated image's metadata
  6. // @author himuro_majika
  7. // @match http://*/*.png
  8. // @match http://*/*.jpg
  9. // @match http://*/*.jpeg
  10. // @match https://*/*.png
  11. // @match https://*/*.jpg
  12. // @match https://*/*.jpeg
  13. // @match file:///*.png
  14. // @match file:///*.jpg
  15. // @match file:///*.jpeg
  16. // @require https://cdn.jsdelivr.net/npm/exif-js@2.3.0/exif.min.js
  17. // @license MIT
  18. // @grant none
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. const img = document.images[0];
  25.  
  26. getImgData(img);
  27.  
  28. async function getImgData(img) {
  29. const imgtext = (await(await fetch(img.src)).text());
  30. // console.log(imgtext);
  31. const text = imgtext.match(/(?<=Xt(?:parameters|Description|Comment|Dream)\0*)([^\0]+)/ug);
  32. if (!text) {
  33. getExif(img);
  34. return;
  35. }
  36. // console.log(text);
  37. makeButton();
  38. makeData(text.join(""));
  39. }
  40.  
  41. function getExif(img) {
  42. EXIF.getData(img, function() {
  43. const comment = EXIF.getTag(this, "UserComment");
  44. console.log("comment");
  45. if (!comment) return;
  46.  
  47. const decode = decodeUnicode(comment);
  48. if (!decode) return;
  49.  
  50. makeButton();
  51. makeData(decode);
  52. });
  53. }
  54.  
  55. function decodeUnicode(array) {
  56. const plain = array.map(t => t.toString(16).padStart(2, "0")).join("");
  57. if (!plain.match(/^554e49434f44450/)) {
  58. // console.log(array);
  59. return;
  60. }
  61. const hex = plain.replace(/^554e49434f44450[0-9]/, "").replace(/[0-9a-f]{4}/g, ",0x$&").replace(/^,/, "");
  62. const arhex = hex.split(",");
  63. let decode = "";
  64. arhex.forEach(v => {
  65. decode += String.fromCodePoint(v);
  66. })
  67. return decode;
  68. }
  69.  
  70. function makeButton() {
  71. const button = document.createElement("button");
  72. button.innerHTML = "Show SD metadata";
  73. button.addEventListener("click", showModal);
  74. document.body.insertBefore(button, img);
  75. }
  76.  
  77. function makeData(text) {
  78. const positive = extractPositivePrompt(text);
  79. const negative = extractNegativePrompt(text);
  80. const others = extractOthers(text);
  81. const container = document.createElement("div");
  82. container.id ="_gm_sipv_container";
  83. container.style.display = "none";
  84. container.style.width = "100%";
  85. const copybutton = location.protocol == "http:" ? "" : `<button class="_gm_sipv_copybutton" type="button" style="cursor: pointer; opacity: 0.5;">copy</button>`;
  86. container.innerHTML = `
  87. <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;">
  88. <div style="display:flex; justify-content: space-between; padding: 0px 10px;">
  89. <h5>Stable Diffusion image metadata</h5>
  90. <button id="_gm_sipv_closebutton" type="button" style="cursor: pointer; height: 4em; opacity: 0.5; padding: 1em; background: #0000; border: 0; width: 3em;">❎</button>
  91. </div>
  92. <div style="padding: 10px;">
  93. <div>
  94. <div style="display:flex; justify-content: space-between;">
  95. <label>Positive Prompt</label>
  96. ${copybutton}
  97. </div>
  98. <textarea rows="6" style="display: block; width: 774px; max-width: 100%; background: #aaaa; border: 0px none; margin: 10px 0;">${positive}</textarea>
  99. </div>
  100. <div>
  101. <div style="display:flex; justify-content: space-between;">
  102. <label>Negative Prompt</label>
  103. ${copybutton}
  104. </div>
  105. <textarea rows="6" style="display: block; width: 774px; max-width: 100%; background: #aaaa; border: 0px none; margin: 10px 0;">${negative}</textarea>
  106. </div>
  107. <div>
  108. <div style="display:flex; justify-content: space-between;">
  109. <label>Other info</label>
  110. ${copybutton}
  111. </div>
  112. <textarea rows="3" style="display: block; width: 774px; max-width: 100%;background: #aaaa; border: 0px none; margin: 10px 0;">${others}</textarea>
  113. </div>
  114. </div>
  115. </div>`;
  116. document.body.insertBefore(container, img);
  117. document.getElementById("_gm_sipv_closebutton").addEventListener("click", closeModal);
  118. document.querySelectorAll("._gm_sipv_copybutton").forEach(item => {
  119. item.addEventListener("click", copyText);
  120. });
  121. }
  122.  
  123. function extractPositivePrompt(text) {
  124. try {
  125. let matchtext =
  126. text.match(/([^]+)Negative prompt: /) ||
  127. text.match(/([^]+)Steps: /) ||
  128. text.match(/([^]+){"steps"/) ||
  129. text.match(/([^]+)\[[^[]+\]/);
  130. return matchtext[1];
  131. } catch (e) {
  132. // console.log(text);
  133. return "";
  134. }
  135. }
  136.  
  137. function extractNegativePrompt(text) {
  138. try {
  139. let matchtext =
  140. text.match(/Negative prompt: ([^]+)Steps: /) ||
  141. text.match(/"uc": "([^]+)"}/) ||
  142. text.match(/\[([^[]+)\]/);
  143. return matchtext[1];
  144. } catch (e) {
  145. // console.log(text);
  146. return "";
  147. }
  148. }
  149.  
  150. function extractOthers(text) {
  151. try {
  152. let matchtext =
  153. text.match(/(Steps: [^]+)/) ||
  154. text.match(/{("steps"[^]+)"uc": /) ||
  155. text.match(/\]([^]+)/);
  156. return matchtext[1];
  157. } catch (e) {
  158. // console.log(text);
  159. return "";
  160. }
  161. }
  162.  
  163. function showModal() {
  164. document.getElementById("_gm_sipv_container").style.display = "block";
  165. }
  166.  
  167. function closeModal() {
  168. document.getElementById("_gm_sipv_container").style.display = "none";
  169. }
  170.  
  171. function copyText() {
  172. const value = this.parentNode.parentNode.querySelector("textarea").value;
  173. navigator.clipboard.writeText(value);
  174. }
  175.  
  176. })();