feat: sync latest local version as authoritative codebase

Complete rewrite/sync of comfyui_o1key custom nodes.
Treat this commit as the current canonical version.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
This commit is contained in:
o1key
2026-04-03 16:18:45 +08:00
co-authored by Claude Sonnet 4.5
commit 9ab209b2b7
46 changed files with 16757 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
import { app } from "../../../scripts/app.js";
import { api } from "../../../scripts/api.js";
app.registerExtension({
name: "comfyui_o1key.videoPreview",
async beforeRegisterNodeDef(nodeType, nodeData, _app) {
if (nodeData.name !== "VideoPreview") return;
const origOnExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
if (origOnExecuted) {
origOnExecuted.apply(this, arguments);
}
const videos = message?.videos;
if (!videos || videos.length === 0) return;
const videoInfo = videos[0];
const params = new URLSearchParams();
params.set("filename", videoInfo.filename);
if (videoInfo.subfolder) params.set("subfolder", videoInfo.subfolder);
params.set("type", videoInfo.type || "output");
const videoUrl = api.apiURL(`/view?${params.toString()}`);
// ── 首次创建 DOM 结构 ──────────────────────────────
if (!this._videoContainer) {
this._videoContainer = document.createElement("div");
this._videoContainer.style.cssText =
"width:100%;display:flex;flex-direction:column;align-items:center;" +
"padding:4px;box-sizing:border-box;";
this._videoEl = document.createElement("video");
this._videoEl.controls = true;
this._videoEl.loop = true;
this._videoEl.autoplay = true;
this._videoEl.muted = true;
this._videoEl.playsInline = true;
// 宽度铺满容器,高度由 object-fit 自适应,不限制 max-height
this._videoEl.style.cssText =
"width:100%;display:block;border-radius:4px;" +
"background:#000;object-fit:contain;";
this._videoLabel = document.createElement("div");
this._videoLabel.style.cssText =
"font-size:10px;color:#aaa;margin-top:2px;" +
"text-align:center;word-break:break-all;";
this._videoResLabel = document.createElement("div");
this._videoResLabel.style.cssText =
"font-size:10px;color:#888;margin-top:1px;" +
"text-align:center;";
this._videoContainer.appendChild(this._videoEl);
this._videoContainer.appendChild(this._videoLabel);
this._videoContainer.appendChild(this._videoResLabel);
// ── 视频元数据加载后,根据真实宽高比重新调整节点大小 ──
this._videoEl.addEventListener("loadedmetadata", () => {
const vw = this._videoEl.videoWidth;
const vh = this._videoEl.videoHeight;
if (!vw || !vh) return;
// 存储宽高比(高/宽),供 computeSize 使用
this._videoAspectRatio = vh / vw;
// 显示分辨率
if (this._videoResLabel) {
this._videoResLabel.textContent = `${vw} × ${vh}`;
}
// 用真实比例重新计算节点高度
this._resizeToVideo();
});
}
this._videoEl.src = videoUrl;
this._videoLabel.textContent = videoInfo.filename;
// ── 注册 DOM Widget(仅第一次)──────────────────────
if (!this.widgets?.find((w) => w.name === "video_preview_widget")) {
const self = this;
const widget = this.addDOMWidget(
"video_preview_widget",
"div",
this._videoContainer,
{ serialize: false, hideOnZoom: false }
);
// computeSize 在 LiteGraph 布局时被调用,返回 [宽, 高]
widget.computeSize = function (width) {
const w = width ?? self.size?.[0] ?? 300;
if (self._videoAspectRatio) {
const innerW = Math.max(w - 16, 10); // 减去左右 padding
const videoH = Math.round(innerW * self._videoAspectRatio);
return [w, videoH + 40]; // +40 = 文件名 + 分辨率标签高度
}
// 元数据未就绪时给一个合理默认值
return [w, 260];
};
}
// 初次渲染(元数据尚未加载)给出合理初始尺寸
if (!this._videoAspectRatio) {
const w = Math.max(this.size[0], 320);
const h = Math.max(this.size[1], 300);
this.setSize([w, h]);
}
this.setDirtyCanvas(true, true);
};
// ── 辅助方法:按视频真实比例自适应节点大小 ──────────────
nodeType.prototype._resizeToVideo = function () {
if (!this._videoAspectRatio) return;
const nodeWidth = Math.max(this.size[0], 320);
const innerW = nodeWidth - 16;
const videoH = Math.round(innerW * this._videoAspectRatio);
const labelH = 40; // 文件名 + 分辨率两行
// 节点头部 + 其他 widget 的高度
// LiteGraph 节点头部约 30px,每个普通 widget 约 24px
const NON_VIDEO_WIDGETS = (this.widgets?.filter(
(w) => w.name !== "video_preview"
).length ?? 0);
const headerH = 58 + NON_VIDEO_WIDGETS * 24;
const totalH = headerH + videoH + labelH;
this.setSize([nodeWidth, totalH]);
this.setDirtyCanvas(true, true);
};
// ── 节点手动缩放时同步更新视频高度 ─────────────────────
const origOnResize = nodeType.prototype.onResize;
nodeType.prototype.onResize = function (size) {
if (origOnResize) origOnResize.apply(this, arguments);
if (this._videoAspectRatio && this._videoEl) {
// 用新宽度重新计算正确高度,避免拉伸/压缩
const innerW = Math.max(size[0] - 16, 10);
const videoH = Math.round(innerW * this._videoAspectRatio);
this._videoEl.style.height = videoH + "px";
}
};
},
});