Stable Diffusion Metadata Discord Image Overlay

Displays Stable Diffusion metadata generated as an overlay on Discord images.

  1. // ==UserScript==
  2. // @name Stable Diffusion Metadata Discord Image Overlay
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description Displays Stable Diffusion metadata generated as an overlay on Discord images.
  6. // @author moony
  7. // @icon https://iconarchive.com/icons/ccard3dev/dynamic-yosemite/256/Preview-icon.png
  8. // @match https://discord.com/*
  9. // @require https://cdn.jsdelivr.net/npm/exifreader@4.12.0/dist/exif-reader.min.js
  10. // @license MIT
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM_addStyle
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. function readExif(url) {
  19. return new Promise((resolve, reject) => {
  20. GM_xmlhttpRequest({
  21. method: "GET",
  22. url: url,
  23. responseType: "arraybuffer",
  24. onload: (response) => resolve(getPromptFromTags(ExifReader.load(response.response, { expanded: true }))),
  25. onerror: reject
  26. });
  27. });
  28. }
  29.  
  30. function getPromptFromTags(tags) {
  31. let com = "";
  32. if (tags.exif && tags.exif.UserComment) return decodeUnicode(tags.exif.UserComment.value);
  33. if (!tags.pngText) return "";
  34. if (tags.pngText.parameters) return tags.pngText.parameters.description;
  35. if (tags.pngText.Dream) return tags.pngText.Dream.description + (tags.pngText["sd-metadata"] ? "\r\n" + tags.pngText["sd-metadata"].description : "");
  36. if (tags.pngText.Software && tags.pngText.Software.description == "NovelAI") {
  37. const positive = tags.pngText.Description.description;
  38. const negative = tags.pngText.Comment.description.replaceAll(/\\u00a0/g, " ").match(/"uc": "([^]+)"[,}]/)[1];
  39. let others = tags.pngText.Comment.description.replaceAll(/\\u00a0/g, " ") + "\r\n";
  40. others += tags.pngText.Software.description + "\r\n";
  41. others += tags.pngText.Title.description + "\r\n";
  42. others += tags.pngText.Source.description;
  43. return JSON.stringify({positive, negative, others});
  44. }
  45. Object.keys(tags.pngText).forEach(tag => com += tags.pngText[tag].description);
  46. return com;
  47. }
  48.  
  49. function decodeUnicode(array) {
  50. const plain = array.map(t => t.toString(16).padStart(2, "0")).join("");
  51. if (!plain.match(/^554e49434f44450/)) return "";
  52. const hex = plain.replace(/^554e49434f44450[0-9]/, "").replace(/[0-9a-f]{4}/g, ",0x$&").replace(/^,/, "");
  53. return hex.split(",").map(v => String.fromCodePoint(v)).join("");
  54. }
  55.  
  56. async function getExif(url) {
  57. try {
  58. return await readExif(url);
  59. } catch (error) {
  60. console.error(error);
  61. return '';
  62. }
  63. }
  64.  
  65. async function processUrl(url, maxWidth) {
  66. const cleanUrl = url.split('?')[0];
  67. const text = await getExif(cleanUrl);
  68. let truncatedText = '';
  69. let words = text.split(' ');
  70. for (let word of words) {
  71. if ((truncatedText + word).length > maxWidth) truncatedText += '\n';
  72. truncatedText += word + ' ';
  73. }
  74. return truncatedText.trim();
  75. }
  76.  
  77. async function addTextOverlayWithUrl(imageElement) {
  78. let img = new Image();
  79. img.src = imageElement.getAttribute('data-safe-src');
  80. let wrapper = document.createElement('div');
  81. wrapper.style.position = 'relative';
  82. wrapper.style.display = 'inline-block';
  83. let textOverlay = document.createElement('div');
  84. textOverlay.className = 'text-overlay';
  85. let urlText = document.createElement('span');
  86. urlText.textContent = await processUrl(img.src, img.clientWidth);
  87. imageElement.appendChild(img);
  88. imageElement.insertBefore(wrapper, img);
  89. wrapper.appendChild(textOverlay);
  90. wrapper.appendChild(img);
  91. textOverlay.appendChild(urlText);
  92. }
  93.  
  94. async function checkForNewImages() {
  95. let imageElements = document.querySelectorAll('a[data-safe-src]');
  96. for (let imageElement of imageElements) {
  97. if (!imageElement.classList.contains('textOverlayAdded')) {
  98. await addTextOverlayWithUrl(imageElement);
  99. imageElement.classList.add('textOverlayAdded');
  100. }
  101. }
  102. }
  103.  
  104. setTimeout(() => setInterval(checkForNewImages, 2000), 5000);
  105.  
  106. GM_addStyle(`
  107. .text-overlay {
  108. position: absolute;
  109. top: 0;
  110. left: 0;
  111. width: 100%;
  112. background-color: rgba(0, 0, 0, 0.5);
  113. padding: 5px;
  114. color: white;
  115. font-family: sans-serif;
  116. font-size: 12px;
  117. word-wrap: break-word;
  118. white-space: pre-wrap;
  119. }
  120. .text-overlay span {
  121. color: white;
  122. text-decoration: none;
  123. }
  124. img {
  125. object-fit: contain;
  126. width: 100%;
  127. height: 100%;
  128. }
  129. `);
  130. })();