Replace Long Videos with upstream sampler
This commit is contained in:
+5
-371
@@ -1,375 +1,9 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
import { applyAdaptiveCanvasOnly } from "../shared/nodes2.mjs";
|
||||
|
||||
const COMFY_CLASS = "DumasH3LongVideos";
|
||||
const STATE_PROPERTY = "dumas_h3_longvideos_section_state";
|
||||
const DOM_WIDGET_NAME = "dumas_h3_longvideos_sections";
|
||||
const MIN_WIDTH = 520;
|
||||
const MIN_HEIGHT = 280;
|
||||
|
||||
const GROUPS = [
|
||||
{
|
||||
id: "prompt",
|
||||
label: "Prompt",
|
||||
defaultCollapsed: false,
|
||||
widgets: ["prompt", "resolution", "megapixels", "beat_split", "anchor_override", "shot_seconds", "plan_only", "fps"],
|
||||
},
|
||||
{
|
||||
id: "refs",
|
||||
label: "Refs",
|
||||
defaultCollapsed: true,
|
||||
widgets: ["ref_mode", "ref_image_size", "ref_noise_aug", "character_memory", "trim_seam", "vary_seed_per_shot", "handoff_offset"],
|
||||
},
|
||||
{
|
||||
id: "sampling",
|
||||
label: "Sampling",
|
||||
defaultCollapsed: true,
|
||||
widgets: [
|
||||
"steps", "cfg", "sampler_name", "scheduler", "seed",
|
||||
"apply_model_sampling", "shift_video", "shift_audio",
|
||||
"vram_headroom_gb", "allow_res_backoff",
|
||||
"decode_tile_frames", "decode_tile_size",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "audio",
|
||||
label: "Audio",
|
||||
defaultCollapsed: true,
|
||||
widgets: [
|
||||
"global_soundscape", "non_diegetic_music", "auto_soundscape",
|
||||
"auto_silence_nonspeech", "allow_nonspeech_vocals",
|
||||
"mute_nonspeech_audio", "mute_fade_ms",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "scene",
|
||||
label: "Scene Logic",
|
||||
defaultCollapsed: true,
|
||||
widgets: [
|
||||
"auto_wardrobe", "auto_props", "prevent_nudity", "exposed_terms",
|
||||
"anatomy_guard", "subject_count_guard", "lock_restraints",
|
||||
"contact_guard", "motion_guard", "solidity_guard",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "finish",
|
||||
label: "Upscale",
|
||||
defaultCollapsed: true,
|
||||
widgets: [
|
||||
"upscale", "upscale_model", "upscale_target_short_edge", "upscale_batch",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "overlay",
|
||||
label: "Overlays",
|
||||
defaultCollapsed: true,
|
||||
widgets: [
|
||||
"watermark_text", "watermark_position", "watermark_size", "watermark_opacity", "watermark_margin",
|
||||
"intro_text", "intro_position", "intro_seconds", "intro_fade", "intro_size",
|
||||
"overlay_font", "overlay_stroke",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function injectCSS() {
|
||||
if (document.getElementById("dumas-h3lv-sections-css")) return;
|
||||
const style = document.createElement("style");
|
||||
style.id = "dumas-h3lv-sections-css";
|
||||
style.textContent = `
|
||||
.dh3lv-sections {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 8px 10px 6px;
|
||||
color: #e6e7eb;
|
||||
font: 12px/1.35 "Segoe UI", sans-serif;
|
||||
pointer-events: auto;
|
||||
background: linear-gradient(180deg, rgba(33, 36, 42, 0.96), rgba(22, 24, 29, 0.96));
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.dh3lv-sections-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.dh3lv-sections-title {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #9da5b1;
|
||||
}
|
||||
.dh3lv-sections-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
.dh3lv-sections-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.dh3lv-chip,
|
||||
.dh3lv-action {
|
||||
appearance: none;
|
||||
border: 1px solid #464d59;
|
||||
background: #262c35;
|
||||
color: #d7dce3;
|
||||
border-radius: 999px;
|
||||
padding: 5px 9px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.dh3lv-chip[data-open="true"] {
|
||||
background: #d96f2b;
|
||||
border-color: #f09358;
|
||||
color: #fff7f0;
|
||||
}
|
||||
.dh3lv-chip:hover,
|
||||
.dh3lv-action:hover {
|
||||
filter: brightness(1.06);
|
||||
}
|
||||
.dh3lv-count {
|
||||
opacity: 0.78;
|
||||
margin-left: 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function defaultState() {
|
||||
const state = {};
|
||||
for (const group of GROUPS) state[group.id] = !group.defaultCollapsed;
|
||||
return state;
|
||||
}
|
||||
|
||||
function parseState(value) {
|
||||
let parsed = value;
|
||||
if (typeof parsed === "string") {
|
||||
try {
|
||||
parsed = JSON.parse(parsed);
|
||||
} catch (_error) {
|
||||
parsed = null;
|
||||
}
|
||||
}
|
||||
const base = defaultState();
|
||||
if (!parsed || typeof parsed !== "object") return base;
|
||||
for (const group of GROUPS) {
|
||||
if (typeof parsed[group.id] === "boolean") base[group.id] = parsed[group.id];
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function readState(node) {
|
||||
return parseState(node.properties?.[STATE_PROPERTY] || node._dh3lvSectionState || "");
|
||||
}
|
||||
|
||||
function writeState(node, state) {
|
||||
const normalized = parseState(state);
|
||||
const serialized = JSON.stringify(normalized);
|
||||
node._dh3lvSectionState = serialized;
|
||||
node.properties = node.properties || {};
|
||||
node.properties[STATE_PROPERTY] = serialized;
|
||||
}
|
||||
|
||||
function findWidget(node, name) {
|
||||
return (node.widgets || []).find((widget) => widget?.name === name) || null;
|
||||
}
|
||||
|
||||
function isInteractiveTarget(target) {
|
||||
return !!target?.closest?.("button, input, textarea, select, label");
|
||||
}
|
||||
|
||||
function stopCanvasEvent(event) {
|
||||
if (isInteractiveTarget(event.target)) event.stopPropagation();
|
||||
}
|
||||
|
||||
function stopCanvasKeyboard(event) {
|
||||
if (isInteractiveTarget(event.target)) event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
function setWidgetHidden(widget, hidden) {
|
||||
if (!widget) return;
|
||||
if (!widget._dh3lvOriginal) {
|
||||
widget._dh3lvOriginal = {
|
||||
type: widget.type,
|
||||
computeSize: widget.computeSize,
|
||||
hidden: widget.hidden,
|
||||
};
|
||||
}
|
||||
if (hidden) {
|
||||
widget.type = "hidden";
|
||||
widget.hidden = true;
|
||||
widget.computeSize = () => [0, -4];
|
||||
return;
|
||||
}
|
||||
widget.type = widget._dh3lvOriginal.type;
|
||||
widget.hidden = !!widget._dh3lvOriginal.hidden;
|
||||
widget.computeSize = widget._dh3lvOriginal.computeSize;
|
||||
}
|
||||
|
||||
function applyVisibility(node) {
|
||||
const state = readState(node);
|
||||
for (const group of GROUPS) {
|
||||
for (const name of group.widgets) {
|
||||
const widget = findWidget(node, name);
|
||||
if (!widget || widget.name === DOM_WIDGET_NAME) continue;
|
||||
setWidgetHidden(widget, !state[group.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resizeNode(node) {
|
||||
requestAnimationFrame(() => {
|
||||
const size = node.computeSize?.();
|
||||
if (Array.isArray(size)) {
|
||||
node.size[0] = Math.max(MIN_WIDTH, size[0] || 0, node.size?.[0] || 0);
|
||||
node.size[1] = Math.max(MIN_HEIGHT, size[1] || 0);
|
||||
}
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
});
|
||||
}
|
||||
|
||||
function renderToolbar(node) {
|
||||
const ui = node._dh3lvUI;
|
||||
if (!ui) return;
|
||||
const state = readState(node);
|
||||
ui.list.innerHTML = "";
|
||||
for (const group of GROUPS) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "dh3lv-chip";
|
||||
button.dataset.open = state[group.id] ? "true" : "false";
|
||||
button.textContent = state[group.id] ? `Hide ${group.label}` : `Show ${group.label}`;
|
||||
|
||||
const count = document.createElement("span");
|
||||
count.className = "dh3lv-count";
|
||||
count.textContent = String(group.widgets.filter((name) => findWidget(node, name)).length);
|
||||
button.appendChild(count);
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
const next = readState(node);
|
||||
next[group.id] = !next[group.id];
|
||||
writeState(node, next);
|
||||
applyVisibility(node);
|
||||
renderToolbar(node);
|
||||
resizeNode(node);
|
||||
});
|
||||
ui.list.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
function setAll(node, open) {
|
||||
const next = {};
|
||||
for (const group of GROUPS) next[group.id] = !!open;
|
||||
writeState(node, next);
|
||||
applyVisibility(node);
|
||||
renderToolbar(node);
|
||||
resizeNode(node);
|
||||
}
|
||||
|
||||
function setupNode(node) {
|
||||
if (node._dh3lvUI) return;
|
||||
injectCSS();
|
||||
writeState(node, readState(node));
|
||||
|
||||
const root = document.createElement("div");
|
||||
root.className = "dh3lv-sections";
|
||||
|
||||
const head = document.createElement("div");
|
||||
head.className = "dh3lv-sections-head";
|
||||
|
||||
const title = document.createElement("div");
|
||||
title.className = "dh3lv-sections-title";
|
||||
title.textContent = "Sections";
|
||||
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "dh3lv-sections-actions";
|
||||
|
||||
const expandAll = document.createElement("button");
|
||||
expandAll.type = "button";
|
||||
expandAll.className = "dh3lv-action";
|
||||
expandAll.textContent = "Expand All";
|
||||
expandAll.addEventListener("click", () => setAll(node, true));
|
||||
|
||||
const collapseAll = document.createElement("button");
|
||||
collapseAll.type = "button";
|
||||
collapseAll.className = "dh3lv-action";
|
||||
collapseAll.textContent = "Collapse Extras";
|
||||
collapseAll.addEventListener("click", () => {
|
||||
const next = defaultState();
|
||||
writeState(node, next);
|
||||
applyVisibility(node);
|
||||
renderToolbar(node);
|
||||
resizeNode(node);
|
||||
});
|
||||
|
||||
actions.append(expandAll, collapseAll);
|
||||
head.append(title, actions);
|
||||
|
||||
const list = document.createElement("div");
|
||||
list.className = "dh3lv-sections-list";
|
||||
root.append(head, list);
|
||||
root.addEventListener("pointerdown", stopCanvasEvent);
|
||||
root.addEventListener("mousedown", stopCanvasEvent);
|
||||
root.addEventListener("click", stopCanvasEvent);
|
||||
root.addEventListener("dblclick", stopCanvasEvent);
|
||||
root.addEventListener("keydown", stopCanvasKeyboard, true);
|
||||
|
||||
node._dh3lvUI = { root, list };
|
||||
const widget = node.addDOMWidget(DOM_WIDGET_NAME, "custom", root, {
|
||||
getValue: () => null,
|
||||
setValue: () => {},
|
||||
serialize: false,
|
||||
getMinHeight: () => 52,
|
||||
hideOnZoom: false,
|
||||
});
|
||||
applyAdaptiveCanvasOnly(widget);
|
||||
const widgets = node.widgets || [];
|
||||
const index = widgets.indexOf(widget);
|
||||
if (index > 0) {
|
||||
widgets.splice(index, 1);
|
||||
widgets.unshift(widget);
|
||||
}
|
||||
|
||||
applyVisibility(node);
|
||||
renderToolbar(node);
|
||||
resizeNode(node);
|
||||
}
|
||||
|
||||
// The Dumas Long Videos node now wraps the upstream MiniMax-H3-Longvideos
|
||||
// sampler directly. The old local frontend grouped Dumas-specific widgets that
|
||||
// no longer exist on the upstream node, so this extension intentionally does
|
||||
// nothing.
|
||||
app.registerExtension({
|
||||
name: "Dumas.H3LongVideosSections",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData?.name !== COMFY_CLASS) return;
|
||||
|
||||
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function onNodeCreated() {
|
||||
const result = originalOnNodeCreated?.apply(this, arguments);
|
||||
setupNode(this);
|
||||
return result;
|
||||
};
|
||||
|
||||
const originalConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function onConfigure() {
|
||||
const result = originalConfigure?.apply(this, arguments);
|
||||
setupNode(this);
|
||||
writeState(this, readState(this));
|
||||
applyVisibility(this);
|
||||
renderToolbar(this);
|
||||
resizeNode(this);
|
||||
return result;
|
||||
};
|
||||
|
||||
const originalSerialize = nodeType.prototype.onSerialize;
|
||||
nodeType.prototype.onSerialize = function onSerialize(o) {
|
||||
writeState(this, readState(this));
|
||||
const result = originalSerialize?.apply(this, arguments);
|
||||
if (o && this.properties?.[STATE_PROPERTY]) {
|
||||
o.properties = o.properties || {};
|
||||
o.properties[STATE_PROPERTY] = this.properties[STATE_PROPERTY];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
},
|
||||
name: "Dumas.H3LongVideos.UpstreamWrapper",
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user