Prepare ComfyUI-VideoSlice for GitHub and RunPod (ib video slicer).
Made-with: Cursor
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# OS / editor
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Packaging / test
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
.pytest_cache/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 ComfyUI-VideoSlice authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,156 @@
|
||||
# ComfyUI-VideoSlice — **ib video slicer**
|
||||
|
||||
Custom ComfyUI node that reads `.mov` / `.mp4` and outputs **one frame** as an **IMAGE** (plus sizes, counters, and a text readout).
|
||||
|
||||
---
|
||||
|
||||
## Install from GitHub (any machine)
|
||||
|
||||
1. Clone into ComfyUI’s `custom_nodes` folder (name must stay `ComfyUI-VideoSlice` or Comfy will still load it if the folder contains `__init__.py`):
|
||||
|
||||
```bash
|
||||
cd /path/to/ComfyUI/custom_nodes
|
||||
git clone https://github.com/YOUR_USERNAME/ComfyUI-VideoSlice.git
|
||||
```
|
||||
|
||||
2. Install **opencv-python** with the **same Python / pip** that runs ComfyUI:
|
||||
|
||||
```bash
|
||||
cd ComfyUI-VideoSlice
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Or run the helper (tries common RunPod venv paths; see [RunPod](#runpod-comfyui) below):
|
||||
|
||||
```bash
|
||||
bash install_deps.sh
|
||||
```
|
||||
|
||||
3. **Restart ComfyUI** (full process restart, not only refresh).
|
||||
|
||||
---
|
||||
|
||||
## Publish this repo on GitHub
|
||||
|
||||
From the folder that contains this README:
|
||||
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial commit: ib video slicer custom node"
|
||||
```
|
||||
|
||||
On [github.com/new](https://github.com/new): create a repository named **`ComfyUI-VideoSlice`** (no template). Then:
|
||||
|
||||
```bash
|
||||
git branch -M main
|
||||
git remote add origin https://github.com/YOUR_USERNAME/ComfyUI-VideoSlice.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
Replace **`YOUR_USERNAME`** in the clone URL in this README when you share the project.
|
||||
|
||||
---
|
||||
|
||||
## RunPod ComfyUI
|
||||
|
||||
RunPod templates differ slightly; the idea is always: **node code in `custom_nodes/`**, **deps in Comfy’s Python**, **videos in `ComfyUI/input/`** or **`path_override`**.
|
||||
|
||||
### A. One-time setup on the pod
|
||||
|
||||
1. Open **Terminal** (or SSH) on the pod.
|
||||
2. Find ComfyUI root (common cases):
|
||||
|
||||
```bash
|
||||
ls /workspace/ComfyUI/custom_nodes 2>/dev/null || ls /workspace/comfyui/custom_nodes 2>/dev/null
|
||||
```
|
||||
|
||||
3. Clone **this repo** into `custom_nodes`:
|
||||
|
||||
```bash
|
||||
cd /workspace/ComfyUI/custom_nodes # adjust if your template uses another path
|
||||
git clone https://github.com/YOUR_USERNAME/ComfyUI-VideoSlice.git
|
||||
```
|
||||
|
||||
4. Install dependencies:
|
||||
|
||||
```bash
|
||||
cd ComfyUI-VideoSlice
|
||||
chmod +x install_deps.sh
|
||||
bash install_deps.sh
|
||||
```
|
||||
|
||||
If the script cannot find pip, locate Comfy’s venv and set **`COMFYUI_PIP`**:
|
||||
|
||||
```bash
|
||||
export COMFYUI_PIP=/workspace/ComfyUI/venv/bin/pip # example
|
||||
bash install_deps.sh
|
||||
```
|
||||
|
||||
5. **Restart** the ComfyUI service / pod (per your template’s docs).
|
||||
|
||||
### B. Persistence
|
||||
|
||||
- Ephemeral disks: reinstall or clone into a **RunPod volume** mounted at e.g. **`/workspace`** so `custom_nodes` survives stop/start.
|
||||
- Put large videos on the same volume; reference them with **`path_override`** or copy into **`ComfyUI/input/`**.
|
||||
|
||||
### C. Sharing with teammates
|
||||
|
||||
They use the **same `git clone` URL** into their pod’s `custom_nodes`, run **`install_deps.sh`** (or `pip install -r requirements.txt`), restart Comfy. No drag-and-drop install for the node itself—only media goes through the Comfy **Input** UI.
|
||||
|
||||
## Node: **ib video slicer**
|
||||
|
||||
**Category:** `video`
|
||||
**Type IDs:** `IBVideoSlicer` (new) or `VideoSliceFrame` (legacy saves — same node).
|
||||
|
||||
### Select / upload video (no separate button)
|
||||
|
||||
ComfyUI custom nodes cannot add a real “Upload” button in the graph. Use this workflow:
|
||||
|
||||
1. Open the **Input** sidebar in ComfyUI (or put files in `ComfyUI/input/` on the server).
|
||||
2. **Drag-and-drop** your `.mp4` / `.mov` there (same as “upload”).
|
||||
3. In the node, choose the file from the **video** dropdown.
|
||||
4. Optional: **path_override** = full server path (e.g. RunPod `/workspace/...`) to bypass the list.
|
||||
|
||||
After adding files, reload the page or run once; **IS_CHANGED** uses the input folder fingerprint so new files are noticed.
|
||||
|
||||
### Inputs
|
||||
|
||||
| Input | Description |
|
||||
|--------|-------------|
|
||||
| **video** | Dropdown of videos under ComfyUI `input/` (when any exist). |
|
||||
| **path_override** | Full path overrides the dropdown. |
|
||||
| **video_path** | Used when no videos are in `input/` (remote / empty input). |
|
||||
| **random_frame** | On = pick a random frame from the slice list (ignores **current_frame_index** for selection). |
|
||||
| **random_seed** | If random is on: **0** = different frame each run; **>0** = stable random for that seed. |
|
||||
| **loop_on** | On = wrap **current_frame_index** with modulo (see **loop_every_n_frames**). |
|
||||
| **loop_every_n_frames** | With loop on: **0** = wrap over the **full** slice list; **N > 0** = wrap only over the **first N** entries in that list. |
|
||||
| **start_frame** / **end_frame** | Range in the file ( **end_frame** = **-1** = last frame ). |
|
||||
| **skip_first_n_frames** | Added on top of **start_frame** before building the slice list. |
|
||||
| **skip_every_n_frames** | **1** = every frame in range; **2** = every 2nd; **3** = every 3rd, … |
|
||||
| **current_frame_index** | 0-based index into the slice list (ignored when random is on). |
|
||||
|
||||
### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| **image** | IMAGE | Selected frame (batch 1). |
|
||||
| **image_width** / **image_height** | INT | Pixel size. |
|
||||
| **slice_index** | INT | 0-based index into the slice list for this run. |
|
||||
| **video_frame** | INT | Actual frame index inside the video file. |
|
||||
| **frame_readout** | STRING | Human-readable status, e.g. `slice 3/120 | video frame 42 | sequential loop`. |
|
||||
|
||||
### Selection order
|
||||
|
||||
1. Build indices from **start** + **skip_first_n_frames** through **end**, stepping by **skip_every_n_frames**.
|
||||
2. If **random_frame** → random **slice_index** (seeded if **random_seed** > 0).
|
||||
3. Else if **loop_on** → `current_frame_index % window` (window from **loop_every_n_frames** as above).
|
||||
4. Else → clamp **current_frame_index** to `[0, len(indices)-1]`.
|
||||
|
||||
### Migrating old workflows
|
||||
|
||||
Older graphs used **every_nth_frame**; the widget is now **skip_every_n_frames** (same meaning: 1 = all, 2 = every second, …). Re-open the node and set **skip_every_n_frames** to match the old value if you load an old JSON.
|
||||
|
||||
## Supported formats
|
||||
|
||||
- `.mp4`, `.mov`
|
||||
@@ -0,0 +1,3 @@
|
||||
from .video_slice import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install OpenCV into the same Python environment ComfyUI uses (local or RunPod).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REQ="${ROOT}/requirements.txt"
|
||||
|
||||
run_pip() {
|
||||
local pip_exe="$1"
|
||||
echo "Using: ${pip_exe}"
|
||||
"${pip_exe}" install -r "${REQ}"
|
||||
}
|
||||
|
||||
# 1) Explicit override (recommended on RunPod if auto-detect fails)
|
||||
if [[ -n "${COMFYUI_PIP:-}" && -x "${COMFYUI_PIP}" ]]; then
|
||||
run_pip "${COMFYUI_PIP}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 2) Common RunPod / cloud ComfyUI venv locations (try first match)
|
||||
for candidate in \
|
||||
"/workspace/ComfyUI/venv/bin/pip" \
|
||||
"/workspace/comfyui/venv/bin/pip" \
|
||||
"/opt/ComfyUI/venv/bin/pip" \
|
||||
"${HOME}/ComfyUI/venv/bin/pip"
|
||||
do
|
||||
if [[ -x "${candidate}" ]]; then
|
||||
run_pip "${candidate}"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
# 3) Fallback: whatever `pip` is on PATH (works if you activated Comfy's venv first)
|
||||
if command -v pip >/dev/null 2>&1; then
|
||||
run_pip "$(command -v pip)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "No pip found. On RunPod: open a terminal, find Comfy's venv pip, then run:" >&2
|
||||
echo " export COMFYUI_PIP=/path/to/ComfyUI/venv/bin/pip" >&2
|
||||
echo " bash ${ROOT}/install_deps.sh" >&2
|
||||
exit 1
|
||||
@@ -0,0 +1 @@
|
||||
opencv-python>=4.8.0
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
"""
|
||||
ComfyUI custom node: slice .mov / .mp4 and output one frame as IMAGE.
|
||||
Upload videos via ComfyUI's Input sidebar (drag-drop into input/), then pick from the list or set path override.
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
try:
|
||||
import cv2
|
||||
HAS_CV2 = True
|
||||
except ImportError:
|
||||
HAS_CV2 = False
|
||||
|
||||
try:
|
||||
import folder_paths
|
||||
HAS_FOLDER_PATHS = True
|
||||
except ImportError:
|
||||
HAS_FOLDER_PATHS = False
|
||||
|
||||
VIDEO_EXTENSIONS = (".mp4", ".mov")
|
||||
|
||||
|
||||
def _get_video_list_from_input():
|
||||
if not HAS_FOLDER_PATHS:
|
||||
return []
|
||||
try:
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
if not os.path.isdir(input_dir):
|
||||
return []
|
||||
out = []
|
||||
for root, _dirs, files in os.walk(input_dir, followlinks=True):
|
||||
rel = os.path.relpath(root, input_dir)
|
||||
for f in files:
|
||||
if os.path.splitext(f)[1].lower() in VIDEO_EXTENSIONS:
|
||||
if rel == ".":
|
||||
out.append(f)
|
||||
else:
|
||||
out.append(os.path.join(rel, f).replace("\\", "/"))
|
||||
return sorted(out)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _input_dir_fingerprint():
|
||||
"""Rough fingerprint of input folder so IS_CHANGED picks up new uploads."""
|
||||
if not HAS_FOLDER_PATHS:
|
||||
return 0
|
||||
try:
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
if not os.path.isdir(input_dir):
|
||||
return 0
|
||||
mtimes = []
|
||||
for root, _dirs, files in os.walk(input_dir, followlinks=True):
|
||||
for f in files:
|
||||
if os.path.splitext(f)[1].lower() in VIDEO_EXTENSIONS:
|
||||
p = os.path.join(root, f)
|
||||
try:
|
||||
mtimes.append(os.path.getmtime(p))
|
||||
except OSError:
|
||||
pass
|
||||
return (len(mtimes), round(max(mtimes, default=0), 3))
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
def _resolve_video_path(**kwargs):
|
||||
path_override = (kwargs.get("path_override") or "").strip()
|
||||
if path_override and os.path.isfile(path_override):
|
||||
return path_override
|
||||
if "video" in kwargs:
|
||||
video_name = kwargs["video"]
|
||||
if HAS_FOLDER_PATHS and video_name:
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
return os.path.join(input_dir, video_name)
|
||||
return ""
|
||||
return (kwargs.get("video_path") or "").strip()
|
||||
|
||||
|
||||
class IBVideoSlicer:
|
||||
"""
|
||||
Select a video from ComfyUI input (upload via Input tab) or path, output one frame.
|
||||
Optional random pick, loop / loop window, and readable frame counter outputs.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
video_choices = _get_video_list_from_input()
|
||||
common = {
|
||||
"random_frame": ("BOOLEAN", {"default": False}),
|
||||
"random_seed": (
|
||||
"INT",
|
||||
{
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 2**31 - 1,
|
||||
"step": 1,
|
||||
"display": "number",
|
||||
"tooltip": "When random is on: 0 = different frame each run; >0 = fixed pick for that seed.",
|
||||
},
|
||||
),
|
||||
"loop_on": ("BOOLEAN", {"default": False}),
|
||||
"loop_every_n_frames": (
|
||||
"INT",
|
||||
{
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 2**31 - 1,
|
||||
"step": 1,
|
||||
"display": "number",
|
||||
"tooltip": "When loop is on: 0 = wrap over full clip; N>0 = wrap over first N slice indices only.",
|
||||
},
|
||||
),
|
||||
"start_frame": ("INT", {"default": 0, "min": 0, "max": 2**31 - 1, "step": 1, "display": "number"}),
|
||||
"end_frame": ("INT", {"default": -1, "min": -1, "max": 2**31 - 1, "step": 1, "display": "number"}),
|
||||
"skip_first_n_frames": ("INT", {"default": 0, "min": 0, "max": 2**31 - 1, "step": 1, "display": "number"}),
|
||||
"skip_every_n_frames": (
|
||||
"INT",
|
||||
{
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 10000,
|
||||
"step": 1,
|
||||
"display": "number",
|
||||
"tooltip": "1 = use every frame in range; 2 = every 2nd; 3 = every 3rd, etc.",
|
||||
},
|
||||
),
|
||||
"current_frame_index": (
|
||||
"INT",
|
||||
{
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 2**31 - 1,
|
||||
"step": 1,
|
||||
"display": "number",
|
||||
"tooltip": "Index into the slice list (after skip/every-nth). Ignored when random is on.",
|
||||
},
|
||||
),
|
||||
}
|
||||
if HAS_FOLDER_PATHS and video_choices:
|
||||
return {
|
||||
"required": {
|
||||
"video": (
|
||||
video_choices,
|
||||
{
|
||||
"default": video_choices[0],
|
||||
"tooltip": "Files from ComfyUI input/. Upload: sidebar → Input → drag .mp4 / .mov here, then refresh if needed.",
|
||||
},
|
||||
),
|
||||
**common,
|
||||
},
|
||||
"optional": {
|
||||
"path_override": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"multiline": False,
|
||||
"placeholder": "Full path overrides dropdown (server path, e.g. RunPod /workspace/...)",
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
return {
|
||||
"required": {
|
||||
"video_path": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"multiline": False,
|
||||
"placeholder": "Full path to .mp4 or .mov (or upload to input/ and restart to get dropdown)",
|
||||
},
|
||||
),
|
||||
**common,
|
||||
},
|
||||
"optional": {},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "INT", "INT", "INT", "INT", "STRING")
|
||||
RETURN_NAMES = ("image", "image_width", "image_height", "slice_index", "video_frame", "frame_readout")
|
||||
FUNCTION = "slice_frame"
|
||||
CATEGORY = "video"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(
|
||||
cls,
|
||||
random_frame,
|
||||
random_seed,
|
||||
loop_on,
|
||||
loop_every_n_frames,
|
||||
start_frame,
|
||||
end_frame,
|
||||
skip_first_n_frames,
|
||||
skip_every_n_frames,
|
||||
current_frame_index,
|
||||
**kwargs,
|
||||
):
|
||||
path = _resolve_video_path(**kwargs)
|
||||
fp = _input_dir_fingerprint()
|
||||
base = (path, fp, start_frame, end_frame, skip_first_n_frames, skip_every_n_frames, loop_on, loop_every_n_frames)
|
||||
if random_frame:
|
||||
if random_seed != 0:
|
||||
return base + ("rand", random_seed)
|
||||
return base + ("rand", time.time_ns())
|
||||
return base + ("seq", current_frame_index)
|
||||
|
||||
def slice_frame(
|
||||
self,
|
||||
random_frame,
|
||||
random_seed,
|
||||
loop_on,
|
||||
loop_every_n_frames,
|
||||
start_frame,
|
||||
end_frame,
|
||||
skip_first_n_frames,
|
||||
skip_every_n_frames,
|
||||
current_frame_index,
|
||||
**kwargs,
|
||||
):
|
||||
if not HAS_CV2:
|
||||
raise ModuleNotFoundError(
|
||||
"OpenCV (opencv-python) is required. Install: pip install opencv-python"
|
||||
)
|
||||
# Backward compat: old workflows may pass every_nth_frame
|
||||
if "every_nth_frame" in kwargs and kwargs["every_nth_frame"] is not None:
|
||||
skip_every_n_frames = int(kwargs["every_nth_frame"])
|
||||
path = _resolve_video_path(**kwargs)
|
||||
if not path or not os.path.isfile(path):
|
||||
raise FileNotFoundError(f"Video file not found: {path!r}")
|
||||
|
||||
lower = path.lower()
|
||||
if not (lower.endswith(".mp4") or lower.endswith(".mov")):
|
||||
raise ValueError("Only .mp4 and .mov are supported.")
|
||||
|
||||
cap = cv2.VideoCapture(path)
|
||||
if not cap.isOpened():
|
||||
raise RuntimeError(f"Could not open video: {path}")
|
||||
|
||||
try:
|
||||
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
if total_frames <= 0:
|
||||
raise RuntimeError("Video has no frames or frame count unknown.")
|
||||
|
||||
effective_start = start_frame + skip_first_n_frames
|
||||
effective_end = end_frame if end_frame >= 0 else (total_frames - 1)
|
||||
effective_end = min(effective_end, total_frames - 1)
|
||||
|
||||
if effective_start > effective_end:
|
||||
raise ValueError(
|
||||
f"Effective start frame ({effective_start}) is after effective end ({effective_end})."
|
||||
)
|
||||
|
||||
indices = []
|
||||
i = effective_start
|
||||
while i <= effective_end:
|
||||
indices.append(i)
|
||||
i += skip_every_n_frames
|
||||
|
||||
if not indices:
|
||||
raise ValueError("No frames in selected range with given skip_every_n_frames.")
|
||||
|
||||
n = len(indices)
|
||||
|
||||
if random_frame:
|
||||
if random_seed > 0:
|
||||
rng = random.Random(random_seed)
|
||||
idx = rng.randint(0, n - 1)
|
||||
else:
|
||||
idx = random.randint(0, n - 1)
|
||||
elif loop_on:
|
||||
window = loop_every_n_frames if loop_every_n_frames > 0 else n
|
||||
window = max(1, min(window, n))
|
||||
idx = current_frame_index % window
|
||||
else:
|
||||
idx = min(max(0, current_frame_index), n - 1)
|
||||
|
||||
video_frame_number = indices[idx]
|
||||
|
||||
cap.set(cv2.CAP_PROP_POS_FRAMES, video_frame_number)
|
||||
ret, frame_bgr = cap.read()
|
||||
if not ret or frame_bgr is None:
|
||||
raise RuntimeError(f"Failed to read frame {video_frame_number} from {path}.")
|
||||
|
||||
frame_rgb = frame_bgr[:, :, ::-1].copy()
|
||||
img_float = np.array(frame_rgb, dtype=np.float32) / 255.0
|
||||
image = torch.from_numpy(img_float)[None, ...]
|
||||
|
||||
height, width = image.shape[1], image.shape[2]
|
||||
readout = (
|
||||
f"slice {idx + 1}/{n} | video frame {video_frame_number} | "
|
||||
f"{'random' if random_frame else 'sequential'}{'' if random_frame else (' loop' if loop_on else '')}"
|
||||
)
|
||||
return (image, width, height, idx, video_frame_number, readout)
|
||||
|
||||
finally:
|
||||
cap.release()
|
||||
|
||||
|
||||
# Legacy class name: same implementation (saved workflows may reference either)
|
||||
VideoSliceFrame = IBVideoSlicer
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"IBVideoSlicer": IBVideoSlicer,
|
||||
"VideoSliceFrame": IBVideoSlicer,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"IBVideoSlicer": "ib video slicer",
|
||||
"VideoSliceFrame": "ib video slicer",
|
||||
}
|
||||
Reference in New Issue
Block a user