sd-image-info / parser.js
gutris1's picture
Update parser.js
1e3bb4f verified
async function SharedImageParser(img, imgSrc = false) {
const decodeUserComment = (array) => {
const result = [];
let pos = 7;
if (array[8] === 123) for (let i = pos; i < array.length; i += 2) { const a = array[i], b = array[i + 1]; result.push(a === 0 && b === 32 ? 32 : a * 256 + b); }
else {
for (let i = pos; i < array.length; i++) {
if (i === 7 && array[i] === 0) continue;
if (array[i] === 0) if (i + 1 < array.length && array[i + 1] === 0) { i++; continue; }
if (i + 1 < array.length) { const a = array[i], b = array[i + 1]; result.push(a === 0 && b === 32 ? 32 : a * 256 + b); i++; continue; }
}
}
const output = new TextDecoder('utf-16').decode(new Uint16Array(result)).trim();
return output.replace(/^UNICODE[\x00-\x20]*/, '');
},
NovelAI = (input) => {
const NAIround = v => Math.round(v * 10000) / 10000,
NAIMultiplyRange = (start, multiplier) => res.slice(start).forEach(row => row[1] = NAIround(row[1] * multiplier)),
re_attention = /\{|\[|\}|\]|[^\{\}\[\]]+/gmu,
curly_bracket_multiplier = 1.05,
square_bracket_multiplier = 1 / 1.05;
let t = input.replaceAll('(', '\\(').replaceAll(')', '\\)').replace(/\\{2,}(\(|\))/gim, '\$1'),
res = [], curly_brackets = [], square_brackets = [], result = '';
for (const match of t.matchAll(re_attention)) {
let w = match[0];
if (w === '{') curly_brackets.push(res.length);
else if (w === '[') square_brackets.push(res.length);
else if (w === '}' && curly_brackets.length > 0) NAIMultiplyRange(curly_brackets.pop(), curly_bracket_multiplier);
else if (w === ']' && square_brackets.length > 0) NAIMultiplyRange(square_brackets.pop(), square_bracket_multiplier);
else res.push([w, 1.0]);
}
for (const pos of curly_brackets) NAIMultiplyRange(pos, curly_bracket_multiplier);
for (const pos of square_brackets) NAIMultiplyRange(pos, square_bracket_multiplier);
if (res.length === 0) res = [['', 1.0]];
let i = 0;
while (i + 1 < res.length) { if (res[i][1] === res[i + 1][1]) { res[i][0] += res[i + 1][0]; res.splice(i + 1, 1); } else { i++; }}
for (let i = 0; i < res.length; i++) { if (res[i][1] === 1.0) { result += res[i][0]; } else { result += `(${res[i][0]}:${res[i][1]})`; }}
return result;
},
swarmUI = (Sui, extraData = {}) => {
const parts = [],
Format = {
prompt: v => `${v}\n`,
negativeprompt: v => `Negative prompt: ${v}\n`,
steps: v => `Steps: ${v}`,
sampler: v => `Sampler: ${v.replace(/\beuler\b|\beuler(-\w+)?/gi, m => m.replace(/euler/i, 'Euler'))}`,
scheduler: v => `Schedule type: ${v}`,
cfgscale: v => `CFG scale: ${v}`,
seed: v => `Seed: ${v}`,
width: (_, obj) => obj.width && obj.height ? `Size: ${obj.width}x${obj.height}` : null,
model: v => `Model: ${v}`,
vae: v => `VAE: ${v.split('/').pop()}`
};
for (const [key, fn] of Object.entries(Format)) {
if (Sui[key] != null) {
const str = fn(Sui[key], Sui);
if (str) parts.push(str.replace(/\n$/, ''));
}
}
window.SharedParserSoftwareInfo = Sui?.swarm_version ? `SwarmUI ${Sui.swarm_version}` : '';
const ignoreKeys = Object.keys(Format).concat('swarm_version'),
otherParams = Object.entries(Sui).filter(([k]) => !ignoreKeys.includes(k)).map(([k, v]) => `${k}: ${v}`),
extraParams = Object.entries(extraData).map(([k, v]) => `${k}: ${v}`);
return [...parts, ...otherParams, ...extraParams].join(', ').trim();
};
['EncryptInfo', 'Sha256Info', 'ExtrasInfo', 'PostProcessingInfo', 'NaiSourceInfo', 'SoftwareInfo']
.forEach(k => window[`SharedParser${k}`] = '');
let output = '', buff, blob, tags;
if (img.src.startsWith('data:')) {
const [prefix, base64] = img.src.split(','), b = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
buff = b.buffer;
blob = new Blob([b], { type: prefix.match(/data:(.*?);base64/)[1] });
} else {
const url = imgSrc ? img.src : (window.SDHubImg?.trim() || img.src);
blob = await (await fetch(url)).blob();
buff = await blob.arrayBuffer();
}
img.src = URL.createObjectURL(blob);
tags = ExifReader.load(buff);
if (tags) {
window.SharedParserEncryptInfo = tags.Encrypt?.description || '';
window.SharedParserSha256Info = tags.EncryptPwdSha?.description || '';
const extra = tags.extras?.description || tags.postprocessing?.description || '';
window.SharedParserExtrasInfo = tags.extras?.description ? extra : '';
window.SharedParserPostProcessingInfo = tags.extras?.description ? '' : extra;
if (tags.parameters?.description) {
if (tags.parameters.description.includes('sui_image_params')) {
const parSing = JSON.parse(tags.parameters.description);
const Sui = parSing['sui_image_params'];
output = swarmUI(Sui, {});
} else {
output = tags.parameters.description;
}
} else if (tags.UserComment?.value) {
const array = tags.UserComment.value;
const UserComments = decodeUserComment(array);
if (UserComments.includes('sui_image_params')) {
const rippin = UserComments.trim().replace(/[\x00-\x1F\x7F]/g, '');
const parSing = JSON.parse(rippin);
if (parSing['sui_image_params']) {
const Sui = parSing['sui_image_params'];
const SuiExtra = parSing['sui_extra_data'] || {};
output = swarmUI(Sui, SuiExtra);
}
} else {
if (UserComments.startsWith('Postprocess upscale')) {
window.SharedParserExtrasInfo = UserComments;
output = '';
} else {
output = UserComments;
}
}
} else if (tags['Software']?.description === 'NovelAI' && tags.Comment?.description) {
window.SharedParserSoftwareInfo = tags['Software']?.description || '';
window.SharedParserNaiSourceInfo = tags['Source']?.description || '';
const nai = JSON.parse(tags.Comment.description);
nai.sampler = 'Euler';
output = NovelAI(nai['prompt']) +
'\nNegative prompt: ' + NovelAI(nai['uc']) +
'\nSteps: ' + nai['steps'] +
', Sampler: ' + nai['sampler'] +
', CFG scale: ' + parseFloat(nai['scale']).toFixed(1) +
', Seed: ' + nai['seed'] +
', Size: ' + nai['width'] + 'x' + nai['height'] +
', Clip skip: 2, ENSD: 31337';
} else if (tags.prompt?.description?.includes('"filename_prefix": "ComfyUI"')) {
output = 'ComfyUI<br>Nothing To Read Here';
} else if (tags.invokeai_graph?.description) {
output = 'InvokeAI<br>Nothing To Read Here';
} else {
output = 'Nothing To See Here';
}
}
return output;
}
function SharedPromptParser(t) {
const negativePromptIndex = t.indexOf('Negative prompt:'),
stepsIndex = t.indexOf('Steps:'),
hashesIndex = t.indexOf('Hashes:');
let prompt = '', negativePrompt = '', params = '';
if (negativePromptIndex !== -1) {
prompt = t.substring(0, negativePromptIndex).trim();
} else if (stepsIndex !== -1) {
prompt = t.substring(0, stepsIndex).trim();
} else {
prompt = t.trim();
}
if (negativePromptIndex !== -1 && stepsIndex !== -1 && stepsIndex > negativePromptIndex) {
negativePrompt = t.slice(negativePromptIndex + 'Negative prompt:'.length, stepsIndex).trim();
}
if (stepsIndex !== -1) {
const paramsRAW = t.slice(stepsIndex).trim();
params = paramsRAW.replace(/,\s*(Lora hashes|TI hashes):\s*"[^"]+"/g, '').trim();
const h = t.slice(hashesIndex).match(/Hashes:\s*(\{.*?\})(,\s*)?/);
if (h?.[1]) params = params.replace(h[0], '').trim();
if (params.endsWith(',')) params = params.slice(0, -1).trim();
return { prompt, negativePrompt, params, paramsRAW };
} else {
params = t.trim();
return { prompt, negativePrompt, params, paramsRAW: null };
}
}
async function FetchModel(name, hash, cv = false) {
const nonlink =
`<span class='sd-image-scripts-nonlink'>` +
`${name}${cv ? '' : `: ${hash}`}` +
`</span>`;
if (!hash) {
return nonlink;
}
let t = hash;
while (t.length >= 8) {
try {
const r = await fetch(`https://civitai.red/api/v1/model-versions/by-hash/${t}`);
if (r.status === 200) {
const d = await r.json();
const fN = d?.model?.name;
const sN = d?.name;
if (fN && sN) {
return `
<a
class='sd-image-scripts-link'
href='https://civitai.red/models/${d.modelId}?modelVersionId=${d.id}'
target='_blank'
tabindex='-1'>
${fN} - ${sN}
</a>
`;
}
}
} catch {}
t = t.slice(0, -2);
}
return cv
? nonlink
: `
<a
class='sd-image-scripts-link'
href='https://civitai.red/search/models?sortBy=models_v9&query=${hash}'
target='_blank'
tabindex='-1'>
${name}
</a>
`;
}
function Result(label, models) {
if (window.innerWidth <= 768 && label === 'checkpoint') label = 'ckpt';
return `
<div class='sd-image-scripts-modeloutput-line'>
<div class='sd-image-scripts-modeloutput-label'>
${label}
</div>
<div class='sd-image-scripts-modeloutput-hashes'>
${models.join(' ')}
</div>
</div>
`;
}
async function SharedModelsFetch(i, timeout = 60000) {
const err = console.error;
console.error = function(...args) {
const msg = args.toString();
if (msg) return;
err.apply(console, args);
};
return await Promise.race([
(async () => {
const Cat = {
checkpoint: [],
vae: [],
lora: [],
embed: []
},
EmbedNames = new Set(),
LoraNames = new Set(),
HashesDict = {},
modelEX = i.includes('Model: "') ? i.match(/Model:\s*"?([^"]+)"/) : i.match(/Model:\s*([^,]+)/),
modelHashEX = i.match(/Model hash:\s*([^,]+)/),
vaeEX = i.match(/VAE:\s*([^,]+)/),
vaeHashEX = i.match(/VAE hash:\s*([^,]+)/),
loraHashEX = i.match(/Lora hashes:\s*"([^"]+)"/),
tiHashEX = i.match(/TI hashes:\s*"([^"]+)"/) || i.match(/TI:\s*"([^"]+)"/),
hashesIndex = i.indexOf('Hashes:'),
hashesEX = hashesIndex !== -1 ? i.slice(hashesIndex).match(/Hashes:\s*(\{.*?\})(,\s*)?/) : null;
if (modelEX) {
const modelValue = modelEX[1],
modelHash = modelHashEX ? modelHashEX[1] : null,
vaeValue = vaeEX ? vaeEX[1] : null,
vaeHash = vaeHashEX ? vaeHashEX[1] : null;
if (modelHash || vaeValue || vaeHash) { Cat.checkpoint.push({ n: modelValue, h: modelHash }); }
if (vaeValue || vaeHash) { Cat.vae.push({ n: vaeValue, h: vaeHash }); }
}
if (hashesEX && hashesEX[1]) {
try {
const s = JSON.parse(hashesEX[1].trim());
for (const [k, h] of Object.entries(s)) {
if (k.startsWith('embed:')) {
const n = k.slice(6);
HashesDict[n] = h;
if (!EmbedNames.has(n)) { EmbedNames.add(n); Cat.embed.push({ n, h }); }
} else if (k.startsWith('lora:')) {
const n = k.slice(5);
HashesDict[n] = h;
if (!LoraNames.has(n)) { LoraNames.add(n); Cat.lora.push({ n, h }); }
}
}
} catch (e) {
console.warn('Failed to parse Hashes:', e);
}
}
if (loraHashEX) {
const loraPairs =
loraHashEX[1].split(',').map(pair => pair.trim());
for (const p of loraPairs) {
const [n, h] = p.split(':').map(x => x.trim());
if (h && !HashesDict[n] && !LoraNames.has(n)) {
LoraNames.add(n);
Cat.lora.push({ n, h });
}
}
}
if (tiHashEX) {
const embedPairs =
tiHashEX[1].split(',').map(pair => pair.trim());
for (const p of embedPairs) {
const [n, h] = p.split(':').map(x => x.trim());
if (h && !HashesDict[n] && !EmbedNames.has(n)) {
EmbedNames.add(n);
Cat.embed.push({ n, h });
}
}
}
try {
let html = '';
for (const [category, items] of Object.entries(Cat)) {
if (!items.length) continue;
const cv = category === 'checkpoint' || category === 'vae';
const models = await Promise.all(items.map(v => FetchModel(v.n, v.h, cv)));
html += Result(category, models);
}
setTimeout(() => {
[
'sd-image-scripts-modeloutput-label',
'sd-image-scripts-modeloutput-hashes'
].forEach(C => {
document.querySelectorAll(`.${C}`).forEach(el =>
el.classList.add('sd-image-scripts-display')
);
});
}, 100);
return html ? `<div id='SD-Image-Scripts-Model-Output'>${html}</div>` : '';
} catch (err) {
console.error('Fetch failed', err);
return '';
}
})(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout))
]);
}