Completes the v0.1.5 -> v1.0 roadmap and hardens the path handling around it. Features - Auto Stitch: recommends where to cut between two clips, scoring candidates on frame similarity and optical-flow motion continuity (recommend + Apply, never an automatic edit). - AI Detect: scene-change detection (ffmpeg scene score) and beat tracking (librosa), with beats used as snap targets. - Pre-render: flattens the marked range into one cached file for smooth playback; invalidated automatically by any edit. - Version Stack, Wipe Compare, Box/Shift select, per-clip Solo/Gain, MOV/WebM export, whole-timeline Send To ComfyUI. Security - Contain clip paths on export. A clip's path is read from the project file and POST /itda/api/project/<name> stores arbitrary JSON, so any local process could point a clip at any file, have ffmpeg composite it into the export, and fetch it back via /itda/api/file. - Contain the export path in ITDA Load Export. The node trusted "path" from last_export.json and handed it straight to a decoder, so a tampered manifest could emit any file's contents as node output. - Reject dot-only project names. safe_name() stripped separators but passed "." and ".." through, collapsing media/<project> to the whole ITDA root; media/delete would then accept any file under input/ITDA. - Validate drawtext colors. Unvalidated color/shadow_color were interpolated into the ffmpeg filter, so "ffffff:textfile=..." appended a real drawtext option. Also set expansion=none. Fixes - ffmpeg no longer blocks ComfyUI's event loop (an export froze the whole UI for its duration); all calls now run off-loop. - Auto Stitch no longer ranks on audio: measured against known-correct cut points it could not separate good cuts from bad, and demoted the perfect cut to last. Frame + motion scores 5/5 on ground truth. Removed itda_server.py, web/itda_editor.html/.css and an empty itda_client.js (all superseded and unreferenced), and stopped tracking __pycache__. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
2.7 KiB
JavaScript
71 lines
2.7 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
|
|
// Consumes ?itda_send=<subfolder/file>&itda_kind=<image|video|audio> (single
|
|
// clip/range) or ?itda_send_batch=<JSON array of {relative,kind}> (whole
|
|
// timeline) set by the ITDA editor's "Send To ComfyUI" button, and drops
|
|
// matching loader node(s) pointed at the exported file(s) into the graph.
|
|
//
|
|
// ComfyUI's own frontend strips/normalizes the URL (history.replaceState)
|
|
// during its early startup, before any extension's setup() hook runs. So the
|
|
// query string must be captured here at module top-level (which executes
|
|
// synchronously the moment this script is imported), not inside setup().
|
|
const _params = new URLSearchParams(window.location.search);
|
|
const _pendingSend = _params.get("itda_send");
|
|
const _pendingKind = _params.get("itda_kind");
|
|
let _pendingBatch = null;
|
|
try {
|
|
const raw = _params.get("itda_send_batch");
|
|
if (raw) _pendingBatch = JSON.parse(raw);
|
|
} catch (e) {
|
|
console.warn("[ITDA] Could not parse itda_send_batch:", e);
|
|
}
|
|
if (_pendingSend || _pendingBatch) {
|
|
_params.delete("itda_send");
|
|
_params.delete("itda_kind");
|
|
_params.delete("itda_send_batch");
|
|
const clean = _params.toString();
|
|
window.history.replaceState({}, "", clean ? `${window.location.pathname}?${clean}` : window.location.pathname);
|
|
}
|
|
|
|
let _consumed = false;
|
|
|
|
function nodeTypeFor(kind) {
|
|
return kind === "image" ? "LoadImage" : kind === "audio" ? "LoadAudio" : "LoadVideo";
|
|
}
|
|
function widgetNameFor(kind) {
|
|
return kind === "image" ? "image" : kind === "audio" ? "audio" : "file";
|
|
}
|
|
function dropLoaderNode(relative, kind, pos) {
|
|
const node = LiteGraph.createNode(nodeTypeFor(kind));
|
|
if (!node) {
|
|
console.warn(`[ITDA] Node type "${nodeTypeFor(kind)}" not available in this ComfyUI install.`);
|
|
return null;
|
|
}
|
|
node.pos = pos;
|
|
app.graph.add(node);
|
|
const widget = node.widgets?.find((w) => w.name === widgetNameFor(kind));
|
|
if (widget) widget.value = relative;
|
|
return node;
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: "ITDA.Bridge",
|
|
// setup() fires before app.graph exists ("ComfyApp graph accessed before
|
|
// initialization"); afterConfigureGraph fires once the graph is loaded and
|
|
// ready, which is what every other node-inserting extension here uses.
|
|
afterConfigureGraph() {
|
|
if (_consumed || (!_pendingSend && !_pendingBatch)) return;
|
|
_consumed = true;
|
|
|
|
if (_pendingBatch && Array.isArray(_pendingBatch)) {
|
|
_pendingBatch.forEach((item, i) => {
|
|
if (!item?.relative || !item?.kind) return;
|
|
dropLoaderNode(item.relative, item.kind, [200, 120 + i * 220]);
|
|
});
|
|
} else if (_pendingSend) {
|
|
dropLoaderNode(_pendingSend, _pendingKind, [200, 200]);
|
|
}
|
|
app.graph.setDirtyCanvas(true, true);
|
|
},
|
|
});
|