brunvelop/ComfyUI
2
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 