Files
1hew-ComfyUI-1hewNodes/web/js/core/load_video_menu.js
T
1hew f492675b25 v3.10.0 chore(release): 添加 Image Alpha Join 并修复媒体预览
补充 Image Solid 256x256 预设,并修复 Load Image / Load Video / Load Video to Image 的预览显示、交互与旧工作流兼容问题。
2026-04-16 02:55:21 +08:00

121 lines
3.9 KiB
JavaScript

import { addPreviewMenuOptions } from "./preview_menu.js";
function getVideoElementFromNode(node) {
if (!node?.videoWidget?.element) {
return null;
}
const element = node.videoWidget.element;
return element.tagName === "VIDEO" ? element : element.querySelector("video");
}
function getVideoMenuTargets(app, currentNode) {
const canvas = app.canvas;
const selected = canvas.selected_nodes || {};
const selection = Object.values(selected);
if (selection.length > 0 && selection.includes(currentNode)) {
return selection;
}
return [currentNode];
}
function basename(pathValue) {
if (!pathValue) return "";
const normalized = String(pathValue).replace(/\\/g, "/");
const parts = normalized.split("/");
return parts.length ? parts[parts.length - 1] : normalized;
}
function getVideoNodeParams(node) {
const fileWidget = node?.widgets?.find((widget) => widget.name === "file");
const indexWidget = node?.widgets?.find((widget) => widget.name === "video_index");
const includeSubdirWidget = node?.widgets?.find(
(widget) => widget.name === "include_subdir"
);
const file = String(fileWidget?.value || "").trim();
if (!file) {
return null;
}
return {
file,
index: String(indexWidget?.value ?? "0"),
includeSubdir: String(includeSubdirWidget?.value ?? true),
};
}
export function extendLoadVideoMenu({ app, api, node, options }) {
addPreviewMenuOptions(options, {
app,
currentNode: node,
respectFrameAccurateOnShow: true,
});
const videoEl = getVideoElementFromNode(node);
options.push({
content: "Save Video",
callback: () => {
const targets = getVideoMenuTargets(app, node);
for (const targetNode of targets) {
const params = getVideoNodeParams(targetNode);
if (!params) {
continue;
}
const urlParams = new URLSearchParams({
file: params.file,
index: params.index,
include_subdir: params.includeSubdir,
raw: "1",
t: Date.now(),
});
const url = `/1hew/view_video_from_folder?${urlParams.toString()}`;
const suggested =
basename(targetNode?._comfy1hewVideoInfo?.path)
|| `video_${targetNode.id}_${Date.now()}.mp4`;
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = suggested;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
}
},
});
if (!videoEl) {
return;
}
options.push({
content: "Copy Frame",
callback: async () => {
const params = getVideoNodeParams(node);
if (!params) {
return;
}
const urlParams = new URLSearchParams({
file: params.file,
index: params.index,
include_subdir: params.includeSubdir,
t: String(videoEl.currentTime || 0),
r: String(Date.now()),
});
try {
const res = await api.fetchApi(
`/1hew/video_frame_from_folder?${urlParams.toString()}`,
{ cache: "no-store" }
);
if (!res || res.status !== 200) {
return;
}
const blob = await res.blob();
const item = new ClipboardItem({ "image/png": blob });
await navigator.clipboard.write([item]);
} catch (err) {
console.error("Failed to copy frame to clipboard:", err);
}
},
});
}