Spaces:
Running
on
Zero
Running
on
Zero
import { api } from "./api.js"; | |
export function getPngMetadata(file) { | |
return new Promise((r) => { | |
const reader = new FileReader(); | |
reader.onload = (event) => { | |
// Get the PNG data as a Uint8Array | |
const pngData = new Uint8Array(event.target.result); | |
const dataView = new DataView(pngData.buffer); | |
// Check that the PNG signature is present | |
if (dataView.getUint32(0) !== 0x89504e47) { | |
console.error("Not a valid PNG file"); | |
r(); | |
return; | |
} | |
// Start searching for chunks after the PNG signature | |
let offset = 8; | |
let txt_chunks = {}; | |
// Loop through the chunks in the PNG file | |
while (offset < pngData.length) { | |
// Get the length of the chunk | |
const length = dataView.getUint32(offset); | |
// Get the chunk type | |
const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8)); | |
if (type === "tEXt" || type == "comf" || type === "iTXt") { | |
// Get the keyword | |
let keyword_end = offset + 8; | |
while (pngData[keyword_end] !== 0) { | |
keyword_end++; | |
} | |
const keyword = String.fromCharCode(...pngData.slice(offset + 8, keyword_end)); | |
// Get the text | |
const contentArraySegment = pngData.slice(keyword_end + 1, offset + 8 + length); | |
const contentJson = new TextDecoder("utf-8").decode(contentArraySegment); | |
txt_chunks[keyword] = contentJson; | |
} | |
offset += 12 + length; | |
} | |
r(txt_chunks); | |
}; | |
reader.readAsArrayBuffer(file); | |
}); | |
} | |
function parseExifData(exifData) { | |
// Check for the correct TIFF header (0x4949 for little-endian or 0x4D4D for big-endian) | |
const isLittleEndian = String.fromCharCode(...exifData.slice(0, 2)) === "II"; | |
// Function to read 16-bit and 32-bit integers from binary data | |
function readInt(offset, isLittleEndian, length) { | |
let arr = exifData.slice(offset, offset + length) | |
if (length === 2) { | |
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint16(0, isLittleEndian); | |
} else if (length === 4) { | |
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint32(0, isLittleEndian); | |
} | |
} | |
// Read the offset to the first IFD (Image File Directory) | |
const ifdOffset = readInt(4, isLittleEndian, 4); | |
function parseIFD(offset) { | |
const numEntries = readInt(offset, isLittleEndian, 2); | |
const result = {}; | |
for (let i = 0; i < numEntries; i++) { | |
const entryOffset = offset + 2 + i * 12; | |
const tag = readInt(entryOffset, isLittleEndian, 2); | |
const type = readInt(entryOffset + 2, isLittleEndian, 2); | |
const numValues = readInt(entryOffset + 4, isLittleEndian, 4); | |
const valueOffset = readInt(entryOffset + 8, isLittleEndian, 4); | |
// Read the value(s) based on the data type | |
let value; | |
if (type === 2) { | |
// ASCII string | |
value = String.fromCharCode(...exifData.slice(valueOffset, valueOffset + numValues - 1)); | |
} | |
result[tag] = value; | |
} | |
return result; | |
} | |
// Parse the first IFD | |
const ifdData = parseIFD(ifdOffset); | |
return ifdData; | |
} | |
function splitValues(input) { | |
var output = {}; | |
for (var key in input) { | |
var value = input[key]; | |
var splitValues = value.split(':', 2); | |
output[splitValues[0]] = splitValues[1]; | |
} | |
return output; | |
} | |
export function getWebpMetadata(file) { | |
return new Promise((r) => { | |
const reader = new FileReader(); | |
reader.onload = (event) => { | |
const webp = new Uint8Array(event.target.result); | |
const dataView = new DataView(webp.buffer); | |
// Check that the WEBP signature is present | |
if (dataView.getUint32(0) !== 0x52494646 || dataView.getUint32(8) !== 0x57454250) { | |
console.error("Not a valid WEBP file"); | |
r(); | |
return; | |
} | |
// Start searching for chunks after the WEBP signature | |
let offset = 12; | |
let txt_chunks = {}; | |
// Loop through the chunks in the WEBP file | |
while (offset < webp.length) { | |
const chunk_length = dataView.getUint32(offset + 4, true); | |
const chunk_type = String.fromCharCode(...webp.slice(offset, offset + 4)); | |
if (chunk_type === "EXIF") { | |
if (String.fromCharCode(...webp.slice(offset + 8, offset + 8 + 6)) == "Exif\0\0") { | |
offset += 6; | |
} | |
let data = parseExifData(webp.slice(offset + 8, offset + 8 + chunk_length)); | |
for (var key in data) { | |
var value = data[key]; | |
let index = value.indexOf(':'); | |
txt_chunks[value.slice(0, index)] = value.slice(index + 1); | |
} | |
break; | |
} | |
offset += 8 + chunk_length; | |
} | |
r(txt_chunks); | |
}; | |
reader.readAsArrayBuffer(file); | |
}); | |
} | |
export function getLatentMetadata(file) { | |
return new Promise((r) => { | |
const reader = new FileReader(); | |
reader.onload = (event) => { | |
const safetensorsData = new Uint8Array(event.target.result); | |
const dataView = new DataView(safetensorsData.buffer); | |
let header_size = dataView.getUint32(0, true); | |
let offset = 8; | |
let header = JSON.parse(new TextDecoder().decode(safetensorsData.slice(offset, offset + header_size))); | |
r(header.__metadata__); | |
}; | |
var slice = file.slice(0, 1024 * 1024 * 4); | |
reader.readAsArrayBuffer(slice); | |
}); | |
} | |
function getString(dataView, offset, length) { | |
let string = ''; | |
for (let i = 0; i < length; i++) { | |
string += String.fromCharCode(dataView.getUint8(offset + i)); | |
} | |
return string; | |
} | |
// Function to parse the Vorbis Comment block | |
function parseVorbisComment(dataView) { | |
let offset = 0; | |
const vendorLength = dataView.getUint32(offset, true); | |
offset += 4; | |
const vendorString = getString(dataView, offset, vendorLength); | |
offset += vendorLength; | |
const userCommentListLength = dataView.getUint32(offset, true); | |
offset += 4; | |
const comments = {}; | |
for (let i = 0; i < userCommentListLength; i++) { | |
const commentLength = dataView.getUint32(offset, true); | |
offset += 4; | |
const comment = getString(dataView, offset, commentLength); | |
offset += commentLength; | |
const ind = comment.indexOf('=') | |
const key = comment.substring(0, ind); | |
comments[key] = comment.substring(ind+1); | |
} | |
return comments; | |
} | |
// Function to read a FLAC file and parse Vorbis comments | |
export function getFlacMetadata(file) { | |
return new Promise((r) => { | |
const reader = new FileReader(); | |
reader.onload = function(event) { | |
const arrayBuffer = event.target.result; | |
const dataView = new DataView(arrayBuffer); | |
// Verify the FLAC signature | |
const signature = String.fromCharCode(...new Uint8Array(arrayBuffer, 0, 4)); | |
if (signature !== 'fLaC') { | |
console.error('Not a valid FLAC file'); | |
return; | |
} | |
// Parse metadata blocks | |
let offset = 4; | |
let vorbisComment = null; | |
while (offset < dataView.byteLength) { | |
const isLastBlock = dataView.getUint8(offset) & 0x80; | |
const blockType = dataView.getUint8(offset) & 0x7F; | |
const blockSize = dataView.getUint32(offset, false) & 0xFFFFFF; | |
offset += 4; | |
if (blockType === 4) { // Vorbis Comment block type | |
vorbisComment = parseVorbisComment(new DataView(arrayBuffer, offset, blockSize)); | |
} | |
offset += blockSize; | |
if (isLastBlock) break; | |
} | |
r(vorbisComment); | |
}; | |
reader.readAsArrayBuffer(file); | |
}); | |
} | |
export async function importA1111(graph, parameters) { | |
const p = parameters.lastIndexOf("\nSteps:"); | |
if (p > -1) { | |
const embeddings = await api.getEmbeddings(); | |
const opts = parameters | |
.substr(p) | |
.split("\n")[1] | |
.match(new RegExp("\\s*([^:]+:\\s*([^\"\\{].*?|\".*?\"|\\{.*?\\}))\\s*(,|$)", "g")) | |
.reduce((p, n) => { | |
const s = n.split(":"); | |
if (s[1].endsWith(',')) { | |
s[1] = s[1].substr(0, s[1].length -1); | |
} | |
p[s[0].trim().toLowerCase()] = s[1].trim(); | |
return p; | |
}, {}); | |
const p2 = parameters.lastIndexOf("\nNegative prompt:", p); | |
if (p2 > -1) { | |
let positive = parameters.substr(0, p2).trim(); | |
let negative = parameters.substring(p2 + 18, p).trim(); | |
const ckptNode = LiteGraph.createNode("CheckpointLoaderSimple"); | |
const clipSkipNode = LiteGraph.createNode("CLIPSetLastLayer"); | |
const positiveNode = LiteGraph.createNode("CLIPTextEncode"); | |
const negativeNode = LiteGraph.createNode("CLIPTextEncode"); | |
const samplerNode = LiteGraph.createNode("KSampler"); | |
const imageNode = LiteGraph.createNode("EmptyLatentImage"); | |
const vaeNode = LiteGraph.createNode("VAEDecode"); | |
const vaeLoaderNode = LiteGraph.createNode("VAELoader"); | |
const saveNode = LiteGraph.createNode("SaveImage"); | |
let hrSamplerNode = null; | |
let hrSteps = null; | |
const ceil64 = (v) => Math.ceil(v / 64) * 64; | |
function getWidget(node, name) { | |
return node.widgets.find((w) => w.name === name); | |
} | |
function setWidgetValue(node, name, value, isOptionPrefix) { | |
const w = getWidget(node, name); | |
if (isOptionPrefix) { | |
const o = w.options.values.find((w) => w.startsWith(value)); | |
if (o) { | |
w.value = o; | |
} else { | |
console.warn(`Unknown value '${value}' for widget '${name}'`, node); | |
w.value = value; | |
} | |
} else { | |
w.value = value; | |
} | |
} | |
function createLoraNodes(clipNode, text, prevClip, prevModel) { | |
const loras = []; | |
text = text.replace(/<lora:([^:]+:[^>]+)>/g, function (m, c) { | |
const s = c.split(":"); | |
const weight = parseFloat(s[1]); | |
if (isNaN(weight)) { | |
console.warn("Invalid LORA", m); | |
} else { | |
loras.push({ name: s[0], weight }); | |
} | |
return ""; | |
}); | |
for (const l of loras) { | |
const loraNode = LiteGraph.createNode("LoraLoader"); | |
graph.add(loraNode); | |
setWidgetValue(loraNode, "lora_name", l.name, true); | |
setWidgetValue(loraNode, "strength_model", l.weight); | |
setWidgetValue(loraNode, "strength_clip", l.weight); | |
prevModel.node.connect(prevModel.index, loraNode, 0); | |
prevClip.node.connect(prevClip.index, loraNode, 1); | |
prevModel = { node: loraNode, index: 0 }; | |
prevClip = { node: loraNode, index: 1 }; | |
} | |
prevClip.node.connect(1, clipNode, 0); | |
prevModel.node.connect(0, samplerNode, 0); | |
if (hrSamplerNode) { | |
prevModel.node.connect(0, hrSamplerNode, 0); | |
} | |
return { text, prevModel, prevClip }; | |
} | |
function replaceEmbeddings(text) { | |
if(!embeddings.length) return text; | |
return text.replaceAll( | |
new RegExp( | |
"\\b(" + embeddings.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\b|\\b") + ")\\b", | |
"ig" | |
), | |
"embedding:$1" | |
); | |
} | |
function popOpt(name) { | |
const v = opts[name]; | |
delete opts[name]; | |
return v; | |
} | |
graph.clear(); | |
graph.add(ckptNode); | |
graph.add(clipSkipNode); | |
graph.add(positiveNode); | |
graph.add(negativeNode); | |
graph.add(samplerNode); | |
graph.add(imageNode); | |
graph.add(vaeNode); | |
graph.add(vaeLoaderNode); | |
graph.add(saveNode); | |
ckptNode.connect(1, clipSkipNode, 0); | |
clipSkipNode.connect(0, positiveNode, 0); | |
clipSkipNode.connect(0, negativeNode, 0); | |
ckptNode.connect(0, samplerNode, 0); | |
positiveNode.connect(0, samplerNode, 1); | |
negativeNode.connect(0, samplerNode, 2); | |
imageNode.connect(0, samplerNode, 3); | |
vaeNode.connect(0, saveNode, 0); | |
samplerNode.connect(0, vaeNode, 0); | |
vaeLoaderNode.connect(0, vaeNode, 1); | |
const handlers = { | |
model(v) { | |
setWidgetValue(ckptNode, "ckpt_name", v, true); | |
}, | |
"vae"(v) { | |
setWidgetValue(vaeLoaderNode, "vae_name", v, true); | |
}, | |
"cfg scale"(v) { | |
setWidgetValue(samplerNode, "cfg", +v); | |
}, | |
"clip skip"(v) { | |
setWidgetValue(clipSkipNode, "stop_at_clip_layer", -v); | |
}, | |
sampler(v) { | |
let name = v.toLowerCase().replace("++", "pp").replaceAll(" ", "_"); | |
if (name.includes("karras")) { | |
name = name.replace("karras", "").replace(/_+$/, ""); | |
setWidgetValue(samplerNode, "scheduler", "karras"); | |
} else { | |
setWidgetValue(samplerNode, "scheduler", "normal"); | |
} | |
const w = getWidget(samplerNode, "sampler_name"); | |
const o = w.options.values.find((w) => w === name || w === "sample_" + name); | |
if (o) { | |
setWidgetValue(samplerNode, "sampler_name", o); | |
} | |
}, | |
size(v) { | |
const wxh = v.split("x"); | |
const w = ceil64(+wxh[0]); | |
const h = ceil64(+wxh[1]); | |
const hrUp = popOpt("hires upscale"); | |
const hrSz = popOpt("hires resize"); | |
hrSteps = popOpt("hires steps"); | |
let hrMethod = popOpt("hires upscaler"); | |
setWidgetValue(imageNode, "width", w); | |
setWidgetValue(imageNode, "height", h); | |
if (hrUp || hrSz) { | |
let uw, uh; | |
if (hrUp) { | |
uw = w * hrUp; | |
uh = h * hrUp; | |
} else { | |
const s = hrSz.split("x"); | |
uw = +s[0]; | |
uh = +s[1]; | |
} | |
let upscaleNode; | |
let latentNode; | |
if (hrMethod.startsWith("Latent")) { | |
latentNode = upscaleNode = LiteGraph.createNode("LatentUpscale"); | |
graph.add(upscaleNode); | |
samplerNode.connect(0, upscaleNode, 0); | |
switch (hrMethod) { | |
case "Latent (nearest-exact)": | |
hrMethod = "nearest-exact"; | |
break; | |
} | |
setWidgetValue(upscaleNode, "upscale_method", hrMethod, true); | |
} else { | |
const decode = LiteGraph.createNode("VAEDecodeTiled"); | |
graph.add(decode); | |
samplerNode.connect(0, decode, 0); | |
vaeLoaderNode.connect(0, decode, 1); | |
const upscaleLoaderNode = LiteGraph.createNode("UpscaleModelLoader"); | |
graph.add(upscaleLoaderNode); | |
setWidgetValue(upscaleLoaderNode, "model_name", hrMethod, true); | |
const modelUpscaleNode = LiteGraph.createNode("ImageUpscaleWithModel"); | |
graph.add(modelUpscaleNode); | |
decode.connect(0, modelUpscaleNode, 1); | |
upscaleLoaderNode.connect(0, modelUpscaleNode, 0); | |
upscaleNode = LiteGraph.createNode("ImageScale"); | |
graph.add(upscaleNode); | |
modelUpscaleNode.connect(0, upscaleNode, 0); | |
const vaeEncodeNode = (latentNode = LiteGraph.createNode("VAEEncodeTiled")); | |
graph.add(vaeEncodeNode); | |
upscaleNode.connect(0, vaeEncodeNode, 0); | |
vaeLoaderNode.connect(0, vaeEncodeNode, 1); | |
} | |
setWidgetValue(upscaleNode, "width", ceil64(uw)); | |
setWidgetValue(upscaleNode, "height", ceil64(uh)); | |
hrSamplerNode = LiteGraph.createNode("KSampler"); | |
graph.add(hrSamplerNode); | |
ckptNode.connect(0, hrSamplerNode, 0); | |
positiveNode.connect(0, hrSamplerNode, 1); | |
negativeNode.connect(0, hrSamplerNode, 2); | |
latentNode.connect(0, hrSamplerNode, 3); | |
hrSamplerNode.connect(0, vaeNode, 0); | |
} | |
}, | |
steps(v) { | |
setWidgetValue(samplerNode, "steps", +v); | |
}, | |
seed(v) { | |
setWidgetValue(samplerNode, "seed", +v); | |
}, | |
}; | |
for (const opt in opts) { | |
if (opt in handlers) { | |
handlers[opt](popOpt(opt)); | |
} | |
} | |
if (hrSamplerNode) { | |
setWidgetValue(hrSamplerNode, "steps", hrSteps? +hrSteps : getWidget(samplerNode, "steps").value); | |
setWidgetValue(hrSamplerNode, "cfg", getWidget(samplerNode, "cfg").value); | |
setWidgetValue(hrSamplerNode, "scheduler", getWidget(samplerNode, "scheduler").value); | |
setWidgetValue(hrSamplerNode, "sampler_name", getWidget(samplerNode, "sampler_name").value); | |
setWidgetValue(hrSamplerNode, "denoise", +(popOpt("denoising strength") || "1")); | |
} | |
let n = createLoraNodes(positiveNode, positive, { node: clipSkipNode, index: 0 }, { node: ckptNode, index: 0 }); | |
positive = n.text; | |
n = createLoraNodes(negativeNode, negative, n.prevClip, n.prevModel); | |
negative = n.text; | |
setWidgetValue(positiveNode, "text", replaceEmbeddings(positive)); | |
setWidgetValue(negativeNode, "text", replaceEmbeddings(negative)); | |
graph.arrange(); | |
for (const opt of ["model hash", "ensd", "version", "vae hash", "ti hashes", "lora hashes", "hashes"]) { | |
delete opts[opt]; | |
} | |
console.warn("Unhandled parameters:", opts); | |
} | |
} | |
} | |