Stable Diffusion Metadata Discord Image Overlay

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

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

// ==UserScript==
// @name         Stable Diffusion Metadata Discord Image Overlay
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Displays Stable Diffusion metadata generated as an overlay on Discord images.
// @author       moony
// @icon         https://iconarchive.com/icons/ccard3dev/dynamic-yosemite/256/Preview-icon.png
// @match        https://discord.com/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/exif-reader.min.js
// @license      MIT
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    function readExif(url) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                responseType: "arraybuffer",
                onload: (response) => {
                    const fileBuffer = response.response;
                    const tags = ExifReader.load(fileBuffer, { expanded: true });
                    const prompt = getPromptFromTags(tags);
                    resolve(prompt);
                },
                onerror: (error) => {
                    reject(error);
                }
            });
        });
    }

    function getPromptFromTags(tags) {
        let com = "";

        if (tags.exif && tags.exif.UserComment) {
            com = decodeUnicode(tags.exif.UserComment.value);
            return com;
        }

        if (!tags.pngText) return "";

        if (tags.pngText.parameters) {
            com = tags.pngText.parameters.description;
            return com;
        }

        if (tags.pngText.Dream) {
            com = tags.pngText.Dream.description;
            com += tags.pngText["sd-metadata"] ? "\r\n" + tags.pngText["sd-metadata"].description : "";
            return com;
        }

        if (tags.pngText.Software && tags.pngText.Software.description == "NovelAI") {
            const positive = tags.pngText.Description.description;
            const negative = tags.pngText.Comment.description.replaceAll(/\\u00a0/g, " ").match(/"uc": "([^]+)"[,}]/)[1];
            let others = tags.pngText.Comment.description.replaceAll(/\\u00a0/g, " ") + "\r\n";
            others += tags.pngText.Software.description + "\r\n";
            others += tags.pngText.Title.description + "\r\n";
            others += tags.pngText.Source.description;
            const prompt = {
                positive: positive,
                negative: negative,
                others: others
            };
            return JSON.stringify(prompt);
        }

        Object.keys(tags.pngText).forEach(tag => {
            com += tags.pngText[tag].description;
        });

        return com;
    }

    function decodeUnicode(array) {
        const plain = array.map(t => t.toString(16).padStart(2, "0")).join("");
        if (!plain.match(/^554e49434f44450/)) {
            return "";
        }
        const hex = plain.replace(/^554e49434f44450[0-9]/, "").replace(/[0-9a-f]{4}/g, ",0x$&").replace(/^,/, "");
        const arhex = hex.split(",");
        let decode = "";
        arhex.forEach(v => {
            decode += String.fromCodePoint(v);
        });
        return decode;
    }



    // Main Function to retrieve prompt from URL
    async function getExif(url) {
        try {
            const result = await readExif(url);
            return result;
        } catch (error) {
            console.error(error);
            return '';
        }
    }

    // Function to process the URL and return the shortened version
    async function processUrl(url, maxWidth) {
        const cleanUrl = url.split('?')[0]; const text = await getExif(cleanUrl); let truncatedText = ''; let words = text.split(' '); // console.log(text);
        for (let word of words) {
            if ((truncatedText + word).length > maxWidth) {
                truncatedText += '\n';
            }
            truncatedText += word + ' ';
        }
        return truncatedText.trim();
    }

    // Function to add text overlay with URL to a given image
    async function addTextOverlayWithUrl(img) {
        // Create a new div to hold the image and the text
        let wrapper = document.createElement('div');
        wrapper.style.position = 'relative';
        wrapper.style.display = 'inline-block';
        // Create a new div for the text overlay
        let textOverlay = document.createElement('div');
        textOverlay.className = 'text-overlay';
        // Create a span element for the URL text
        let urlText = document.createElement('span');
        urlText.textContent = await processUrl(img.src, img.clientWidth); // Wait for the promise to resolve
        // Insert the new elements
        img.parentNode.insertBefore(wrapper, img);
        wrapper.appendChild(textOverlay);
        wrapper.appendChild(img);
        textOverlay.appendChild(urlText);
    }

    // Function to check for new images and add text overlay with URL
    async function checkForNewImages() {
        let images = document.querySelectorAll('.imageWrapper-oMkQl4 img');
        // Loop through each image
        for (let img of images) {
            // Check if the image already has a text overlay
            if (!img.parentNode.classList.contains('textOverlayAdded')) {
                // Add text overlay with URL to new image
                await addTextOverlayWithUrl(img); // Wait for the promise to resolve
                img.parentNode.classList.add('textOverlayAdded');
            }
        }
    }

    // Wait 5 sec for page loads
    setTimeout(() => setInterval(checkForNewImages, 2000), 5000);

    GM_addStyle(`
        .text-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            padding: 5px;
            color: white;
            font-family: sans-serif;
            font-size: 12px;
            word-wrap: break-word;
            white-space: pre-wrap;
        }

        .text-overlay span {
            color: white;
            text-decoration: none;
        }
    `);
})();