Skip to content

Commit ee6a7c2

Browse files
committed
0.4.3: preview de video + boton de carga (mp4/webp/gif) en Load Video
1 parent 894f0ac commit ee6a7c2

3 files changed

Lines changed: 98 additions & 2 deletions

File tree

__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,6 @@
3535
else: # pragma: no cover - solo en import standalone (tests/herramientas)
3636
NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS = {}, {}
3737

38-
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
38+
# Frontend: preview + carga de vídeo para BerniniRLoadVideo (web/berninir.js).
39+
WEB_DIRECTORY = "./web"
40+
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "comfyui-berninir"
33
description = "ByteDance Bernini-R (Wan2.2-T2V-A14B + source-id RoPE + APG multi-condition guidance) for ComfyUI: t2v/t2i, image & video editing, reference-to-video."
4-
version = "0.4.2"
4+
version = "0.4.3"
55
license = { file = "LICENSE" }
66
requires-python = ">=3.10"
77
dependencies = [

web/berninir.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// ComfyUI-BerniniR — preview + carga de vídeo para "BerniniR · Load Video".
2+
// Añade un botón que acepta mp4/mov/avi/mkv/webm/webp/gif (el uploader de
3+
// imágenes de ComfyUI no deja elegir vídeo) y un preview para ver que cargó.
4+
import { app } from "../../scripts/app.js";
5+
import { api } from "../../scripts/api.js";
6+
7+
const ANIM = ["webp", "gif"];
8+
9+
function viewURL(name) {
10+
return api.apiURL(`/view?filename=${encodeURIComponent(name)}&type=input&subfolder=`);
11+
}
12+
13+
app.registerExtension({
14+
name: "BerniniR.LoadVideoPreview",
15+
async beforeRegisterNodeDef(nodeType, nodeData) {
16+
if (nodeData?.name !== "BerniniRLoadVideo") return;
17+
18+
const onCreated = nodeType.prototype.onNodeCreated;
19+
nodeType.prototype.onNodeCreated = function () {
20+
const ret = onCreated?.apply(this, arguments);
21+
const node = this;
22+
23+
// --- preview ---
24+
const wrap = document.createElement("div");
25+
wrap.style.cssText = "width:100%;display:flex;justify-content:center;align-items:center;";
26+
const video = document.createElement("video");
27+
video.controls = true; video.loop = true; video.muted = true; video.playsInline = true;
28+
video.style.cssText = "max-width:100%;max-height:240px;border-radius:8px;display:none;";
29+
const img = document.createElement("img");
30+
img.style.cssText = "max-width:100%;max-height:240px;border-radius:8px;display:none;";
31+
wrap.appendChild(video); wrap.appendChild(img);
32+
node.addDOMWidget("br_preview", "preview", wrap, { serialize: false });
33+
34+
const show = (name) => {
35+
const empty = !name || name === "source.webp";
36+
const ext = empty ? "" : (name.split(".").pop() || "").toLowerCase();
37+
if (empty) {
38+
video.style.display = "none"; img.style.display = "none"; video.removeAttribute("src");
39+
} else if (ANIM.includes(ext)) {
40+
img.src = viewURL(name); img.style.display = "block";
41+
video.style.display = "none"; video.removeAttribute("src");
42+
} else {
43+
video.src = viewURL(name); video.style.display = "block";
44+
img.style.display = "none"; video.load();
45+
}
46+
node.setDirtyCanvas?.(true, true);
47+
};
48+
49+
const combo = node.widgets?.find((w) => w.name === "video");
50+
if (combo) {
51+
const prev = combo.callback;
52+
combo.callback = function () {
53+
const r = prev?.apply(this, arguments);
54+
show(combo.value);
55+
return r;
56+
};
57+
setTimeout(() => show(combo.value), 100);
58+
}
59+
60+
// --- botón de carga (acepta vídeo, no solo imagen) ---
61+
const picker = document.createElement("input");
62+
picker.type = "file";
63+
picker.accept = "video/*,image/webp,image/gif";
64+
picker.style.display = "none";
65+
document.body.appendChild(picker);
66+
picker.onchange = async () => {
67+
const file = picker.files?.[0];
68+
picker.value = "";
69+
if (!file) return;
70+
const body = new FormData();
71+
body.append("image", file);
72+
body.append("type", "input");
73+
body.append("subfolder", "");
74+
try {
75+
const resp = await api.fetchApi("/upload/image", { method: "POST", body });
76+
if (resp.status !== 200) throw new Error(await resp.text());
77+
const data = await resp.json();
78+
const name = data.subfolder ? `${data.subfolder}/${data.name}` : data.name;
79+
if (combo) {
80+
const vals = combo.options?.values;
81+
if (Array.isArray(vals) && !vals.includes(name)) vals.push(name);
82+
combo.value = name;
83+
combo.callback?.(name);
84+
}
85+
} catch (e) {
86+
alert("BerniniR: fallo al cargar el vídeo -> " + e);
87+
}
88+
};
89+
node.addWidget("button", "📹 Cargar vídeo (mp4 / webp / gif)", null, () => picker.click());
90+
91+
return ret;
92+
};
93+
},
94+
});

0 commit comments

Comments
 (0)