Files
ausboss-ComfyUI-AusBoss/js/shared/mp4_workflow.mjs
ausboss 4e435e3013 Trade the display-name suffix for the 🆎 signature and unclip the chat toolbar
Every display name swaps ' (AusBoss)' for ' 🆎' - menus, docs, tooltips,
the README, and the example workflow's notes. Mapping keys never change,
so saved workflows keep loading; 'ausboss' keeps finding everything via
the category, the AUSBOSS_NODES_ prefix, and SEARCH_ALIASES. Python error
messages drop the suffix entirely so exception text stays ASCII for
cp1252 consoles.

The LM Studio endpoint toolbar declared no getMinHeight, so the frontend
gave its wrapper a 12px sliver and the buttons clipped; it now declares
32px through every sizing path, and the buttons breathe at 22px.
2026-08-16 20:29:38 -05:00

138 lines
4.9 KiB
JavaScript

// Pure byte parsing for the workflow Save Video 🆎 embeds in its mp4s.
//
// The backend writes container metadata with ffmpeg's use_metadata_tags flag,
// which lands as the ISO BMFF "mdta" scheme: moov > udta > meta holding a
// `keys` atom (key names) and an `ilst` atom (values indexed by key number).
// This module walks those boxes and returns the embedded JSON strings. It is
// dependency-free so node:test can drive it directly.
const UTF8 = new TextDecoder();
function toBytes(input) {
if (input instanceof Uint8Array) return input;
if (input instanceof ArrayBuffer) return new Uint8Array(input);
return null;
}
function fourcc(bytes, offset) {
return String.fromCharCode(
bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3],
);
}
function u32(view, offset) {
return view.getUint32(offset);
}
// List the child boxes of [start, end). Each entry carries the type both as a
// string and as the raw uint32, because ilst entries use a key index where
// every other box keeps a printable four-character code. Corrupt or truncated
// sizes stop the walk instead of throwing.
function childBoxes(bytes, view, start, end) {
const children = [];
let offset = start;
while (offset + 8 <= end) {
let size = u32(view, offset);
let headerSize = 8;
if (size === 1) {
if (offset + 16 > end) break;
size = u32(view, offset + 8) * 2 ** 32 + u32(view, offset + 12);
headerSize = 16;
} else if (size === 0) {
size = end - offset;
}
if (size < headerSize || offset + size > end) break;
children.push({
type: fourcc(bytes, offset + 4),
rawType: u32(view, offset + 4),
start: offset + headerSize,
end: offset + size,
});
offset += size;
}
return children;
}
function findChild(children, type) {
return children.find((child) => child.type === type) ?? null;
}
// keys atom: fullbox (version/flags), entry count, then per entry a size,
// a namespace code (mdta), and the key name. Returns names in atom order.
function parseKeys(bytes, view, box) {
if (box.end - box.start < 8) return [];
const names = [];
const count = u32(view, box.start + 4);
let offset = box.start + 8;
while (names.length < count && offset + 8 <= box.end) {
const size = u32(view, offset);
if (size < 8 || offset + size > box.end) break;
names.push(UTF8.decode(bytes.subarray(offset + 8, offset + size)));
offset += size;
}
return names;
}
// ilst atom: each child's type is a 1-based index into keys; its `data`
// sub-box carries a type code (1 = UTF-8), a locale, then the payload.
function parseIlst(bytes, view, box, names) {
const tags = {};
for (const entry of childBoxes(bytes, view, box.start, box.end)) {
const name = names[entry.rawType - 1];
if (!name) continue;
const data = findChild(childBoxes(bytes, view, entry.start, entry.end), "data");
if (!data || data.end - data.start < 8) continue;
if (u32(view, data.start) !== 1) continue; // not UTF-8 text
tags[name] = UTF8.decode(bytes.subarray(data.start + 8, data.end));
}
return tags;
}
export function looksLikeMp4(input) {
const bytes = toBytes(input);
return !!bytes && bytes.length >= 12 && fourcc(bytes, 4) === "ftyp";
}
// All UTF-8 metadata tags embedded via the mdta scheme, as name -> string.
// Empty object when the file has none or the bytes are not a usable mp4.
export function extractMp4MetadataTags(input) {
const bytes = toBytes(input);
if (!bytes || !looksLikeMp4(bytes)) return {};
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
const moov = findChild(childBoxes(bytes, view, 0, bytes.length), "moov");
if (!moov) return {};
const udta = findChild(childBoxes(bytes, view, moov.start, moov.end), "udta");
if (!udta) return {};
const meta = findChild(childBoxes(bytes, view, udta.start, udta.end), "meta");
if (!meta) return {};
// ISO files make meta a fullbox (4 bytes of version/flags before the first
// child); QuickTime-brand files do not. Try both offsets.
for (const start of [meta.start + 4, meta.start]) {
if (start > meta.end) continue;
const children = childBoxes(bytes, view, start, meta.end);
const keysBox = findChild(children, "keys");
const ilstBox = findChild(children, "ilst");
if (keysBox && ilstBox) {
return parseIlst(bytes, view, ilstBox, parseKeys(bytes, view, keysBox));
}
}
return {};
}
function parseJson(text) {
if (typeof text !== "string" || !text) return null;
try {
const value = JSON.parse(text);
return value && typeof value === "object" ? value : null;
} catch {
return null;
}
}
// The embedded graphs as objects: `workflow` (editor format) and `prompt`
// (API format). Either is null when missing or unparseable.
export function mp4WorkflowPayload(input) {
const tags = extractMp4MetadataTags(input);
return { workflow: parseJson(tags.workflow), prompt: parseJson(tags.prompt) };
}