Huggingface Image Downloader

Add buttons to quickly download images from Stable Diffusion

Verze ze dne 24. 02. 2023. Zobrazit nejnovější verzi.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name           Huggingface Image Downloader
// @description    Add buttons to quickly download images from Stable Diffusion
// @author         Isaiah Odhner
// @namespace      https://isaiahodhner.io
// @version        1.2
// @license        MIT
// @match          https://*.hf.space/*
// @icon           https://www.google.com/s2/favicons?sz=64&domain=huggingface.co
// @grant          none
// ==/UserScript==

// v1.1 adds support for Stable Diffusion 2. It expands the domain scope to match other applications on HuggingFace, and includes negative prompts in the filename, with format: "'<positive>' (anti '<negative>')".
// v1.2 handles updated DOM structure of the site

let foundGrid = false;

setInterval(() => {
    // assuming prompt comes before negative prompt in DOM
	const input = document.querySelector('#prompt-text-input input, [name=prompt], [placeholder*="prompt"]');
    const negativeInput = document.querySelector('#negative-prompt-text-input input, [name=negative-prompt], [placeholder*="negative prompt"]');

	const dlButtons = [];
	for (const img of document.querySelectorAll(".grid img, #gallery img, .grid-container img, .thumbnail-item img")) {
		const existingA = img.parentElement.querySelector("a");
		if (existingA) {
			if (existingA._imgSrc !== img.src) {
				existingA.remove();
				const index = dlButtons.indexOf(existingA);
				if (index > -1) {
					dlButtons.splice(index);
				}
			} else {
				continue; // don't add a duplicate <a> or change the supposed prompt it was generated with
			}
		}

		const a = document.createElement("a");
		a.style.position = "absolute";
		a.style.opacity = "0";
		a.style.top = "0";
		a.style.left = "0";
		a.style.background = "black";
		a.style.color = "white";
		a.style.borderRadius = "5px";
		a.style.padding = "5px";
		a.style.margin = "5px";
		a.style.fontSize = "50px";
		a.style.lineHeight = "50px";
		a.textContent = "Download";
		a._imgSrc = img.src;

		let filename = `'${sanitizeFilename(input.value)}'`;
		if (negativeInput) {
			filename += ` (anti '${sanitizeFilename(negativeInput.value)}')`;
		}
		filename += ".jpeg";
		a.download = filename;

		a.href = img.src;
		img.parentElement.append(a);
		dlButtons.push(a);

		// Can't be delegated because it needs to stop the click event from bubbling up to the handler that zooms in
		a.addEventListener("click", (event) => {
			// Prevent also zooming into the image when clicking Download
			event.stopImmediatePropagation();
		});
	}
	const grid = document.querySelector(".grid, #gallery");
	if (grid && !foundGrid) {
		foundGrid = true;
		grid.addEventListener("mouseover", (event) => {
			for (const a of dlButtons) {
				a.style.opacity = "0";
			}
			const cell = event.target.closest(".gallery-item, .thumbnail-item");
			if (cell) {
				cell.querySelector("a[download]").style.opacity = "1";
			}
		});
		grid.addEventListener("mouseout", (event) => {
			if (event.target instanceof HTMLImageElement) {
				const cell = event.target.closest(".gallery-item, .thumbnail-item");
				const newCell = event.relatedTarget.closest(".gallery-item, .thumbnail-item");
				if (cell === newCell) return;
				cell.querySelector("a[download]").style.opacity = "0";
			}
		});
		document.addEventListener("mouseleave", (event) => {
			for (const a of document.querySelectorAll(".gallery-item a[download], .thumbnail-item a[download]")) {
				a.style.opacity = "0";
			}
		});
	}
}, 300);

function sanitizeFilename(str) {
	// Sanitize for file name, replacing symbols rather than removing them
	str = str.replace(/\//g, "⧸");
	str = str.replace(/\\/g, "⧹");
	str = str.replace(/</g, "ᐸ");
	str = str.replace(/>/g, "ᐳ");
	str = str.replace(/:/g, "꞉");
	str = str.replace(/\|/g, "∣");
	str = str.replace(/\?/g, "?");
	str = str.replace(/\*/g, "∗");
	str = str.replace(/(^|[-—\s(\["])'/g, "$1\u2018");  // opening singles
	str = str.replace(/'/g, "\u2019");                  // closing singles & apostrophes
	str = str.replace(/(^|[-—/\[(‘\s])"/g, "$1\u201c"); // opening doubles
	str = str.replace(/"/g, "\u201d");                  // closing doubles
	str = str.replace(/--/g, "\u2014");                 // em-dashes
	str = str.replace(/\.\.\./g, "…");                  // ellipses
	str = str.replace(/~/g, "\u301C");                  // Chrome at least doesn't like tildes
	str = str.trim();
	return str;
}