CoolFace
Apppublic

brunvelop/ComfyUI

sourceHugging Faceupdated 3y agoView on Hugging Face
2likes
pnginfo.js424 linesDownload Raw Back to scripts
1import { api } from "./api.js";2 3export function getPngMetadata(file) {4	return new Promise((r) => {5		const reader = new FileReader();6		reader.onload = (event) => {7			// Get the PNG data as a Uint8Array8			const pngData = new Uint8Array(event.target.result);9			const dataView = new DataView(pngData.buffer);10 11			// Check that the PNG signature is present12			if (dataView.getUint32(0) !== 0x89504e47) {13				console.error("Not a valid PNG file");14				r();15				return;16			}17 18			// Start searching for chunks after the PNG signature19			let offset = 8;20			let txt_chunks = {};21			// Loop through the chunks in the PNG file22			while (offset < pngData.length) {23				// Get the length of the chunk24				const length = dataView.getUint32(offset);25				// Get the chunk type26				const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8));27				if (type === "tEXt") {28					// Get the keyword29					let keyword_end = offset + 8;30					while (pngData[keyword_end] !== 0) {31						keyword_end++;32					}33					const keyword = String.fromCharCode(...pngData.slice(offset + 8, keyword_end));34					// Get the text35					const contentArraySegment = pngData.slice(keyword_end + 1, offset + 8 + length);36					const contentJson = Array.from(contentArraySegment).map(s=>String.fromCharCode(s)).join('')37					txt_chunks[keyword] = contentJson;38				}39 40				offset += 12 + length;41			}42 43			r(txt_chunks);44		};45 46		reader.readAsArrayBuffer(file);47	});48}49 50function parseExifData(exifData) {51	// Check for the correct TIFF header (0x4949 for little-endian or 0x4D4D for big-endian)52	const isLittleEndian = new Uint16Array(exifData.slice(0, 2))[0] === 0x4949;53	console.log(exifData);54 55	// Function to read 16-bit and 32-bit integers from binary data56	function readInt(offset, isLittleEndian, length) {57		let arr = exifData.slice(offset, offset + length)58		if (length === 2) {59			return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint16(0, isLittleEndian);60		} else if (length === 4) {61			return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint32(0, isLittleEndian);62		}63	}64 65	// Read the offset to the first IFD (Image File Directory)66	const ifdOffset = readInt(4, isLittleEndian, 4);67 68	function parseIFD(offset) {69		const numEntries = readInt(offset, isLittleEndian, 2);70		const result = {};71 72		for (let i = 0; i < numEntries; i++) {73			const entryOffset = offset + 2 + i * 12;74			const tag = readInt(entryOffset, isLittleEndian, 2);75			const type = readInt(entryOffset + 2, isLittleEndian, 2);76			const numValues = readInt(entryOffset + 4, isLittleEndian, 4);77			const valueOffset = readInt(entryOffset + 8, isLittleEndian, 4);78 79			// Read the value(s) based on the data type80			let value;81			if (type === 2) {82				// ASCII string83				value = String.fromCharCode(...exifData.slice(valueOffset, valueOffset + numValues - 1));84			}85 86			result[tag] = value;87		}88 89		return result;90	}91 92	// Parse the first IFD93	const ifdData = parseIFD(ifdOffset);94	return ifdData;95}96 97function splitValues(input) {98    var output = {};99    for (var key in input) {100		var value = input[key];101		var splitValues = value.split(':', 2);102		output[splitValues[0]] = splitValues[1];103    }104    return output;105}106 107export function getWebpMetadata(file) {108	return new Promise((r) => {109		const reader = new FileReader();110		reader.onload = (event) => {111			const webp = new Uint8Array(event.target.result);112			const dataView = new DataView(webp.buffer);113 114			// Check that the WEBP signature is present115			if (dataView.getUint32(0) !== 0x52494646 || dataView.getUint32(8) !== 0x57454250) {116				console.error("Not a valid WEBP file");117				r();118				return;119			}120 121			// Start searching for chunks after the WEBP signature122			let offset = 12;123			let txt_chunks = {};124			// Loop through the chunks in the WEBP file125			while (offset < webp.length) {126				const chunk_length = dataView.getUint32(offset + 4, true);127				const chunk_type = String.fromCharCode(...webp.slice(offset, offset + 4));128				if (chunk_type === "EXIF") {129					let data = parseExifData(webp.slice(offset + 8, offset + 8 + chunk_length));130					for (var key in data) {131						var value = data[key];132						let index = value.indexOf(':');133						txt_chunks[value.slice(0, index)] = value.slice(index + 1);134					}135				}136 137				offset += 8 + chunk_length;138			}139 140			r(txt_chunks);141		};142 143		reader.readAsArrayBuffer(file);144	});145}146 147export function getLatentMetadata(file) {148	return new Promise((r) => {149		const reader = new FileReader();150		reader.onload = (event) => {151			const safetensorsData = new Uint8Array(event.target.result);152			const dataView = new DataView(safetensorsData.buffer);153			let header_size = dataView.getUint32(0, true);154			let offset = 8;155			let header = JSON.parse(new TextDecoder().decode(safetensorsData.slice(offset, offset + header_size)));156			r(header.__metadata__);157		};158 159		var slice = file.slice(0, 1024 * 1024 * 4);160		reader.readAsArrayBuffer(slice);161	});162}163 164export async function importA1111(graph, parameters) {165	const p = parameters.lastIndexOf("\nSteps:");166	if (p > -1) {167		const embeddings = await api.getEmbeddings();168		const opts = parameters169			.substr(p)170			.split("\n")[1]171			.split(",")172			.reduce((p, n) => {173				const s = n.split(":");174				p[s[0].trim().toLowerCase()] = s[1].trim();175				return p;176			}, {});177		const p2 = parameters.lastIndexOf("\nNegative prompt:", p);178		if (p2 > -1) {179			let positive = parameters.substr(0, p2).trim();180			let negative = parameters.substring(p2 + 18, p).trim();181 182			const ckptNode = LiteGraph.createNode("CheckpointLoaderSimple");183			const clipSkipNode = LiteGraph.createNode("CLIPSetLastLayer");184			const positiveNode = LiteGraph.createNode("CLIPTextEncode");185			const negativeNode = LiteGraph.createNode("CLIPTextEncode");186			const samplerNode = LiteGraph.createNode("KSampler");187			const imageNode = LiteGraph.createNode("EmptyLatentImage");188			const vaeNode = LiteGraph.createNode("VAEDecode");189			const vaeLoaderNode = LiteGraph.createNode("VAELoader");190			const saveNode = LiteGraph.createNode("SaveImage");191			let hrSamplerNode = null;192 193			const ceil64 = (v) => Math.ceil(v / 64) * 64;194 195			function getWidget(node, name) {196				return node.widgets.find((w) => w.name === name);197			}198 199			function setWidgetValue(node, name, value, isOptionPrefix) {200				const w = getWidget(node, name);201				if (isOptionPrefix) {202					const o = w.options.values.find((w) => w.startsWith(value));203					if (o) {204						w.value = o;205					} else {206						console.warn(`Unknown value '${value}' for widget '${name}'`, node);207						w.value = value;208					}209				} else {210					w.value = value;211				}212			}213 214			function createLoraNodes(clipNode, text, prevClip, prevModel) {215				const loras = [];216				text = text.replace(/<lora:([^:]+:[^>]+)>/g, function (m, c) {217					const s = c.split(":");218					const weight = parseFloat(s[1]);219					if (isNaN(weight)) {220						console.warn("Invalid LORA", m);221					} else {222						loras.push({ name: s[0], weight });223					}224					return "";225				});226 227				for (const l of loras) {228					const loraNode = LiteGraph.createNode("LoraLoader");229					graph.add(loraNode);230					setWidgetValue(loraNode, "lora_name", l.name, true);231					setWidgetValue(loraNode, "strength_model", l.weight);232					setWidgetValue(loraNode, "strength_clip", l.weight);233					prevModel.node.connect(prevModel.index, loraNode, 0);234					prevClip.node.connect(prevClip.index, loraNode, 1);235					prevModel = { node: loraNode, index: 0 };236					prevClip = { node: loraNode, index: 1 };237				}238 239				prevClip.node.connect(1, clipNode, 0);240				prevModel.node.connect(0, samplerNode, 0);241				if (hrSamplerNode) {242					prevModel.node.connect(0, hrSamplerNode, 0);243				}244 245				return { text, prevModel, prevClip };246			}247 248			function replaceEmbeddings(text) {249				if(!embeddings.length) return text;250				return text.replaceAll(251					new RegExp(252						"\\b(" + embeddings.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\b|\\b") + ")\\b",253						"ig"254					),255					"embedding:$1"256				);257			}258 259			function popOpt(name) {260				const v = opts[name];261				delete opts[name];262				return v;263			}264 265			graph.clear();266			graph.add(ckptNode);267			graph.add(clipSkipNode);268			graph.add(positiveNode);269			graph.add(negativeNode);270			graph.add(samplerNode);271			graph.add(imageNode);272			graph.add(vaeNode);273			graph.add(vaeLoaderNode);274			graph.add(saveNode);275 276			ckptNode.connect(1, clipSkipNode, 0);277			clipSkipNode.connect(0, positiveNode, 0);278			clipSkipNode.connect(0, negativeNode, 0);279			ckptNode.connect(0, samplerNode, 0);280			positiveNode.connect(0, samplerNode, 1);281			negativeNode.connect(0, samplerNode, 2);282			imageNode.connect(0, samplerNode, 3);283			vaeNode.connect(0, saveNode, 0);284			samplerNode.connect(0, vaeNode, 0);285			vaeLoaderNode.connect(0, vaeNode, 1);286 287			const handlers = {288				model(v) {289					setWidgetValue(ckptNode, "ckpt_name", v, true);290				},291				"cfg scale"(v) {292					setWidgetValue(samplerNode, "cfg", +v);293				},294				"clip skip"(v) {295					setWidgetValue(clipSkipNode, "stop_at_clip_layer", -v);296				},297				sampler(v) {298					let name = v.toLowerCase().replace("++", "pp").replaceAll(" ", "_");299					if (name.includes("karras")) {300						name = name.replace("karras", "").replace(/_+$/, "");301						setWidgetValue(samplerNode, "scheduler", "karras");302					} else {303						setWidgetValue(samplerNode, "scheduler", "normal");304					}305					const w = getWidget(samplerNode, "sampler_name");306					const o = w.options.values.find((w) => w === name || w === "sample_" + name);307					if (o) {308						setWidgetValue(samplerNode, "sampler_name", o);309					}310				},311				size(v) {312					const wxh = v.split("x");313					const w = ceil64(+wxh[0]);314					const h = ceil64(+wxh[1]);315					const hrUp = popOpt("hires upscale");316					const hrSz = popOpt("hires resize");317					let hrMethod = popOpt("hires upscaler");318 319					setWidgetValue(imageNode, "width", w);320					setWidgetValue(imageNode, "height", h);321 322					if (hrUp || hrSz) {323						let uw, uh;324						if (hrUp) {325							uw = w * hrUp;326							uh = h * hrUp;327						} else {328							const s = hrSz.split("x");329							uw = +s[0];330							uh = +s[1];331						}332 333						let upscaleNode;334						let latentNode;335 336						if (hrMethod.startsWith("Latent")) {337							latentNode = upscaleNode = LiteGraph.createNode("LatentUpscale");338							graph.add(upscaleNode);339							samplerNode.connect(0, upscaleNode, 0);340 341							switch (hrMethod) {342								case "Latent (nearest-exact)":343									hrMethod = "nearest-exact";344									break;345							}346							setWidgetValue(upscaleNode, "upscale_method", hrMethod, true);347						} else {348							const decode = LiteGraph.createNode("VAEDecodeTiled");349							graph.add(decode);350							samplerNode.connect(0, decode, 0);351							vaeLoaderNode.connect(0, decode, 1);352 353							const upscaleLoaderNode = LiteGraph.createNode("UpscaleModelLoader");354							graph.add(upscaleLoaderNode);355							setWidgetValue(upscaleLoaderNode, "model_name", hrMethod, true);356 357							const modelUpscaleNode = LiteGraph.createNode("ImageUpscaleWithModel");358							graph.add(modelUpscaleNode);359							decode.connect(0, modelUpscaleNode, 1);360							upscaleLoaderNode.connect(0, modelUpscaleNode, 0);361 362							upscaleNode = LiteGraph.createNode("ImageScale");363							graph.add(upscaleNode);364							modelUpscaleNode.connect(0, upscaleNode, 0);365 366							const vaeEncodeNode = (latentNode = LiteGraph.createNode("VAEEncodeTiled"));367							graph.add(vaeEncodeNode);368							upscaleNode.connect(0, vaeEncodeNode, 0);369							vaeLoaderNode.connect(0, vaeEncodeNode, 1);370						}371 372						setWidgetValue(upscaleNode, "width", ceil64(uw));373						setWidgetValue(upscaleNode, "height", ceil64(uh));374 375						hrSamplerNode = LiteGraph.createNode("KSampler");376						graph.add(hrSamplerNode);377						ckptNode.connect(0, hrSamplerNode, 0);378						positiveNode.connect(0, hrSamplerNode, 1);379						negativeNode.connect(0, hrSamplerNode, 2);380						latentNode.connect(0, hrSamplerNode, 3);381						hrSamplerNode.connect(0, vaeNode, 0);382					}383				},384				steps(v) {385					setWidgetValue(samplerNode, "steps", +v);386				},387				seed(v) {388					setWidgetValue(samplerNode, "seed", +v);389				},390			};391 392			for (const opt in opts) {393				if (opt in handlers) {394					handlers[opt](popOpt(opt));395				}396			}397 398			if (hrSamplerNode) {399				setWidgetValue(hrSamplerNode, "steps", getWidget(samplerNode, "steps").value);400				setWidgetValue(hrSamplerNode, "cfg", getWidget(samplerNode, "cfg").value);401				setWidgetValue(hrSamplerNode, "scheduler", getWidget(samplerNode, "scheduler").value);402				setWidgetValue(hrSamplerNode, "sampler_name", getWidget(samplerNode, "sampler_name").value);403				setWidgetValue(hrSamplerNode, "denoise", +(popOpt("denoising strength") || "1"));404			}405 406			let n = createLoraNodes(positiveNode, positive, { node: clipSkipNode, index: 0 }, { node: ckptNode, index: 0 });407			positive = n.text;408			n = createLoraNodes(negativeNode, negative, n.prevClip, n.prevModel);409			negative = n.text;410 411			setWidgetValue(positiveNode, "text", replaceEmbeddings(positive));412			setWidgetValue(negativeNode, "text", replaceEmbeddings(negative));413 414			graph.arrange();415 416			for (const opt of ["model hash", "ensd"]) {417				delete opts[opt];418			}419 420			console.warn("Unhandled parameters:", opts);421		}422	}423}424