import { type ComfyApiWorkflow, type ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' import { type ComfyMetadata, ComfyMetadataTags, type EbmlElementRange, type EbmlTagPosition, type TextRange, type VInt } from '@/types/metadataTypes' const WEBM_SIGNATURE = [0x1a, 0x45, 0xdf, 0xa3] const MAX_READ_BYTES = 2 * 1024 * 1024 const EBML_ID = { SIMPLE_TAG: new Uint8Array([0x67, 0xc8]), TAG_NAME: new Uint8Array([0x45, 0xa3]), TAG_VALUE: new Uint8Array([0x44, 0x87]) } const ASCII = { OPEN_BRACE: 0x7b, NULL: 0, PRINTABLE_MIN: 32, PRINTABLE_MAX: 126 } const MASK = { MSB: 0x80, ALL_BITS_SET: -1 } const hasWebmSignature = (data: Uint8Array): boolean => WEBM_SIGNATURE.every((byte, index) => data[index] === byte) const readVint = (data: Uint8Array, pos: number): VInt | null => { if (pos >= data.length) return null const byte = data[pos] // Fast path for common case (1-byte vint) if ((byte & MASK.MSB) === MASK.MSB) { return { value: byte & ~MASK.MSB, length: 1 } } const length = findFirstSetBitPosition(byte) if (length === 0 || pos + length > data.length) { return null } return { value: calculateVintValue(data, pos, length), length } } const calculateVintValue = ( data: Uint8Array, pos: number, length: number ): number => { let value = data[pos] & (0xff >> length) for (let i = 1; i < length; i++) { value = (value << 8) | data[pos + i] } const allBitsSet = Math.pow(2, 7 * length) - 1 return value === allBitsSet ? MASK.ALL_BITS_SET : value } const findFirstSetBitPosition = (byte: number): number => { for (let mask = MASK.MSB, position = 1; mask !== 0; mask >>= 1, position++) { if ((byte & mask) !== 0) return position } return 0 } const matchesId = (data: Uint8Array, pos: number, id: Uint8Array): boolean => { if (pos + id.length > data.length) return false for (let i = 0; i < id.length; i++) { if (data[pos + i] !== id[i]) return false } return true } const findNextTag = ( data: Uint8Array, pos: number ): { tagEnd: number; contents: EbmlTagPosition | null } | null => { if (!matchesId(data, pos, EBML_ID.SIMPLE_TAG)) return null const tagSize = readVint(data, pos + 2) if (!tagSize || tagSize.value <= 0) return null const tagEnd = pos + 2 + tagSize.length + tagSize.value if (tagEnd > data.length) return null const contents = extractTagContents(data, pos + 2 + tagSize.length, tagEnd) return { tagEnd, contents } } const extractTagContents = ( data: Uint8Array, start: number, end: number ): EbmlTagPosition | null => { const nameInfo = findElementsInTag(data, start, end, EBML_ID.TAG_NAME) const valueInfo = findElementsInTag(data, start, end, EBML_ID.TAG_VALUE) if (!nameInfo || !valueInfo) return null return { name: nameInfo, value: valueInfo } } const findElementsInTag = ( data: Uint8Array, start: number, end: number, id: Uint8Array ): EbmlElementRange | null => { for (let pos = start; pos < end - 1; ) { if (matchesId(data, pos, id)) { const size = readVint(data, pos + 2) if (size && size.value > 0) { const elementPos = pos + 2 + size.length return { pos: elementPos, len: size.value } } } pos++ } return null } const processTagContents = ( data: Uint8Array, contents: EbmlTagPosition, meta: ComfyMetadata ) => { try { const name = extractEbmlTagName(data, contents.name.pos, contents.name.len) if (!name) return const value = extractEbmlTagValue( data, name, contents.value.pos, contents.value.len ) if (value !== null) { meta[name.toLowerCase()] = value } } catch { // Silently continue on error } } const extractEbmlTagValue = ( data: Uint8Array, name: string, pos: number, len: number ): string | ComfyWorkflowJSON | ComfyApiWorkflow | null => { if ( name === ComfyMetadataTags.PROMPT || name === ComfyMetadataTags.WORKFLOW ) { return readJson(data, pos, len) } return ebmlToString(data, pos, len) } const extractEbmlTagName = ( data: Uint8Array, start: number, length: number ): string | null => { if (length <= 0) return null const textRange = findReadableTextRange(data, start, length) if (!textRange) return null return new TextDecoder() .decode(data.subarray(textRange.start, textRange.end)) .trim() } const findReadableTextRange = ( data: Uint8Array, start: number, length: number ): TextRange | null => { const isPrintableAscii = (byte: number) => byte >= ASCII.PRINTABLE_MIN && byte <= ASCII.PRINTABLE_MAX let textStart = start while (textStart < start + length && !isPrintableAscii(data[textStart])) { textStart++ } if (textStart >= start + length) return null let textEnd = textStart while (textEnd < start + length && data[textEnd] !== ASCII.NULL) { textEnd++ } return { start: textStart, end: textEnd } } const readJson = ( data: Uint8Array, start: number, length: number ): ComfyWorkflowJSON | ComfyApiWorkflow | null => { if (length <= 0) return null const nullTerminatorPos = findNullTerminator(data, start, length) const jsonStartPos = findJsonStart(data, start, nullTerminatorPos - start) if (jsonStartPos === null) return null const jsonText = decodeJsonText(data, jsonStartPos, nullTerminatorPos) return parseJsonText(jsonText) } const decodeJsonText = ( data: Uint8Array, start: number, end: number ): string => { return new TextDecoder().decode(data.subarray(start, end)) } const parseJsonText = ( jsonText: string ): ComfyWorkflowJSON | ComfyApiWorkflow | null => { const jsonEndPos = findJsonEnd(jsonText) if (jsonEndPos === null) return null try { return JSON.parse(jsonText.substring(0, jsonEndPos)) } catch { return null } } const findNullTerminator = ( data: Uint8Array, start: number, maxLength: number ): number => { const end = Math.min(start + maxLength, data.length) for (let pos = start; pos < end; pos++) { if (data[pos] === ASCII.NULL) return pos } return end } const findJsonStart = ( data: Uint8Array, start: number, length: number ): number | null => { for (let pos = start; pos < start + length; pos++) { if (data[pos] === ASCII.OPEN_BRACE) return pos } return null } const findJsonEnd = (text: string): number | null => { let braceCount = 1 let pos = 1 // Start after the opening brace while (braceCount > 0 && pos < text.length) { if (text[pos] === '{') braceCount++ if (text[pos] === '}') braceCount-- pos++ } return braceCount === 0 ? pos : null } const ebmlToString = ( data: Uint8Array, start: number, length: number ): string => { if (length <= 0) return '' const endPos = findNullTerminator(data, start, length) return new TextDecoder().decode(data.subarray(start, endPos)).trim() } const parseMetadata = (data: Uint8Array): ComfyMetadata => { const meta: ComfyMetadata = {} for (let pos = 0; pos < data.length - 2; ) { const tagInfo = findNextTag(data, pos) if (!tagInfo) { pos++ continue } const { tagEnd, contents } = tagInfo if (contents) { processTagContents(data, contents, meta) } pos = tagEnd } return meta } const handleFileLoad = ( event: ProgressEvent, resolve: (value: ComfyMetadata) => void ) => { if (!event.target?.result) { resolve({}) return } try { const data = new Uint8Array(event.target.result as ArrayBuffer) if (data.length < 4 || !hasWebmSignature(data)) { resolve({}) return } resolve(parseMetadata(data)) } catch { resolve({}) } } /** * Extracts ComfyUI Workflow metadata from a WebM file * @param file - The WebM file to extract metadata from */ export function getFromWebmFile(file: File): Promise { return new Promise((resolve) => { const reader = new FileReader() reader.onload = (event) => handleFileLoad(event, resolve) reader.onerror = () => resolve({}) reader.readAsArrayBuffer(file.slice(0, MAX_READ_BYTES)) }) }