Files
Austin Mroz 3431f48cd7 Raise upper limit on decoding of metadata
The base implementation from pngs uses String.fromCharCode,
but that causes overflow from having too many arguments when decoding
video files, likely since no chunking is done and everything is embedded
as a single monolithic json

As an additional benefit, data is now properly decoded from utf-8
instead of being interpreted as utf-16
2023-09-22 20:32:46 -05:00

105 lines
4.3 KiB
JavaScript

import { app } from '../../../scripts/app.js'
function getVideoMetadata(file) {
return new Promise((r) => {
const reader = new FileReader();
reader.onload = (event) => {
const videoData = new Uint8Array(event.target.result);
const dataView = new DataView(videoData.buffer);
let decoder = new TextDecoder();
// Check for known valid magic strings
if (dataView.getUint32(0) == 0x1A45DFA3) {
//webm
//see http://wiki.webmproject.org/webm-metadata/global-metadata
//and https://www.matroska.org/technical/elements.html
//contrary to specs, tag seems consistently at start
//COMMENT + 0x4487 + packed length?
//length 0x8d8 becomes 0x48d8
//
//description for variable length ints https://github.com/ietf-wg-cellar/ebml-specification/blob/master/specification.markdown
let offset = 4 + 8; //COMMENT is 7 chars + 1 to realign
while(offset < videoData.length-16) {
//Check for text tags
if (dataView.getUint16(offset) == 0x4487) {
//check that name of tag is COMMENT
const name = String.fromCharCode(...videoData.slice(offset-7,offset));
if (name === "COMMENT") {
let vint = dataView.getUint32(offset+2);
let n_octets = Math.clz32(vint)+1;
if (n_octets < 4) {//250MB sanity cutoff
let length = (vint >> (8*(4-n_octets))) & ~(1 << (7*n_octets));
const content = decoder.decode(videoData.slice(offset+2+n_octets, offset+2+n_octets+length));
const json = JSON.parse(content);
r(json);
return;
}
}
}
offset+=1;
}
} else if (dataView.getUint32(4) == 0x66747970 && dataView.getUint32(8) == 0x69736F6D) {
//mp4
//see https://developer.apple.com/documentation/quicktime-file-format
//Seems to make no guarantee for alignment
let offset = videoData.length-4;
while (offset > 16) {//rough safe guess
if (dataView.getUint32(offset) == 0x64617461) {//any data tag
if (dataView.getUint32(offset - 8) == 0xa9636d74) {//cmt data tag
let type = dataView.getUint32(offset+4); //seemingly 1
let locale = dataView.getUint32(offset+8); //seemingly 0
let size = dataView.getUint32(offset-4) - 4*4;
const content = decoder.decode(videoData.slice(offset+12, offset+12+size));
const json = JSON.parse(content);
r(json);
return;
}
}
offset-=1;
}
} else {
console.error("Unknown magic: " + dataView.getUint32(0))
r();
return;
}
};
reader.readAsArrayBuffer(file);
});
}
function isVideoFile(file) {
if (file?.name?.endsWith(".webm")) {
return true;
}
if (file?.name?.endsWith(".mp4")) {
return true;
}
return false;
}
async function handleFile(file) {
if (file?.type?.startsWith("video/") || isVideoFile(file)) {
const videoInfo = await getVideoMetadata(file);
if (videoInfo) {
if (videoInfo.workflow) {
app.loadGraphData(videoInfo.workflow);
}
//Potentially check for/parse A1111 metadata here.
}
} else {
await app.originalHandleFile(file);
}
}
//Storing the original function in app is probably a major no-no
//But it's the only way I've found to keep the 'this' reference
app.originalHandleFile = app.handleFile;
app.handleFile = handleFile;
//hijack comfy-file-input to allow webm/mp4
document.getElementById("comfy-file-input").accept += ",video/webm,video/mp4";