Merge pull request #14 from ModelTC/gp/update-lightx2v
Gp/update lightx2v
This commit is contained in:
@@ -6,6 +6,7 @@ on:
|
||||
- "v*.*.*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Quality checks
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Ruff and repository checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install pre-commit
|
||||
run: python -m pip install pre-commit
|
||||
|
||||
- name: Run checks
|
||||
run: pre-commit run --all-files
|
||||
+1
-1
@@ -346,4 +346,4 @@ Temporary Items
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
.gitnexus/
|
||||
.claude/
|
||||
.claude/
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Contributing
|
||||
|
||||
## Development setup
|
||||
|
||||
The wrapper follows the Python version supported by the bundled LightX2V
|
||||
submodule (Python 3.10 or newer).
|
||||
|
||||
```bash
|
||||
git clone --recursive https://github.com/ModelTC/ComfyUI-Lightx2vWrapper.git
|
||||
cd ComfyUI-Lightx2vWrapper
|
||||
python -m pip install -r lightx2v/requirements.txt
|
||||
python -m pip install -r requirements.txt pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
## Before opening a pull request
|
||||
|
||||
Run the repository checks from the project root:
|
||||
|
||||
```bash
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
For workflow or runner changes, validate the affected ComfyUI workflow with a
|
||||
local LightX2V installation and include the workflow or reproduction details
|
||||
in the pull request description. Keep changes to the `lightx2v` submodule in a
|
||||
separate upstream contribution unless the wrapper specifically requires a
|
||||
submodule revision update.
|
||||
|
||||
Pull requests should explain the user-visible behavior, list validation
|
||||
performed, and avoid committing model weights, generated media, credentials,
|
||||
or local debugging artifacts.
|
||||
@@ -1,202 +1,97 @@
|
||||
[](https://opensource.org/licenses/MIT) [](https://www.python.org/downloads/) [](https://github.com/comfyanonymous/ComfyUI) [](https://huggingface.co/lightx2v) [](https://deepwiki.com/GacLove/ComfyUI-Lightx2vWrapper)
|
||||
[](LICENSE)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://github.com/comfyanonymous/ComfyUI)
|
||||
[](https://huggingface.co/lightx2v)
|
||||
|
||||
# ComfyUI-Lightx2vWrapper
|
||||
|
||||
[中文版](./README_CN.md) | English
|
||||
ComfyUI custom nodes for [LightX2V](https://github.com/ModelTC/LightX2V). The
|
||||
wrapper exposes LightX2V's modular configuration and runner APIs, together
|
||||
with ComfyUI-friendly file inputs and output previews.
|
||||
|
||||
A ComfyUI custom node wrapper for LightX2V, enabling modular video generation with advanced optimization features.
|
||||
[中文文档](README_CN.md)
|
||||
|
||||
## Features
|
||||
## Supported workflows
|
||||
|
||||
- **Modular Configuration System**: Separate nodes for each aspect of video generation
|
||||
- **Text-to-Video (T2V) and Image-to-Video (I2V)**: Support for both generation modes
|
||||
- **Advanced Optimizations**:
|
||||
- TeaCache acceleration (up to 3x speedup)
|
||||
- Quantization support (int8, fp8)
|
||||
- Memory optimization with CPU offloading
|
||||
- Lightweight VAE options
|
||||
- **LoRA Support**: Chain multiple LoRA models for customization
|
||||
- **Multiple Model Support**: wan2.1, hunyuan architectures
|
||||
The repository includes runnable workflow examples under
|
||||
[`example_workflows/`](example_workflows/):
|
||||
|
||||
- [Wan 2.2](example_workflows/lighx2v_wan2.2.json) text/image-to-video
|
||||
- [SekoTalk](example_workflows/lightx2v_seko_talk.json) and [static
|
||||
SekoTalk](example_workflows/lightx2v_seko_talk_static.json)
|
||||
- [Multi-person SekoTalk](example_workflows/lightx2v_seko_talk_multi_talk.json)
|
||||
- [SeedVR2](example_workflows/lightx2v_seedvr.json) video restoration
|
||||
- [SwiftVR image](example_workflows/lightx2v_swiftvr_image.json) and [SwiftVR
|
||||
video](example_workflows/lightx2v_swiftvr_video.json) restoration
|
||||
|
||||
The examples are versioned with the wrapper and are the best reference for
|
||||
current node names and connections.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone this repository with submodules into your ComfyUI's `custom_nodes` directory:
|
||||
Clone the repository with its LightX2V submodule into ComfyUI's
|
||||
`custom_nodes` directory:
|
||||
|
||||
```bash
|
||||
cd ComfyUI/custom_nodes
|
||||
git clone --recursive https://github.com/gaclove/ComfyUI-Lightx2vWrapper.git
|
||||
git clone --recursive https://github.com/ModelTC/ComfyUI-Lightx2vWrapper.git
|
||||
cd ComfyUI-Lightx2vWrapper
|
||||
python -m pip install -r lightx2v/requirements.txt
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
If you already cloned without submodules, initialize them:
|
||||
For an existing checkout, update the submodule with:
|
||||
|
||||
```bash
|
||||
cd ComfyUI-Lightx2vWrapper
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
Download the model files from [LightX2V on Hugging Face](https://huggingface.co/lightx2v)
|
||||
and place them under `ComfyUI/models/lightx2v/`.
|
||||
|
||||
```bash
|
||||
cd ComfyUI-Lightx2vWrapper
|
||||
# Install lightx2v submodule dependencies
|
||||
pip install -r lightx2v/requirements.txt
|
||||
# Install ComfyUI wrapper dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
## Node groups
|
||||
|
||||
3. Download models and place them in `ComfyUI/models/lightx2v/` directory
|
||||
- **Configuration:** `LightX2VInferenceConfig`, `LightX2VTeaCache`,
|
||||
`LightX2VQuantization`, and `LightX2VMemoryOptimization`.
|
||||
- **Composition:** `LightX2VConfigCombinerV2` and `LightX2VConfigCombinerV3`.
|
||||
- **Inference:** `LightX2VModularInferenceV2`.
|
||||
- **Talk inputs:** single-object, file-list, JSON, and multi-object talk
|
||||
combiners.
|
||||
- **Video inputs and restoration:** `LightX2VInputVideoPath`, SeedVR2 and
|
||||
SwiftVR loaders/samplers, plus `LightX2VOutputVideoPreview`.
|
||||
- **LoRA:** `LightX2VLoRALoader`.
|
||||
|
||||
## Node Overview
|
||||
The inference node currently accepts the model classes exposed by
|
||||
`model_utils.support_model_cls_list`: Wan 2.1/2.2 variants, SekoTalk,
|
||||
Qwen Image, and SeedVR2. The retired `*_distill` values remain selectable as
|
||||
compatibility aliases for saved ComfyUI workflows and are normalized before
|
||||
LightX2V's runner is built.
|
||||
|
||||
### Configuration Nodes
|
||||
## Model files
|
||||
|
||||
#### 1. LightX2V Inference Config
|
||||
Place each model in its own directory under `ComfyUI/models/lightx2v/`. LoRA
|
||||
files can be placed in either `ComfyUI/models/lightx2v/loras/` or
|
||||
`ComfyUI/models/loras/`.
|
||||
|
||||
Basic inference configuration for video generation.
|
||||
|
||||
- **Inputs**: model, task_type, inference_steps, seed, cfg_scale, width, height, video_length, fps
|
||||
- **Output**: Base configuration object
|
||||
|
||||
#### 2. LightX2V TeaCache
|
||||
|
||||
Feature caching acceleration configuration.
|
||||
|
||||
- **Inputs**: enable, threshold (0.0-1.0), use_ret_steps
|
||||
- **Output**: TeaCache configuration
|
||||
- **Note**: Lower threshold = more speedup (0.1 ~2x, 0.2 ~3x)
|
||||
|
||||
#### 3. LightX2V Quantization
|
||||
|
||||
Model quantization settings for memory efficiency.
|
||||
|
||||
- **Inputs**: dit_precision, t5_precision, clip_precision, backend, sensitive_layers_precision
|
||||
- **Output**: Quantization configuration
|
||||
- **Backends**: Auto-detected (vllm, sgl, q8f)
|
||||
|
||||
#### 4. LightX2V Memory Optimization
|
||||
|
||||
Memory management strategies.
|
||||
|
||||
- **Inputs**: optimization_level, attention_type, enable_rotary_chunking, cpu_offload, unload_after_generate
|
||||
- **Output**: Memory optimization configuration
|
||||
|
||||
#### 5. LightX2V Lightweight VAE
|
||||
|
||||
VAE optimization options.
|
||||
|
||||
- **Inputs**: use_tiny_vae, use_tiling_vae
|
||||
- **Output**: VAE configuration
|
||||
|
||||
#### 6. LightX2V LoRA Loader
|
||||
|
||||
Load and chain LoRA models.
|
||||
|
||||
- **Inputs**: lora_name, strength (0.0-2.0), lora_chain (optional)
|
||||
- **Output**: LoRA chain configuration
|
||||
|
||||
### Combination Node
|
||||
|
||||
#### 7. LightX2V Config Combiner
|
||||
|
||||
Combines all configuration modules into a single configuration.
|
||||
|
||||
- **Inputs**: All configuration types (optional)
|
||||
- **Output**: Combined configuration object
|
||||
|
||||
### Inference Node
|
||||
|
||||
#### 8. LightX2V Modular Inference
|
||||
|
||||
Main inference node for video generation.
|
||||
|
||||
- **Inputs**: combined_config, prompt, negative_prompt, image (optional), audio (optional)
|
||||
- **Outputs**: Generated video frames
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic T2V Workflow
|
||||
|
||||
1. Create LightX2V Inference Config (task_type: "t2v")
|
||||
2. Use LightX2V Config Combiner
|
||||
3. Connect to LightX2V Modular Inference with text prompt
|
||||
4. Save video output
|
||||
|
||||
### I2V with Optimizations
|
||||
|
||||
1. Load input image
|
||||
2. Create LightX2V Inference Config (task_type: "i2v")
|
||||
3. Add LightX2V TeaCache (threshold: 0.26)
|
||||
4. Add LightX2V Memory Optimization
|
||||
5. Combine configs with LightX2V Config Combiner
|
||||
6. Run LightX2V Modular Inference
|
||||
|
||||
### With LoRA
|
||||
|
||||
1. Create base configuration
|
||||
2. Load LoRA with LightX2V LoRA Loader
|
||||
3. Chain multiple LoRAs if needed
|
||||
4. Combine all configs
|
||||
5. Run inference
|
||||
|
||||
## Model Directory Structure
|
||||
|
||||
Download models from: <https://huggingface.co/lightx2v>
|
||||
|
||||
Models should be placed in:
|
||||
|
||||
```txt
|
||||
```text
|
||||
ComfyUI/models/lightx2v/
|
||||
├── Wan2.1-I2V-14B-720P-xxx/ # Main model checkpoints
|
||||
├── Wan2.1-I2V-14B-480P-xxx/ # Main model checkpoints
|
||||
├── loras/ # LoRA models
|
||||
├── Wan2.2-I2V-A14B-distill-fp8/
|
||||
├── seedvr2_ema_3b_fp8/
|
||||
└── loras/
|
||||
```
|
||||
|
||||
## Tips
|
||||
The exact directory names depend on the downloaded checkpoint. The model
|
||||
picker only lists directories that exist on the local ComfyUI installation.
|
||||
|
||||
- Start with default settings and adjust based on your hardware
|
||||
- Use TeaCache with threshold 0.1-0.2 for significant speedup
|
||||
- Enable memory optimization if running on limited VRAM
|
||||
- Quantization can reduce memory usage but may affect quality
|
||||
- Chain multiple LoRAs for complex style combinations
|
||||
## Development
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Out of Memory**: Enable memory optimization or use quantization
|
||||
- **Slow Generation**: Enable TeaCache or reduce inference steps
|
||||
- **Model Not Found**: Check model paths in `ComfyUI/models/lightx2v/`
|
||||
|
||||
## Example Workflows
|
||||
|
||||
Example workflow JSON files are provided in the `examples/` directory:
|
||||
|
||||
- `wan_i2v.json` - Basic image-to-video
|
||||
- `wan_i2v_with_distill_lora.json` - I2V with distillation LoRA
|
||||
- `wan_t2v_with_distill_lora.json` - T2V with distillation LoRA
|
||||
|
||||
## Contributing Guidelines
|
||||
|
||||
We welcome community contributions! Before submitting code, please ensure you follow these steps:
|
||||
|
||||
### Install Development Dependencies
|
||||
|
||||
```bash
|
||||
pip install ruff pre-commit
|
||||
```
|
||||
|
||||
### Code Quality Check
|
||||
|
||||
Before committing code, run the following command:
|
||||
Install the development hook and run the same checks used by CI:
|
||||
|
||||
```bash
|
||||
python -m pip install pre-commit
|
||||
pre-commit install
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
This will automatically check code formatting, syntax errors, and other code quality issues.
|
||||
|
||||
### Contribution Process
|
||||
|
||||
1. Fork this repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
5. Create a Pull Request
|
||||
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for pull request and workflow
|
||||
validation guidelines.
|
||||
|
||||
+55
-168
@@ -1,202 +1,89 @@
|
||||
[](https://opensource.org/licenses/MIT) [](https://www.python.org/downloads/) [](https://github.com/comfyanonymous/ComfyUI) [](https://huggingface.co/lightx2v) [](https://deepwiki.com/GacLove/ComfyUI-Lightx2vWrapper)
|
||||
[](LICENSE)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://github.com/comfyanonymous/ComfyUI)
|
||||
[](https://huggingface.co/lightx2v)
|
||||
|
||||
# ComfyUI-Lightx2vWrapper
|
||||
|
||||
[English](./README.md) | 中文版
|
||||
[English](README.md) | 中文
|
||||
|
||||
LightX2V 的 ComfyUI 自定义节点封装,通过模块化配置实现视频生成和高级优化功能。
|
||||
[LightX2V](https://github.com/ModelTC/LightX2V) 的 ComfyUI 自定义节点封装,
|
||||
提供模块化配置、ComfyUI 文件输入以及视频输出预览。
|
||||
|
||||
## 功能特性
|
||||
## 已提供的工作流
|
||||
|
||||
- **模块化配置系统**:为视频生成的各个方面提供独立的配置节点
|
||||
- **文本生成视频(T2V)和图像生成视频(I2V)**:支持两种生成模式
|
||||
- **高级优化功能**:
|
||||
- TeaCache 加速(最高可达 3 倍速度提升)
|
||||
- 量化支持(int8、fp8)
|
||||
- 内存优化与 CPU 卸载
|
||||
- 轻量级 VAE 选项
|
||||
- **LoRA 支持**:可串联多个 LoRA 模型进行自定义
|
||||
- **多模型支持**:wan2.1、hunyuan 架构
|
||||
仓库在 [`example_workflows/`](example_workflows/) 中提供了与当前节点注册表
|
||||
同步的示例:
|
||||
|
||||
## 安装说明
|
||||
- [Wan 2.2](example_workflows/lighx2v_wan2.2.json) 文生视频/图生视频
|
||||
- [SekoTalk](example_workflows/lightx2v_seko_talk.json) 和[静态
|
||||
SekoTalk](example_workflows/lightx2v_seko_talk_static.json)
|
||||
- [多人 SekoTalk](example_workflows/lightx2v_seko_talk_multi_talk.json)
|
||||
- [SeedVR2](example_workflows/lightx2v_seedvr.json) 视频修复
|
||||
- [SwiftVR 图像](example_workflows/lightx2v_swiftvr_image.json) 和 [SwiftVR
|
||||
视频](example_workflows/lightx2v_swiftvr_video.json) 修复
|
||||
|
||||
1. 将此仓库克隆到 ComfyUI 的 `custom_nodes` 目录,包含子模块:
|
||||
示例工作流会随封装器一起更新,可作为当前节点名称和连线方式的参考。
|
||||
|
||||
## 安装
|
||||
|
||||
将仓库和 LightX2V 子模块一起克隆到 ComfyUI 的 `custom_nodes` 目录:
|
||||
|
||||
```bash
|
||||
cd ComfyUI/custom_nodes
|
||||
git clone --recursive https://github.com/gaclove/ComfyUI-Lightx2vWrapper.git
|
||||
git clone --recursive https://github.com/ModelTC/ComfyUI-Lightx2vWrapper.git
|
||||
cd ComfyUI-Lightx2vWrapper
|
||||
python -m pip install -r lightx2v/requirements.txt
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
如果已经克隆但未包含子模块,请初始化子模块:
|
||||
如果已有仓库但还没有子模块:
|
||||
|
||||
```bash
|
||||
cd ComfyUI-Lightx2vWrapper
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
2. 安装依赖:
|
||||
从 [Hugging Face 的 LightX2V 页面](https://huggingface.co/lightx2v) 下载模型,
|
||||
放到 `ComfyUI/models/lightx2v/` 下。
|
||||
|
||||
```bash
|
||||
cd ComfyUI-Lightx2vWrapper
|
||||
# 安装 lightx2v 子模块依赖
|
||||
pip install -r lightx2v/requirements.txt
|
||||
# 安装 ComfyUI 封装器依赖
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
## 节点分组
|
||||
|
||||
3. 下载模型并放置在 `ComfyUI/models/lightx2v/` 目录
|
||||
- **配置:** `LightX2VInferenceConfig`、`LightX2VTeaCache`、
|
||||
`LightX2VQuantization`、`LightX2VMemoryOptimization`
|
||||
- **组合:** `LightX2VConfigCombinerV2`、`LightX2VConfigCombinerV3`
|
||||
- **推理:** `LightX2VModularInferenceV2`
|
||||
- **Talk 输入:** 单对象、文件列表、JSON 和多人 Talk 组合节点
|
||||
- **视频输入与修复:** `LightX2VInputVideoPath`、SeedVR2/SwiftVR 加载器与
|
||||
采样器、`LightX2VOutputVideoPreview`
|
||||
- **LoRA:** `LightX2VLoRALoader`
|
||||
|
||||
## 节点概览
|
||||
推理节点当前支持 `model_utils.support_model_cls_list` 中的 Wan 2.1/2.2
|
||||
变体、SekoTalk、Qwen Image 和 SeedVR2。旧 workflow 使用的 `*_distill`
|
||||
值仍作为兼容别名保留,构建 LightX2V runner 前会自动归一化。
|
||||
|
||||
### 配置节点
|
||||
## 模型目录
|
||||
|
||||
#### 1. LightX2V Inference Config(推理配置)
|
||||
每个模型放在 `ComfyUI/models/lightx2v/` 下的独立目录中。LoRA 可以放在
|
||||
`ComfyUI/models/lightx2v/loras/` 或 `ComfyUI/models/loras/`:
|
||||
|
||||
视频生成的基础推理配置。
|
||||
|
||||
- **输入**:model(模型)、task_type(任务类型)、inference_steps(推理步数)、seed(种子)、cfg_scale(CFG 缩放)、width(宽度)、height(高度)、video_length(视频长度)、fps(帧率)
|
||||
- **输出**:基础配置对象
|
||||
|
||||
#### 2. LightX2V TeaCache(TeaCache 配置)
|
||||
|
||||
特征缓存加速配置。
|
||||
|
||||
- **输入**:enable(启用)、threshold(阈值 0.0-1.0)、use_ret_steps(使用返回步数)
|
||||
- **输出**:TeaCache 配置
|
||||
- **注意**:阈值越低 = 速度提升越大(0.1 约 2 倍,0.2 约 3 倍)
|
||||
|
||||
#### 3. LightX2V Quantization(量化配置)
|
||||
|
||||
模型量化设置,提升内存效率。
|
||||
|
||||
- **输入**:dit_precision(DIT 精度)、t5_precision(T5 精度)、clip_precision(CLIP 精度)、backend(后端)、sensitive_layers_precision(敏感层精度)
|
||||
- **输出**:量化配置
|
||||
- **后端**:自动检测(vllm、sgl、q8f)
|
||||
|
||||
#### 4. LightX2V Memory Optimization(内存优化)
|
||||
|
||||
内存管理策略。
|
||||
|
||||
- **输入**:optimization_level(优化级别)、attention_type(注意力类型)、enable_rotary_chunking(启用旋转分块)、cpu_offload(CPU 卸载)、unload_after_generate(生成后卸载)
|
||||
- **输出**:内存优化配置
|
||||
|
||||
#### 5. LightX2V Lightweight VAE(轻量级 VAE)
|
||||
|
||||
VAE 优化选项。
|
||||
|
||||
- **输入**:use_tiny_vae(使用微型 VAE)、use_tiling_vae(使用平铺 VAE)
|
||||
- **输出**:VAE 配置
|
||||
|
||||
#### 6. LightX2V LoRA Loader(LoRA 加载器)
|
||||
|
||||
加载并串联 LoRA 模型。
|
||||
|
||||
- **输入**:lora_name(LoRA 名称)、strength(强度 0.0-2.0)、lora_chain(LoRA 链,可选)
|
||||
- **输出**:LoRA 链配置
|
||||
|
||||
### 组合节点
|
||||
|
||||
#### 7. LightX2V Config Combiner(配置组合器)
|
||||
|
||||
将所有配置模块组合成单一配置。
|
||||
|
||||
- **输入**:所有配置类型(可选)
|
||||
- **输出**:组合后的配置对象
|
||||
|
||||
### 推理节点
|
||||
|
||||
#### 8. LightX2V Modular Inference(模块化推理)
|
||||
|
||||
视频生成的主推理节点。
|
||||
|
||||
- **输入**:combined_config(组合配置)、prompt(提示词)、negative_prompt(负面提示词)、image(图像,可选)、audio(音频,可选)
|
||||
- **输出**:生成的视频帧
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础 T2V 工作流
|
||||
|
||||
1. 创建 LightX2V Inference Config(task_type: "t2v")
|
||||
2. 使用 LightX2V Config Combiner
|
||||
3. 连接到 LightX2V Modular Inference,输入文本提示词
|
||||
4. 保存视频输出
|
||||
|
||||
### 带优化的 I2V
|
||||
|
||||
1. 加载输入图像
|
||||
2. 创建 LightX2V Inference Config(task_type: "i2v")
|
||||
3. 添加 LightX2V TeaCache(threshold: 0.26)
|
||||
4. 添加 LightX2V Memory Optimization
|
||||
5. 使用 LightX2V Config Combiner 组合配置
|
||||
6. 运行 LightX2V Modular Inference
|
||||
|
||||
### 使用 LoRA
|
||||
|
||||
1. 创建基础配置
|
||||
2. 使用 LightX2V LoRA Loader 加载 LoRA
|
||||
3. 如需要可串联多个 LoRA
|
||||
4. 组合所有配置
|
||||
5. 运行推理
|
||||
|
||||
## 模型目录结构
|
||||
|
||||
从以下地址下载模型:<https://huggingface.co/lightx2v>
|
||||
|
||||
模型应放置在:
|
||||
|
||||
```txt
|
||||
```text
|
||||
ComfyUI/models/lightx2v/
|
||||
├── Wan2.1-I2V-14B-720P-xxx/ # 主模型检查点
|
||||
├── Wan2.1-I2V-14B-480P-xxx/ # 主模型检查点
|
||||
├── loras/ # LoRA 模型
|
||||
├── Wan2.2-I2V-A14B-distill-fp8/
|
||||
├── seedvr2_ema_3b_fp8/
|
||||
└── loras/
|
||||
```
|
||||
|
||||
## 使用技巧
|
||||
具体目录名取决于下载的 checkpoint;模型选择器只会列出当前 ComfyUI 中存在
|
||||
的目录。
|
||||
|
||||
- 从默认设置开始,根据硬件情况调整
|
||||
- 使用 TeaCache,阈值设为 0.1-0.2 可显著提速
|
||||
- 在显存有限时启用内存优化
|
||||
- 量化可减少内存使用但可能影响质量
|
||||
- 串联多个 LoRA 实现复杂风格组合
|
||||
## 开发
|
||||
|
||||
## 故障排除
|
||||
|
||||
- **内存不足**:启用内存优化或使用量化
|
||||
- **生成速度慢**:启用 TeaCache 或减少推理步数
|
||||
- **找不到模型**:检查 `ComfyUI/models/lightx2v/` 中的模型路径
|
||||
|
||||
## 示例工作流
|
||||
|
||||
`examples/` 目录中提供了示例工作流 JSON 文件:
|
||||
|
||||
- `wan_i2v.json` - 基础图像生成视频
|
||||
- `wan_i2v_with_distill_lora.json` - 带蒸馏 LoRA 的 I2V
|
||||
- `wan_t2v_with_distill_lora.json` - 带蒸馏 LoRA 的 T2V
|
||||
|
||||
## 贡献指南
|
||||
|
||||
我们欢迎社区贡献!在提交代码之前,请确保遵循以下步骤:
|
||||
|
||||
### 安装开发依赖
|
||||
|
||||
```bash
|
||||
pip install ruff pre-commit
|
||||
```
|
||||
|
||||
### 代码质量检查
|
||||
|
||||
在提交代码之前,请运行以下命令:
|
||||
安装 pre-commit 并运行 CI 使用的检查:
|
||||
|
||||
```bash
|
||||
python -m pip install pre-commit
|
||||
pre-commit install
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
这将自动检查代码格式、语法错误和其他代码质量问题。
|
||||
|
||||
### 贡献流程
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 创建功能分支 (`git checkout -b feature/amazing-feature`)
|
||||
3. 提交更改 (`git commit -m 'Add some amazing feature'`)
|
||||
4. 推送到分支 (`git push origin feature/amazing-feature`)
|
||||
5. 创建 Pull Request
|
||||
贡献流程和 workflow 验证要求见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。
|
||||
|
||||
@@ -76,5 +76,4 @@ class LightX2VDefaultConfig:
|
||||
"audio_sr": 16000,
|
||||
"talk_objects": None,
|
||||
"boundary_step_index": 2,
|
||||
"rope_type": "torch",
|
||||
}
|
||||
|
||||
@@ -44,6 +44,39 @@ WRAPPER_TO_LIGHTX2V_FIELDS: Dict[str, str] = {
|
||||
# Attention type fans out to three internal slots in lightx2v.
|
||||
_ATTN_TYPE_SLOTS = ("self_attn_1_type", "cross_attn_1_type", "cross_attn_2_type")
|
||||
|
||||
# LightX2V unified the dedicated distilled Wan runners into the regular Wan
|
||||
# runners in 23fc3d45. Keep old ComfyUI workflows loadable by translating the
|
||||
# retired model class names to the new model class plus scheduler selector.
|
||||
_LEGACY_MODEL_CLASS_ALIASES = {
|
||||
"wan2.1_distill": ("wan2.1", "dmd2"),
|
||||
"wan2.1_mean_flow_distill": ("wan2.1", "mean_flow"),
|
||||
"wan2.2_moe_distill": ("wan2.2_moe", "dmd2"),
|
||||
}
|
||||
|
||||
|
||||
def _add_wan22_branch_paths(model_path: str, updates: Dict[str, Any]) -> None:
|
||||
"""Expose Wan2.2's two transformer branches in the runner config.
|
||||
|
||||
Hugging Face model packages commonly keep the branches below
|
||||
``distill_models/`` while the unified runner accepts explicit branch
|
||||
directories. Supplying both path variants is harmless: the runner picks
|
||||
the original or quantized key according to ``dit_quantized``.
|
||||
"""
|
||||
if not model_path:
|
||||
return
|
||||
|
||||
for branch in ("high_noise_model", "low_noise_model"):
|
||||
candidates = (
|
||||
os.path.join(model_path, branch),
|
||||
os.path.join(model_path, "distill_models", branch),
|
||||
)
|
||||
branch_path = next((path for path in candidates if os.path.isdir(path)), None)
|
||||
if branch_path is None:
|
||||
continue
|
||||
config_key = branch.removesuffix("_model")
|
||||
updates.setdefault(f"{config_key}_original_ckpt", branch_path)
|
||||
updates.setdefault(f"{config_key}_quantized_ckpt", branch_path)
|
||||
|
||||
|
||||
def apply_inference_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Translate inference widget values to a partial lightx2v config dict."""
|
||||
@@ -65,9 +98,16 @@ def apply_inference_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
# Wan2.2 MoE has two CFG scales (high/low noise) and a boundary param.
|
||||
model_cls = config.get("model_cls", "")
|
||||
legacy_model = _LEGACY_MODEL_CLASS_ALIASES.get(model_cls)
|
||||
if legacy_model is not None:
|
||||
model_cls, distill_method = legacy_model
|
||||
updates["model_cls"] = model_cls
|
||||
updates["distill_method"] = distill_method
|
||||
|
||||
if "wan2.2_moe" in model_cls:
|
||||
updates["boundary"] = 0.9
|
||||
updates["sample_guide_scale"] = [config.get("cfg_scale"), config.get("cfg_scale2")]
|
||||
_add_wan22_branch_paths(config.get("model_path", ""), updates)
|
||||
if "wan2.2" in model_cls:
|
||||
updates["use_image_encoder"] = False
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ class ConfigBuilder:
|
||||
("model_cls", None),
|
||||
("model_path", None),
|
||||
("task", None),
|
||||
("distill_method", None),
|
||||
("t5_quantized", False),
|
||||
("clip_quantized", False),
|
||||
("lora_configs", None),
|
||||
|
||||
@@ -1,94 +1,65 @@
|
||||
{
|
||||
"1": {
|
||||
"inputs": {
|
||||
"video": "1954627330480766977_wan2-2.mp4",
|
||||
"force_rate": 0,
|
||||
"custom_width": 0,
|
||||
"custom_height": 0,
|
||||
"frame_load_cap": 0,
|
||||
"skip_first_frames": 0,
|
||||
"select_every_nth": 1,
|
||||
"format": "AnimateDiff"
|
||||
},
|
||||
"class_type": "VHS_LoadVideo",
|
||||
"_meta": {
|
||||
"title": "Load Video (Upload) 🎥🅥🅗🅢"
|
||||
}
|
||||
"1": {
|
||||
"inputs": {
|
||||
"video": "飞书20260721-124954.mp4"
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"ckpt_name": "seedvr2_ema_3b_fp8.safetensors",
|
||||
"precision": "fp8-sgl",
|
||||
"cpu_offload": false,
|
||||
"use_tiling_vae": true
|
||||
},
|
||||
"class_type": "LightX2VSeedVR2Loader",
|
||||
"_meta": {
|
||||
"title": "LightX2V SeedVR2 Loader"
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"inputs": {
|
||||
"target_height": 1920,
|
||||
"target_width": 1080,
|
||||
"infer_steps": 1,
|
||||
"segment_length": 81,
|
||||
"segment_overlap": 1,
|
||||
"seed": 3816942657,
|
||||
"source_fps": 16,
|
||||
"model": [
|
||||
"4",
|
||||
0
|
||||
],
|
||||
"images": [
|
||||
"1",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "LightX2VSeedVR2Sampler",
|
||||
"_meta": {
|
||||
"title": "LightX2V SeedVR2 Sampler"
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"video_info": [
|
||||
"1",
|
||||
3
|
||||
]
|
||||
},
|
||||
"class_type": "VHS_VideoInfo",
|
||||
"_meta": {
|
||||
"title": "Video Info 🎥🅥🅗🅢"
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"inputs": {
|
||||
"frame_rate": [
|
||||
"6",
|
||||
0
|
||||
],
|
||||
"loop_count": 0,
|
||||
"filename_prefix": "AnimateDiff",
|
||||
"format": "video/h264-mp4",
|
||||
"pix_fmt": "yuv420p",
|
||||
"crf": 19,
|
||||
"save_metadata": true,
|
||||
"trim_to_audio": false,
|
||||
"pingpong": false,
|
||||
"save_output": true,
|
||||
"images": [
|
||||
"5",
|
||||
0
|
||||
],
|
||||
"audio": [
|
||||
"1",
|
||||
2
|
||||
]
|
||||
},
|
||||
"class_type": "VHS_VideoCombine",
|
||||
"_meta": {
|
||||
"title": "Video Combine 🎥🅥🅗🅢"
|
||||
}
|
||||
"class_type": "LightX2VInputVideoPath",
|
||||
"_meta": {
|
||||
"title": "LightX2V Input Video Path (Upload)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"inputs": {
|
||||
"ckpt_name": "seedvr2_ema_3b_fp8.safetensors",
|
||||
"precision": "fp8-sgl",
|
||||
"cpu_offload": false,
|
||||
"use_tiling_vae": true,
|
||||
"vae_tile_size": 1024,
|
||||
"vae_tile_overlap": 64,
|
||||
"vae_causal_slice_size": 4,
|
||||
"vae_memory_limit_gb": 0.5
|
||||
},
|
||||
"class_type": "LightX2VSeedVR2Loader",
|
||||
"_meta": {
|
||||
"title": "LightX2V SeedVR2 Loader"
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"inputs": {
|
||||
"model": [
|
||||
"2",
|
||||
0
|
||||
],
|
||||
"video_path": [
|
||||
"1",
|
||||
0
|
||||
],
|
||||
"target_width": 1080,
|
||||
"target_height": 1920,
|
||||
"infer_steps": 1,
|
||||
"segment_length": 81,
|
||||
"segment_overlap": 1,
|
||||
"seed": 3816942657,
|
||||
"filename_prefix": "lightx2v_seedvr2/SeedVR2",
|
||||
"color_fix": "gpu"
|
||||
},
|
||||
"class_type": "LightX2VSeedVR2FileSampler",
|
||||
"_meta": {
|
||||
"title": "LightX2V SeedVR2 File Sampler"
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"filename": [
|
||||
"3",
|
||||
0
|
||||
],
|
||||
"validate_exists": true,
|
||||
"mux_audio": false
|
||||
},
|
||||
"class_type": "LightX2VOutputVideoPreview",
|
||||
"_meta": {
|
||||
"title": "LightX2V Output Video Preview"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"11": {
|
||||
"inputs": {
|
||||
"audio": "12秒.mp3",
|
||||
"audio": "audio_full_female_10s.wav",
|
||||
"start_time": 0,
|
||||
"duration": 0
|
||||
},
|
||||
@@ -25,6 +25,14 @@
|
||||
"audio": [
|
||||
"11",
|
||||
0
|
||||
],
|
||||
"memory_config": [
|
||||
"16",
|
||||
0
|
||||
],
|
||||
"quantization_config": [
|
||||
"17",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "LightX2VConfigCombinerV2",
|
||||
@@ -35,7 +43,7 @@
|
||||
"14": {
|
||||
"inputs": {
|
||||
"model_cls": "seko_talk",
|
||||
"model_name": "SekoTalk-v2.7_beta2-bf16-step4_temp",
|
||||
"model_name": "SekoTalk-v2.7-fp8-step4-xt-490",
|
||||
"task": "rs2v",
|
||||
"infer_steps": 4,
|
||||
"seed": 4221706066,
|
||||
@@ -70,23 +78,54 @@
|
||||
"title": "LightX2V Modular Inference V2"
|
||||
}
|
||||
},
|
||||
"16": {
|
||||
"inputs": {
|
||||
"enable_rotary_chunk": false,
|
||||
"rotary_chunk_size": 100,
|
||||
"clean_cuda_cache": true,
|
||||
"cpu_offload": true,
|
||||
"offload_granularity": "block",
|
||||
"offload_ratio": 1.0,
|
||||
"t5_cpu_offload": true,
|
||||
"t5_offload_granularity": "model",
|
||||
"audio_encoder_cpu_offload": true,
|
||||
"audio_adapter_cpu_offload": true,
|
||||
"vae_cpu_offload": true,
|
||||
"use_tiling_vae": true,
|
||||
"lazy_load": false,
|
||||
"unload_after_inference": false
|
||||
},
|
||||
"class_type": "LightX2VMemoryOptimization",
|
||||
"_meta": {
|
||||
"title": "LightX2V Memory Optimization"
|
||||
}
|
||||
},
|
||||
"17": {
|
||||
"inputs": {
|
||||
"dit_quant_scheme": "fp8-sgl",
|
||||
"t5_quant_scheme": "fp8-sgl",
|
||||
"clip_quant_scheme": "fp8-sgl",
|
||||
"adapter_quant_scheme": "fp8-sgl"
|
||||
},
|
||||
"class_type": "LightX2VQuantization",
|
||||
"_meta": {
|
||||
"title": "LightX2V Quantization"
|
||||
}
|
||||
},
|
||||
"18": {
|
||||
"inputs": {
|
||||
"frame_rate": [
|
||||
"22",
|
||||
0
|
||||
],
|
||||
"frame_rate": 16,
|
||||
"loop_count": 0,
|
||||
"filename_prefix": "vigen-15ebb023",
|
||||
"filename_prefix": "lightx2v_sekotalk/single",
|
||||
"format": "video/h264-mp4",
|
||||
"pix_fmt": "yuv420p",
|
||||
"crf": 23,
|
||||
"crf": 19,
|
||||
"save_metadata": false,
|
||||
"trim_to_audio": false,
|
||||
"pingpong": false,
|
||||
"save_output": false,
|
||||
"save_output": true,
|
||||
"images": [
|
||||
"21",
|
||||
"15",
|
||||
0
|
||||
],
|
||||
"audio": [
|
||||
@@ -101,41 +140,11 @@
|
||||
},
|
||||
"20": {
|
||||
"inputs": {
|
||||
"image": "00000.jpg"
|
||||
"image": "codex_image_20260922_135831_1323a530_B.jpg"
|
||||
},
|
||||
"class_type": "LoadImage",
|
||||
"_meta": {
|
||||
"title": "Load Image"
|
||||
}
|
||||
},
|
||||
"21": {
|
||||
"inputs": {
|
||||
"source_fps": 16,
|
||||
"target_fps": [
|
||||
"22",
|
||||
0
|
||||
],
|
||||
"scale": 1,
|
||||
"model_name": "flownet.pkl",
|
||||
"batch_size": 8,
|
||||
"use_fp16": true,
|
||||
"images": [
|
||||
"15",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "RIFEInterpolation",
|
||||
"_meta": {
|
||||
"title": "RIFE Frame Interpolation"
|
||||
}
|
||||
},
|
||||
"22": {
|
||||
"inputs": {
|
||||
"value": 25.000000000000007
|
||||
},
|
||||
"class_type": "FloatConstant",
|
||||
"_meta": {
|
||||
"title": "target fps"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"revision": 0,
|
||||
"last_node_id": 50,
|
||||
"last_link_id": 49,
|
||||
"last_node_id": 51,
|
||||
"last_link_id": 50,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 10,
|
||||
@@ -32,16 +32,15 @@
|
||||
"Node name for S&R": "LightX2VQuantization"
|
||||
},
|
||||
"widgets_values": [
|
||||
"sgl",
|
||||
"fp8",
|
||||
"fp8",
|
||||
"fp16",
|
||||
"fp8"
|
||||
"fp8-sgl",
|
||||
"fp8-sgl",
|
||||
"fp8-sgl",
|
||||
"fp8-sgl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "LightX2VConfigCombiner",
|
||||
"type": "LightX2VConfigCombinerV3",
|
||||
"pos": [
|
||||
-1207.53271484375,
|
||||
111.63562774658203
|
||||
@@ -51,7 +50,7 @@
|
||||
126
|
||||
],
|
||||
"flags": {},
|
||||
"order": 14,
|
||||
"order": 16,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -75,7 +74,7 @@
|
||||
"name": "memory_config",
|
||||
"shape": 7,
|
||||
"type": "MEMORY_CONFIG",
|
||||
"link": null
|
||||
"link": 50
|
||||
},
|
||||
{
|
||||
"name": "lora_chain",
|
||||
@@ -88,41 +87,6 @@
|
||||
"shape": 7,
|
||||
"type": "TALK_OBJECTS_CONFIG",
|
||||
"link": 49
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "combined_config",
|
||||
"type": "COMBINED_CONFIG",
|
||||
"links": [
|
||||
13
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LightX2VConfigCombiner"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "LightX2VModularInference",
|
||||
"pos": [
|
||||
-818.747802734375,
|
||||
89.00883483886719
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 16,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "combined_config",
|
||||
"type": "COMBINED_CONFIG",
|
||||
"link": 13
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
@@ -134,7 +98,42 @@
|
||||
"name": "audio",
|
||||
"shape": 7,
|
||||
"type": "AUDIO",
|
||||
"link": 15
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prepared_config",
|
||||
"type": "PREPARED_CONFIG",
|
||||
"links": [
|
||||
13
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LightX2VConfigCombinerV3"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "LightX2VModularInferenceV2",
|
||||
"pos": [
|
||||
-818.747802734375,
|
||||
89.00883483886719
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 18,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "prepared_config",
|
||||
"type": "PREPARED_CONFIG",
|
||||
"link": 13
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -154,7 +153,7 @@
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LightX2VModularInference"
|
||||
"Node name for S&R": "LightX2VModularInferenceV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"The video feature the person is talking.",
|
||||
@@ -195,44 +194,10 @@
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"input (1).png",
|
||||
"codex_two_person_input.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "LoadAudio",
|
||||
"pos": [
|
||||
-1618.51171875,
|
||||
526.3804931640625
|
||||
],
|
||||
"size": [
|
||||
274.080078125,
|
||||
136
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "AUDIO",
|
||||
"type": "AUDIO",
|
||||
"links": [
|
||||
15
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "加载音频",
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadAudio"
|
||||
},
|
||||
"widgets_values": [
|
||||
"15.wav",
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "VHS_VideoCombine",
|
||||
@@ -337,8 +302,8 @@
|
||||
},
|
||||
"widgets_values": [
|
||||
"seko_talk",
|
||||
"Wan2.1-R2V0909-Audio-14B-720P-fp8",
|
||||
"i2v",
|
||||
"SekoTalk-v2.7-fp8-step4-xt-490",
|
||||
"rs2v",
|
||||
4,
|
||||
584734195,
|
||||
"randomize",
|
||||
@@ -357,42 +322,6 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"type": "easy showAnything",
|
||||
"pos": [
|
||||
-508.2369079589844,
|
||||
-330.85296630859375
|
||||
],
|
||||
"size": [
|
||||
511.8559875488281,
|
||||
291.96728515625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 17,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "anything",
|
||||
"shape": 7,
|
||||
"type": "*",
|
||||
"link": 42
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output",
|
||||
"type": "*",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "easy showAnything"
|
||||
},
|
||||
"widgets_values": [
|
||||
"TalkObjectsConfig(talk_objects=[{'name': 'person_1', 'audio': '/mnt/aigc/users/gaopeng1/ComfyUI/input/person_1_audio.wav', 'mask': '/mnt/aigc/users/gaopeng1/ComfyUI/input/person_1_mask.png', 'source_type': 'file'}, {'name': 'person_1', 'audio': '/mnt/aigc/users/gaopeng1/ComfyUI/input/person_1_audio.wav', 'mask': '/mnt/aigc/users/gaopeng1/ComfyUI/input/person_1_mask.png', 'source_type': 'file'}])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"type": "LoadAudio",
|
||||
@@ -423,7 +352,7 @@
|
||||
"Node name for S&R": "LoadAudio"
|
||||
},
|
||||
"widgets_values": [
|
||||
"p2.mp3",
|
||||
"audio_full_female_10s.wav",
|
||||
null,
|
||||
null
|
||||
]
|
||||
@@ -458,8 +387,8 @@
|
||||
"Node name for S&R": "LoadImageMask"
|
||||
},
|
||||
"widgets_values": [
|
||||
"input_p2_mask.png",
|
||||
"red",
|
||||
"codex_two_person_input.png",
|
||||
"blue",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
@@ -493,77 +422,7 @@
|
||||
"Node name for S&R": "LoadImageMask"
|
||||
},
|
||||
"widgets_values": [
|
||||
"input_p1_mask.png",
|
||||
"red",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"type": "LoadAudio",
|
||||
"pos": [
|
||||
-1468.0582275390625,
|
||||
-386.4920654296875
|
||||
],
|
||||
"size": [
|
||||
274.080078125,
|
||||
136
|
||||
],
|
||||
"flags": {
|
||||
"collapsed": true
|
||||
},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "AUDIO",
|
||||
"type": "AUDIO",
|
||||
"links": [
|
||||
44
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadAudio"
|
||||
},
|
||||
"widgets_values": [
|
||||
"15.wav",
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"type": "LoadImageMask",
|
||||
"pos": [
|
||||
-1477.0850830078125,
|
||||
-332.2462463378906
|
||||
],
|
||||
"size": [
|
||||
274.080078125,
|
||||
318
|
||||
],
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
45
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImageMask"
|
||||
},
|
||||
"widgets_values": [
|
||||
"input_p3_mask.png",
|
||||
"codex_two_person_input.png",
|
||||
"red",
|
||||
"image"
|
||||
]
|
||||
@@ -649,8 +508,7 @@
|
||||
"name": "talk_objects_config",
|
||||
"type": "TALK_OBJECTS_CONFIG",
|
||||
"links": [
|
||||
42,
|
||||
48
|
||||
49
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -744,50 +602,7 @@
|
||||
"Node name for S&R": "LightX2VTalkObjectInput"
|
||||
},
|
||||
"widgets_values": [
|
||||
"person_1",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"type": "LightX2VTalkObjectInput",
|
||||
"pos": [
|
||||
-937.0840454101562,
|
||||
-414.41925048828125
|
||||
],
|
||||
"size": [
|
||||
317.0162048339844,
|
||||
102
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "audio",
|
||||
"shape": 7,
|
||||
"type": "AUDIO",
|
||||
"link": 44
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": 45
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "talk_object",
|
||||
"type": "TALK_OBJECT",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LightX2VTalkObjectInput"
|
||||
},
|
||||
"widgets_values": [
|
||||
"person_1",
|
||||
"person_2",
|
||||
true
|
||||
]
|
||||
},
|
||||
@@ -821,59 +636,21 @@
|
||||
"Node name for S&R": "LoadAudio"
|
||||
},
|
||||
"widgets_values": [
|
||||
"p1.mp3",
|
||||
"male_10s.wav",
|
||||
null,
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"type": "SetNode",
|
||||
"id": 51,
|
||||
"type": "LightX2VMemoryOptimization",
|
||||
"pos": [
|
||||
-199.871337890625,
|
||||
-578.7578735351562
|
||||
-1750,
|
||||
420
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 18,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "TALK_OBJECTS_CONFIG",
|
||||
"type": "TALK_OBJECTS_CONFIG",
|
||||
"link": 48
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "*",
|
||||
"type": "*",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"title": "Set_talk_objects",
|
||||
"properties": {
|
||||
"previousName": "talk_objects"
|
||||
},
|
||||
"widgets_values": [
|
||||
"talk_objects"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"type": "GetNode",
|
||||
"pos": [
|
||||
-1453.525390625,
|
||||
249.66848754882812
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
60
|
||||
290,
|
||||
420
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
@@ -881,20 +658,32 @@
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "TALK_OBJECTS_CONFIG",
|
||||
"type": "TALK_OBJECTS_CONFIG",
|
||||
"name": "memory_config",
|
||||
"type": "MEMORY_CONFIG",
|
||||
"links": [
|
||||
49
|
||||
50
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "Get_talk_objects",
|
||||
"properties": {},
|
||||
"properties": {
|
||||
"Node name for S&R": "LightX2VMemoryOptimization"
|
||||
},
|
||||
"widgets_values": [
|
||||
"talk_objects"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
false,
|
||||
100,
|
||||
true,
|
||||
true,
|
||||
"block",
|
||||
1.0,
|
||||
true,
|
||||
"model",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -920,24 +709,16 @@
|
||||
0,
|
||||
12,
|
||||
0,
|
||||
"COMBINED_CONFIG"
|
||||
"PREPARED_CONFIG"
|
||||
],
|
||||
[
|
||||
14,
|
||||
19,
|
||||
0,
|
||||
12,
|
||||
1,
|
||||
11,
|
||||
6,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
15,
|
||||
20,
|
||||
0,
|
||||
12,
|
||||
2,
|
||||
"AUDIO"
|
||||
],
|
||||
[
|
||||
18,
|
||||
12,
|
||||
@@ -994,30 +775,6 @@
|
||||
1,
|
||||
"TALK_OBJECT"
|
||||
],
|
||||
[
|
||||
42,
|
||||
43,
|
||||
0,
|
||||
34,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
44,
|
||||
41,
|
||||
0,
|
||||
46,
|
||||
0,
|
||||
"AUDIO"
|
||||
],
|
||||
[
|
||||
45,
|
||||
42,
|
||||
0,
|
||||
46,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
47,
|
||||
47,
|
||||
@@ -1026,21 +783,21 @@
|
||||
0,
|
||||
"AUDIO"
|
||||
],
|
||||
[
|
||||
48,
|
||||
43,
|
||||
0,
|
||||
48,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
49,
|
||||
50,
|
||||
43,
|
||||
0,
|
||||
11,
|
||||
5,
|
||||
"TALK_OBJECTS_CONFIG"
|
||||
],
|
||||
[
|
||||
50,
|
||||
51,
|
||||
0,
|
||||
11,
|
||||
3,
|
||||
"MEMORY_CONFIG"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"11": {
|
||||
"inputs": {
|
||||
"audio": "audio_full_female_10s.wav",
|
||||
"start_time": 0,
|
||||
"duration": 0
|
||||
},
|
||||
"class_type": "VHS_LoadAudioUpload",
|
||||
"_meta": {
|
||||
"title": "Load Audio (Upload)🎥🅥🅗🅢"
|
||||
}
|
||||
},
|
||||
"13": {
|
||||
"inputs": {
|
||||
"prompt": "A person speaks naturally to the camera, with clear lip movement and subtle facial expressions.",
|
||||
"negative_prompt": "",
|
||||
"inference_config": [
|
||||
"14",
|
||||
0
|
||||
],
|
||||
"image": [
|
||||
"20",
|
||||
0
|
||||
],
|
||||
"audio": [
|
||||
"11",
|
||||
0
|
||||
],
|
||||
"memory_config": [
|
||||
"16",
|
||||
0
|
||||
],
|
||||
"quantization_config": [
|
||||
"17",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "LightX2VConfigCombinerV2",
|
||||
"_meta": {
|
||||
"title": "LightX2V Config Combiner V2"
|
||||
}
|
||||
},
|
||||
"14": {
|
||||
"inputs": {
|
||||
"model_cls": "seko_talk",
|
||||
"model_name": "Wan2.1-R2V0909-Audio-14B-720P-fp8",
|
||||
"task": "s2v",
|
||||
"infer_steps": 4,
|
||||
"seed": 4221706066,
|
||||
"cfg_scale": 1,
|
||||
"cfg_scale2": 1,
|
||||
"sample_shift": 5,
|
||||
"height": 1280,
|
||||
"width": 720,
|
||||
"duration": 5,
|
||||
"attention_type": "sage_attn2",
|
||||
"denoising_steps": "",
|
||||
"resize_mode": "adaptive",
|
||||
"fixed_area": "720p",
|
||||
"segment_length": 81,
|
||||
"prev_frame_length": 5,
|
||||
"use_tiny_vae": false
|
||||
},
|
||||
"class_type": "LightX2VInferenceConfig",
|
||||
"_meta": {
|
||||
"title": "LightX2V Inference Config"
|
||||
}
|
||||
},
|
||||
"15": {
|
||||
"inputs": {
|
||||
"prepared_config": [
|
||||
"13",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "LightX2VModularInferenceV2",
|
||||
"_meta": {
|
||||
"title": "LightX2V Modular Inference V2"
|
||||
}
|
||||
},
|
||||
"16": {
|
||||
"inputs": {
|
||||
"enable_rotary_chunk": false,
|
||||
"rotary_chunk_size": 100,
|
||||
"clean_cuda_cache": true,
|
||||
"cpu_offload": true,
|
||||
"offload_granularity": "block",
|
||||
"offload_ratio": 1.0,
|
||||
"t5_cpu_offload": true,
|
||||
"t5_offload_granularity": "model",
|
||||
"audio_encoder_cpu_offload": true,
|
||||
"audio_adapter_cpu_offload": true,
|
||||
"vae_cpu_offload": true,
|
||||
"use_tiling_vae": true,
|
||||
"lazy_load": false,
|
||||
"unload_after_inference": false
|
||||
},
|
||||
"class_type": "LightX2VMemoryOptimization",
|
||||
"_meta": {
|
||||
"title": "LightX2V Memory Optimization"
|
||||
}
|
||||
},
|
||||
"17": {
|
||||
"inputs": {
|
||||
"dit_quant_scheme": "fp8-sgl",
|
||||
"t5_quant_scheme": "fp8-sgl",
|
||||
"clip_quant_scheme": "fp8-sgl",
|
||||
"adapter_quant_scheme": "fp8-sgl"
|
||||
},
|
||||
"class_type": "LightX2VQuantization",
|
||||
"_meta": {
|
||||
"title": "LightX2V Quantization"
|
||||
}
|
||||
},
|
||||
"18": {
|
||||
"inputs": {
|
||||
"frame_rate": 16,
|
||||
"loop_count": 0,
|
||||
"filename_prefix": "lightx2v_sekotalk/static",
|
||||
"format": "video/h264-mp4",
|
||||
"pix_fmt": "yuv420p",
|
||||
"crf": 19,
|
||||
"save_metadata": false,
|
||||
"trim_to_audio": false,
|
||||
"pingpong": false,
|
||||
"save_output": true,
|
||||
"images": [
|
||||
"15",
|
||||
0
|
||||
],
|
||||
"audio": [
|
||||
"15",
|
||||
1
|
||||
]
|
||||
},
|
||||
"class_type": "VHS_VideoCombine",
|
||||
"_meta": {
|
||||
"title": "Video Combine 🎥🅥🅗🅢"
|
||||
}
|
||||
},
|
||||
"20": {
|
||||
"inputs": {
|
||||
"image": "codex_image_20260922_135831_1323a530_B.jpg"
|
||||
},
|
||||
"class_type": "LoadImage",
|
||||
"_meta": {
|
||||
"title": "Load Image"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,4 +202,4 @@
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "2ac26f51-8427-4fd5-a63b-8c0c35716909",
|
||||
"revision": 0,
|
||||
"last_node_id": 6,
|
||||
"last_link_id": 10,
|
||||
"last_node_id": 5,
|
||||
"last_link_id": 11,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
@@ -41,19 +41,50 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "LightX2VInputVideoPath",
|
||||
"pos": [
|
||||
-554.2465629475984,
|
||||
172.59098534531842
|
||||
],
|
||||
"size": [
|
||||
280,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "video_path",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
11
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LightX2VInputVideoPath"
|
||||
},
|
||||
"widgets_values": [
|
||||
"飞书20260721-124954.mp4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "LightX2VSwiftVRSampler",
|
||||
"type": "LightX2VSwiftVRFileSampler",
|
||||
"pos": [
|
||||
410.84880341840596,
|
||||
119.34257282956952
|
||||
],
|
||||
"size": [
|
||||
337.3125,
|
||||
320
|
||||
280
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -62,25 +93,12 @@
|
||||
"link": 4
|
||||
},
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 7
|
||||
},
|
||||
{
|
||||
"name": "source_fps",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "source_fps"
|
||||
},
|
||||
"link": 9
|
||||
"name": "video_path",
|
||||
"type": "STRING",
|
||||
"link": 11
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "filename",
|
||||
"type": "STRING",
|
||||
@@ -90,12 +108,10 @@
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LightX2VSwiftVRSampler"
|
||||
"Node name for S&R": "LightX2VSwiftVRFileSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
1080,
|
||||
16,
|
||||
true,
|
||||
"lightx2v_swiftvr_api/SwiftVR",
|
||||
"libx265",
|
||||
60,
|
||||
@@ -114,19 +130,13 @@
|
||||
703.328125
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "filename",
|
||||
"type": "STRING",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "audio",
|
||||
"shape": 7,
|
||||
"type": "AUDIO",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -141,173 +151,8 @@
|
||||
},
|
||||
"widgets_values": [
|
||||
true,
|
||||
true
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "VHS_LoadVideo",
|
||||
"pos": [
|
||||
-554.2465629475984,
|
||||
172.59098534531842
|
||||
],
|
||||
"size": [
|
||||
260.421875,
|
||||
761.6875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "meta_batch",
|
||||
"shape": 7,
|
||||
"type": "VHS_BatchManager",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"shape": 7,
|
||||
"type": "VAE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
7
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "frame_count",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"links": [
|
||||
10
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "video_info",
|
||||
"type": "VHS_VIDEOINFO",
|
||||
"links": [
|
||||
8
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VHS_LoadVideo"
|
||||
},
|
||||
"widgets_values": {
|
||||
"video": "飞书20260721-124954.mp4",
|
||||
"force_rate": 0,
|
||||
"custom_width": 0,
|
||||
"custom_height": 0,
|
||||
"frame_load_cap": 0,
|
||||
"skip_first_frames": 0,
|
||||
"select_every_nth": 1,
|
||||
"format": "AnimateDiff",
|
||||
"videopreview": {
|
||||
"hidden": false,
|
||||
"paused": false,
|
||||
"params": {
|
||||
"filename": "飞书20260721-124954.mp4",
|
||||
"type": "input",
|
||||
"format": "video/mp4",
|
||||
"force_rate": 0,
|
||||
"custom_width": 0,
|
||||
"custom_height": 0,
|
||||
"frame_load_cap": 0,
|
||||
"skip_first_frames": 0,
|
||||
"select_every_nth": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "VHS_VideoInfo",
|
||||
"pos": [
|
||||
-245.90446161258,
|
||||
309.43756146966683
|
||||
],
|
||||
"size": [
|
||||
233.453125,
|
||||
288
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_info",
|
||||
"type": "VHS_VIDEOINFO",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "source_fps🟨",
|
||||
"type": "FLOAT",
|
||||
"links": [
|
||||
9
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "source_frame_count🟨",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "source_duration🟨",
|
||||
"type": "FLOAT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "source_width🟨",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "source_height🟨",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "loaded_fps🟦",
|
||||
"type": "FLOAT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "loaded_frame_count🟦",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "loaded_duration🟦",
|
||||
"type": "FLOAT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "loaded_width🟦",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "loaded_height🟦",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VHS_VideoInfo"
|
||||
},
|
||||
"widgets_values": {}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -322,42 +167,18 @@
|
||||
[
|
||||
6,
|
||||
3,
|
||||
1,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
7,
|
||||
11,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
8,
|
||||
5,
|
||||
3,
|
||||
6,
|
||||
0,
|
||||
"VHS_VIDEOINFO"
|
||||
],
|
||||
[
|
||||
9,
|
||||
6,
|
||||
0,
|
||||
3,
|
||||
2,
|
||||
"FLOAT"
|
||||
],
|
||||
[
|
||||
10,
|
||||
5,
|
||||
2,
|
||||
4,
|
||||
1,
|
||||
"AUDIO"
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
@@ -370,11 +191,7 @@
|
||||
389.36680475087843
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.45.15",
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
"frontendVersion": "1.45.15"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,34 +142,6 @@ class AudioFileHandler(FileHandler):
|
||||
|
||||
return waveform
|
||||
|
||||
def _save_with_wave(self, path: str, waveform: np.ndarray, sample_rate: int):
|
||||
"""Save audio using wave module."""
|
||||
import wave
|
||||
|
||||
with wave.open(path, "wb") as wav_file:
|
||||
wav_file.setnchannels(1 if waveform.ndim == 1 else waveform.shape[-1])
|
||||
wav_file.setsampwidth(2) # 16-bit
|
||||
wav_file.setframerate(sample_rate)
|
||||
|
||||
if waveform.dtype != np.int16:
|
||||
waveform = (waveform * 32767).astype(np.int16)
|
||||
|
||||
wav_file.writeframes(waveform.tobytes())
|
||||
|
||||
def _load_with_wave(self, path: str) -> Tuple[np.ndarray, int]:
|
||||
"""Load audio using wave module."""
|
||||
import wave
|
||||
|
||||
with wave.open(path, "rb") as wav_file:
|
||||
sample_rate = wav_file.getframerate()
|
||||
frames = wav_file.readframes(wav_file.getnframes())
|
||||
waveform = np.frombuffer(frames, dtype=np.int16)
|
||||
|
||||
if wav_file.getnchannels() > 1:
|
||||
waveform = waveform.reshape(-1, wav_file.getnchannels())
|
||||
|
||||
return waveform, sample_rate
|
||||
|
||||
|
||||
class ImageFileHandler(FileHandler):
|
||||
"""Handler for image files."""
|
||||
|
||||
+1
-1
Submodule lightx2v updated: 52e86de7c2...94f0f19459
+13
-5
@@ -30,19 +30,27 @@ def scan_models() -> List[str]:
|
||||
|
||||
|
||||
def support_model_cls_list() -> List[str]:
|
||||
"""Return model classes understood by the bundled LightX2V revision.
|
||||
|
||||
The retired ``*_distill`` entries are kept as compatibility aliases for saved
|
||||
ComfyUI workflows. The current LightX2V runner registry uses the base Wan
|
||||
class plus ``distill_method`` instead; ``bridge.translator.inference``
|
||||
canonicalizes those aliases before the runner is built.
|
||||
"""
|
||||
return [
|
||||
"wan2.1",
|
||||
"wan2.1_distill",
|
||||
"wan2.1_vace",
|
||||
"cogvideox",
|
||||
"seko_talk",
|
||||
"seko_talk_ar",
|
||||
"wan2.2_moe",
|
||||
"wan2.2",
|
||||
"wan2.2_moe_audio",
|
||||
"wan2.2_audio",
|
||||
"wan2.2_moe_distill",
|
||||
"wan2.2_s2v",
|
||||
"qwen_image",
|
||||
"seedvr2",
|
||||
# Legacy workflow values; normalized before build_runner().
|
||||
"wan2.1_distill",
|
||||
"wan2.1_mean_flow_distill",
|
||||
"wan2.2_moe_distill",
|
||||
]
|
||||
|
||||
|
||||
|
||||
+13
-2
@@ -41,6 +41,17 @@ _PATCH_SITES = (
|
||||
)
|
||||
|
||||
|
||||
class _AudioSentinel(str):
|
||||
"""A path-shaped token that releases its registry lease when discarded."""
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
release(str(self))
|
||||
except Exception:
|
||||
# Destructors can run during interpreter shutdown.
|
||||
pass
|
||||
|
||||
|
||||
def register(waveform: torch.Tensor, sample_rate: int) -> str:
|
||||
"""Register a [C, T] float32 waveform; return a sentinel "path" for it."""
|
||||
if waveform.dim() != 2:
|
||||
@@ -50,12 +61,12 @@ def register(waveform: torch.Tensor, sample_rate: int) -> str:
|
||||
stashed = waveform.detach().to(torch.float32).cpu().contiguous()
|
||||
with _LOCK:
|
||||
_REGISTRY[sentinel] = (stashed, int(sample_rate))
|
||||
return sentinel
|
||||
return _AudioSentinel(sentinel)
|
||||
|
||||
|
||||
def release(sentinel: str) -> None:
|
||||
with _LOCK:
|
||||
_REGISTRY.pop(sentinel, None)
|
||||
_REGISTRY.pop(str(sentinel), None)
|
||||
|
||||
|
||||
def is_sentinel(path) -> bool:
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Keep ComfyUI IMAGE inputs in memory while LightX2V reads them.
|
||||
|
||||
LightX2V's runner API names the request field ``image_path`` and accepts a
|
||||
PIL image at the final read boundary. The wrapper used to serialize the first
|
||||
ComfyUI frame to a temporary PNG only to have the runner decode it again. This
|
||||
shim keeps the path-shaped request contract while replacing that disk
|
||||
round-trip with a short-lived in-memory PIL image.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import types
|
||||
import uuid
|
||||
from typing import Dict
|
||||
|
||||
import torch
|
||||
from PIL import Image
|
||||
|
||||
SENTINEL_PREFIX = "<lightx2v-mem-image:"
|
||||
SENTINEL_SUFFIX = ">"
|
||||
|
||||
_REGISTRY: Dict[str, Image.Image] = {}
|
||||
_LOCK = threading.Lock()
|
||||
|
||||
|
||||
class _ImageSentinel(str):
|
||||
"""A path-shaped token that releases its registry lease when discarded.
|
||||
|
||||
The registry keeps a plain ``str`` key rather than this object, so the
|
||||
object can be collected even while the registry still contains the image.
|
||||
This covers failures between Combiner execution and ``generate()`` where
|
||||
the normal inference ``finally`` block has not started yet.
|
||||
"""
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
release(str(self))
|
||||
except Exception:
|
||||
# Destructors can run during interpreter shutdown.
|
||||
pass
|
||||
|
||||
|
||||
def register(images: torch.Tensor) -> str:
|
||||
"""Register the first ComfyUI IMAGE frame and return a path-shaped token."""
|
||||
if not torch.is_tensor(images) or images.ndim != 4:
|
||||
raise ValueError(f"IMAGE must be a [B, H, W, C] tensor; got {type(images).__name__} {getattr(images, 'shape', '')}")
|
||||
if images.shape[-1] not in (1, 3, 4):
|
||||
raise ValueError(f"IMAGE must have 1, 3, or 4 channels; got shape {tuple(images.shape)}")
|
||||
|
||||
frame = images[0].detach().cpu().float().clamp(0.0, 1.0)
|
||||
frame_u8 = (frame.mul(255.0).round()).to(torch.uint8).numpy()
|
||||
if frame_u8.shape[-1] == 1:
|
||||
frame_u8 = frame_u8[..., 0]
|
||||
elif frame_u8.shape[-1] == 4:
|
||||
frame_u8 = frame_u8[..., :3]
|
||||
|
||||
token = f"{SENTINEL_PREFIX}{uuid.uuid4().hex}{SENTINEL_SUFFIX}"
|
||||
image = Image.fromarray(frame_u8)
|
||||
if image.mode != "RGB":
|
||||
image = image.convert("RGB")
|
||||
with _LOCK:
|
||||
_REGISTRY[token] = image
|
||||
return _ImageSentinel(token)
|
||||
|
||||
|
||||
def release(sentinel: str | None) -> None:
|
||||
if not is_sentinel(sentinel):
|
||||
return
|
||||
with _LOCK:
|
||||
_REGISTRY.pop(str(sentinel), None)
|
||||
|
||||
|
||||
def is_sentinel(path) -> bool:
|
||||
return isinstance(path, str) and path.startswith(SENTINEL_PREFIX)
|
||||
|
||||
|
||||
def _lookup(sentinel: str) -> Image.Image:
|
||||
with _LOCK:
|
||||
image = _REGISTRY.get(sentinel)
|
||||
if image is None:
|
||||
raise FileNotFoundError(f"Stale LightX2V in-memory image sentinel: {sentinel}")
|
||||
return image
|
||||
|
||||
|
||||
def install(runner) -> bool:
|
||||
"""Patch one runner's image read boundary; return whether it was patched."""
|
||||
if runner is None or not hasattr(runner, "read_image_input"):
|
||||
return False
|
||||
if hasattr(runner, "_lightx2v_original_read_image_input"):
|
||||
return True
|
||||
|
||||
original = runner.read_image_input
|
||||
|
||||
def _read_image_input(self, image_path):
|
||||
if is_sentinel(image_path):
|
||||
image_path = _lookup(image_path)
|
||||
return original(image_path)
|
||||
|
||||
runner._lightx2v_original_read_image_input = original
|
||||
runner.read_image_input = types.MethodType(_read_image_input, runner)
|
||||
logging.info("_image_shim installed on %s", type(runner).__name__)
|
||||
return True
|
||||
|
||||
|
||||
def install_on_runner_tree(runner) -> int:
|
||||
"""Patch a regular runner or every clip runner inside an rs2v pipeline."""
|
||||
targets = [runner]
|
||||
clip_generators = getattr(runner, "clip_generators", None)
|
||||
if isinstance(clip_generators, dict):
|
||||
targets.extend(clip_generators.values())
|
||||
return sum(1 for target in targets if install(target))
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Small adapters for LightX2V 0.6's startup/request runner API."""
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
def build_runner(startup_data):
|
||||
"""Build a resident LightX2V runner from deployment-time settings."""
|
||||
from ..lightx2v.lightx2v.models.runners.runner_factory import build_runner as _build_runner
|
||||
from ..lightx2v.lightx2v.utils.set_config import build_startup_config
|
||||
|
||||
startup_config = build_startup_config(dict(startup_data))
|
||||
return _build_runner(startup_config)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def temporary_runner_config(runner, values):
|
||||
"""Apply per-call implementation settings while a request is running.
|
||||
|
||||
LightX2V 0.6 keeps deployment settings in a locked ``LockableDict`` and
|
||||
exposes request data through ``prepare_request``. A few legacy ComfyUI
|
||||
controls (SeedVR segmenting/color correction and SwiftVR video encoding)
|
||||
are still implementation settings, so they need a narrow, serialized
|
||||
override instead of being passed as unsupported request fields.
|
||||
"""
|
||||
values = {key: value for key, value in values.items() if value is not None}
|
||||
if not values:
|
||||
yield
|
||||
return
|
||||
|
||||
config = runner.config
|
||||
missing = object()
|
||||
previous = {key: config.get(key, missing) for key in values}
|
||||
unlock = getattr(config, "temporarily_unlocked", None)
|
||||
if unlock is None:
|
||||
raise RuntimeError("LightX2V runner config does not support temporary request settings")
|
||||
|
||||
with unlock():
|
||||
config.update(values)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
for key, value in previous.items():
|
||||
if value is missing:
|
||||
config.pop(key, None)
|
||||
else:
|
||||
config[key] = value
|
||||
|
||||
|
||||
def run_request(runner, request, *, runtime_config=None):
|
||||
"""Validate one modern request and execute it through the runner API."""
|
||||
with temporary_runner_config(runner, runtime_config or {}):
|
||||
input_info = runner.prepare_request(dict(request))
|
||||
return runner.run_request(input_info)
|
||||
+12
-7
@@ -168,19 +168,24 @@ class _BaseConfigCombiner:
|
||||
if image is None:
|
||||
raise ValueError("i2v or s2v or rs2v task requires input image")
|
||||
|
||||
image_np = (image[0].cpu().numpy() * 255).astype(np.uint8)
|
||||
pil_image = Image.fromarray(image_np)
|
||||
temp_path = self.temp_manager.create_temp_file(suffix=".png")
|
||||
pil_image.save(temp_path)
|
||||
config.image_path = temp_path
|
||||
logging.info(f"Image saved to {temp_path}")
|
||||
frame_count = int(image.shape[0]) if hasattr(image, "shape") and image.ndim > 0 else 0
|
||||
if frame_count > 1:
|
||||
logging.warning(
|
||||
"LightX2V uses only the first IMAGE frame as the reference; received %s frames",
|
||||
frame_count,
|
||||
)
|
||||
|
||||
from ._image_shim import register
|
||||
|
||||
config.image_path = register(image)
|
||||
logging.info("Routed ComfyUI IMAGE through the in-memory shim")
|
||||
|
||||
def _save_single_audio_if_needed(self, config, audio):
|
||||
# Route ComfyUI AUDIO straight into the runner via the in-memory shim
|
||||
# (no WAV temp file, no soundfile round-trip). The shim only intercepts
|
||||
# this single-AUDIO path; V3 multi-talker padding still produces real
|
||||
# files since its inputs are external paths/URLs, not ComfyUI tensors.
|
||||
if audio is None or not hasattr(config, "model_cls") or "seko" not in config.model_cls:
|
||||
if audio is None or config.task not in {"s2v", "rs2v"}:
|
||||
return
|
||||
from ._audio_shim import comfyui_audio_to_loader_pair, install, register
|
||||
|
||||
|
||||
+3
-3
@@ -42,7 +42,7 @@ class LightX2VInferenceConfig:
|
||||
["t2v", "i2v", "s2v", "rs2v"],
|
||||
{
|
||||
"default": "i2v",
|
||||
"tooltip": "Task type: text-to-video or image-to-video or reference_image and audio to video (shot)",
|
||||
"tooltip": "Task: t2v text-to-video, i2v image-to-video, s2v image+audio-to-video, or rs2v reference-shot image+audio-to-video",
|
||||
},
|
||||
),
|
||||
"infer_steps": (
|
||||
@@ -136,7 +136,7 @@ class LightX2VInferenceConfig:
|
||||
],
|
||||
{
|
||||
"default": "adaptive",
|
||||
"tooltip": "Adaptive resize input image to target aspect ratio",
|
||||
"tooltip": "Resize policy: adaptive preserves the target area, fixed_shape uses the exact target size, and fixed-area/min-side modes preserve aspect ratio",
|
||||
},
|
||||
),
|
||||
"fixed_area": (
|
||||
@@ -161,7 +161,7 @@ class LightX2VInferenceConfig:
|
||||
"default": 5,
|
||||
"min": 0,
|
||||
"max": 16,
|
||||
"tooltip": "Previous frame overlap for sekotalk models",
|
||||
"tooltip": "Previous-frame overlap for rs2v continuation; s2v uses the model's own initial reference and usually ignores this value",
|
||||
},
|
||||
),
|
||||
"use_tiny_vae": (
|
||||
|
||||
+150
-31
@@ -7,9 +7,36 @@ import torch
|
||||
from comfy.utils import ProgressBar
|
||||
|
||||
from ..config_builder import ConfigBuilder
|
||||
from ..lightx2v.lightx2v.infer import init_runner
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info, update_input_info_from_dict
|
||||
from ..lightx2v.lightx2v.utils.set_config import auto_calc_config, set_args2config
|
||||
|
||||
# LightX2V 0.6 replaced the resident ``init_runner`` entrypoint and the
|
||||
# ``set_args2config``/``auto_calc_config`` pair with a startup/request split.
|
||||
# Keep the imports optional so the wrapper can select the API at runtime while
|
||||
# still supporting the previous submodule revision.
|
||||
try:
|
||||
from ..lightx2v.lightx2v.infer import init_runner as _legacy_init_runner
|
||||
except ImportError:
|
||||
_legacy_init_runner = None
|
||||
|
||||
try:
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info as _legacy_init_empty_input_info
|
||||
from ..lightx2v.lightx2v.utils.input_info import update_input_info_from_dict as _legacy_update_input_info_from_dict
|
||||
except ImportError:
|
||||
_legacy_init_empty_input_info = None
|
||||
_legacy_update_input_info_from_dict = None
|
||||
|
||||
try:
|
||||
from ..lightx2v.lightx2v.utils.set_config import auto_calc_config as _legacy_auto_calc_config
|
||||
from ..lightx2v.lightx2v.utils.set_config import set_args2config as _legacy_set_args2config
|
||||
except ImportError:
|
||||
_legacy_auto_calc_config = None
|
||||
_legacy_set_args2config = None
|
||||
|
||||
try:
|
||||
from ..lightx2v.lightx2v.models.runners.runner_factory import build_runner as _modern_build_runner
|
||||
from ..lightx2v.lightx2v.utils.set_config import build_startup_config as _modern_build_startup_config
|
||||
except ImportError:
|
||||
_modern_build_runner = None
|
||||
_modern_build_startup_config = None
|
||||
|
||||
|
||||
class LightX2VModularInferenceV2:
|
||||
@@ -62,16 +89,31 @@ class LightX2VModularInferenceV2:
|
||||
# never get merged — rs2v_infer then KeyErrors on config["vae_stride"].
|
||||
if "task" not in config:
|
||||
config["task"] = "rs2v"
|
||||
# set_config = set_args2config + auto_calc_config. set_args2config strips
|
||||
# any key that's part of an InputInfo dataclass (target_video_length,
|
||||
# infer_steps, seed, ...). For CLI runs auto_calc_config recovers them
|
||||
# by merging --config_json, but we have no external JSON, so
|
||||
# auto_calc_config's `config["target_video_length"]` access KeyErrors.
|
||||
# Inject the bridge between set_args2config and auto_calc_config.
|
||||
target_video_length = config.get("target_video_length", config.get("segment_length", config.get("video_length", 81)))
|
||||
formatted = set_args2config(config)
|
||||
formatted["target_video_length"] = target_video_length
|
||||
formatted = auto_calc_config(formatted)
|
||||
# LightX2V 0.6 uses the same startup builder as the regular runner;
|
||||
# older revisions need the set_args2config + auto_calc_config pair.
|
||||
# The shot runner's load_clip_configs() calls build_startup_config
|
||||
# again, so passing the modern startup shape here is safe and keeps
|
||||
# model config/vae metadata loading in LightX2V itself.
|
||||
if _modern_build_startup_config is not None:
|
||||
startup_data = dict(config)
|
||||
startup_data["size"] = [
|
||||
int(config.get("target_height", config.get("height", 480))),
|
||||
int(config.get("target_width", config.get("width", 832))),
|
||||
]
|
||||
startup_data["num_frames"] = int(config.get("target_video_length", config.get("video_length", config.get("num_frames", 81))))
|
||||
formatted = _modern_build_startup_config(startup_data)
|
||||
else:
|
||||
# set_args2config strips any key that's part of an InputInfo
|
||||
# dataclass (target_video_length, infer_steps, seed, ...). For
|
||||
# CLI runs auto_calc_config recovers them by merging
|
||||
# --config_json; our in-memory flow has no external JSON, so
|
||||
# bridge target_video_length back before auto_calc_config.
|
||||
target_video_length = config.get("target_video_length", config.get("segment_length", config.get("video_length", 81)))
|
||||
if _legacy_set_args2config is None or _legacy_auto_calc_config is None:
|
||||
raise RuntimeError("No compatible LightX2V shot-runner config API found")
|
||||
formatted = _legacy_set_args2config(config)
|
||||
formatted["target_video_length"] = target_video_length
|
||||
formatted = _legacy_auto_calc_config(formatted)
|
||||
main_cfg = {
|
||||
"lightx2v_path": "",
|
||||
"clip_configs": [
|
||||
@@ -88,10 +130,61 @@ class LightX2VModularInferenceV2:
|
||||
|
||||
return load_clip_configs(main_cfg)
|
||||
|
||||
@staticmethod
|
||||
def _build_modern_startup_config(config):
|
||||
"""Translate wrapper shape fields into LightX2V 0.6 startup fields."""
|
||||
if _modern_build_startup_config is None or _modern_build_runner is None:
|
||||
raise RuntimeError("The installed LightX2V revision does not provide the modern startup API")
|
||||
|
||||
startup_data = dict(config)
|
||||
startup_data["size"] = [
|
||||
int(config.get("target_height", config.get("height", 480))),
|
||||
int(config.get("target_width", config.get("width", 832))),
|
||||
]
|
||||
startup_data["num_frames"] = int(config.get("target_video_length", config.get("video_length", config.get("num_frames", 81))))
|
||||
return _modern_build_startup_config(startup_data)
|
||||
|
||||
@staticmethod
|
||||
def _build_modern_request(config, runner):
|
||||
"""Build only fields accepted by the current runner's request schema."""
|
||||
task = config.get("task")
|
||||
supported_fields = runner.get_supported_request_fields(task)
|
||||
request = {}
|
||||
for field in supported_fields:
|
||||
if field == "size":
|
||||
# SekoTalk's fixed-area/min-side modes derive the output size
|
||||
# from the model's resize policy. The 0.6 request validator
|
||||
# rejects an explicit size unless the request selected
|
||||
# fixed_shape; keep the wrapper's target dimensions in startup
|
||||
# config but do not send them as a per-request override.
|
||||
if config.get("model_cls", "").startswith("seko") and config.get("resize_mode") != "fixed_shape":
|
||||
continue
|
||||
request[field] = [
|
||||
int(config.get("target_height", config.get("height", 480))),
|
||||
int(config.get("target_width", config.get("width", 832))),
|
||||
]
|
||||
elif field == "num_frames":
|
||||
request[field] = int(config.get("target_video_length", config.get("video_length", config.get("num_frames", 81))))
|
||||
elif field in config:
|
||||
request[field] = config[field]
|
||||
|
||||
request["task"] = task
|
||||
request["return_result_tensor"] = True
|
||||
request["save_result_path"] = ""
|
||||
return request
|
||||
|
||||
def generate(self, prepared_config):
|
||||
"""Run inference with prepared configuration."""
|
||||
|
||||
config = prepared_config
|
||||
from ._image_shim import install_on_runner_tree as _install_image_shim
|
||||
from ._image_shim import is_sentinel as _is_image_sentinel
|
||||
from ._image_shim import release as _release_image_sentinel
|
||||
|
||||
_image_sentinel = config.get("image_path") if isinstance(config, dict) else getattr(config, "image_path", None)
|
||||
if not _is_image_sentinel(_image_sentinel):
|
||||
_image_sentinel = None
|
||||
|
||||
# Combiner may have stashed the input AUDIO in the in-memory shim and
|
||||
# set audio_path to a sentinel — release it on exit so the tensor isn't
|
||||
# retained across runs (one ComfyUI graph tick = one sentinel).
|
||||
@@ -108,7 +201,7 @@ class LightX2VModularInferenceV2:
|
||||
current_runner = getattr(self.__class__, "_current_runner", None)
|
||||
current_config_hash = getattr(self.__class__, "_current_config_hash", None)
|
||||
|
||||
needs_reinit = current_runner is None or current_config_hash != config_hash or getattr(config, "lazy_load", False)
|
||||
needs_reinit = current_runner is None or current_config_hash != config_hash or config.get("lazy_load", False)
|
||||
|
||||
logging.info(f"Needs reinit: {needs_reinit}, old config hash: {current_config_hash}, new config hash: {config_hash}")
|
||||
if needs_reinit:
|
||||
@@ -124,19 +217,22 @@ class LightX2VModularInferenceV2:
|
||||
shot_cfg = self._build_rs2v_shot_config(config)
|
||||
self.__class__._current_runner = ShotRS2VPipeline(shot_cfg)
|
||||
else:
|
||||
# set_args2config strips InputInfo-dataclass keys (target_video_length,
|
||||
# infer_steps, ...). CLI flows recover them via --config_json merging
|
||||
# inside auto_calc_config; our in-memory flow has no external JSON, so
|
||||
# we bridge target_video_length manually between the two halves so
|
||||
# auto_calc_config:194's modulo check on s2v/i2v doesn't KeyError.
|
||||
target_video_length = config.get(
|
||||
"target_video_length",
|
||||
config.get("segment_length", config.get("video_length", 81)),
|
||||
)
|
||||
formatted_config = set_args2config(config)
|
||||
formatted_config["target_video_length"] = target_video_length
|
||||
formatted_config = auto_calc_config(formatted_config)
|
||||
self.__class__._current_runner = init_runner(formatted_config)
|
||||
if _modern_build_runner is not None and _modern_build_startup_config is not None:
|
||||
startup_config = self._build_modern_startup_config(config)
|
||||
self.__class__._current_runner = _modern_build_runner(startup_config)
|
||||
else:
|
||||
if _legacy_set_args2config is None or _legacy_auto_calc_config is None or _legacy_init_runner is None:
|
||||
raise RuntimeError("No compatible LightX2V runner API found in the installed LightX2V submodule")
|
||||
# The legacy API strips InputInfo-dataclass keys. Bridge
|
||||
# target_video_length back before auto_calc_config.
|
||||
target_video_length = config.get(
|
||||
"target_video_length",
|
||||
config.get("segment_length", config.get("video_length", 81)),
|
||||
)
|
||||
formatted_config = _legacy_set_args2config(config)
|
||||
formatted_config["target_video_length"] = target_video_length
|
||||
formatted_config = _legacy_auto_calc_config(formatted_config)
|
||||
self.__class__._current_runner = _legacy_init_runner(formatted_config)
|
||||
self.__class__._current_config_hash = config_hash
|
||||
|
||||
progress = ProgressBar(100)
|
||||
@@ -146,6 +242,10 @@ class LightX2VModularInferenceV2:
|
||||
|
||||
current_runner = self.__class__._current_runner
|
||||
|
||||
if _image_sentinel is not None:
|
||||
if _install_image_shim(current_runner) == 0:
|
||||
raise RuntimeError("LightX2V IMAGE input shim could not find a runner image reader")
|
||||
|
||||
if hasattr(current_runner, "set_progress_callback"):
|
||||
current_runner.set_progress_callback(update_progress)
|
||||
|
||||
@@ -153,10 +253,27 @@ class LightX2VModularInferenceV2:
|
||||
config["save_result_path"] = ""
|
||||
config["negative_prompt"] = config.get("negative_prompt", "")
|
||||
if config.get("task") == "rs2v":
|
||||
result_dict = current_runner.run_pipeline(config)
|
||||
# ShotRS2VPipeline delegates request validation to its clip
|
||||
# runner. Passing the prepared config wholesale worked with
|
||||
# the old InputInfo path, but LightX2V 0.6 rejects startup
|
||||
# fields (model paths, quantization flags, etc.) as request
|
||||
# fields. Filter the request against the modern runner schema
|
||||
# before handing it to the shot pipeline.
|
||||
if _modern_build_runner is not None and _modern_build_startup_config is not None:
|
||||
shot_runner = current_runner.clip_generators["rs2v_clip"]
|
||||
request_data = self._build_modern_request(config, shot_runner)
|
||||
result_dict = current_runner.run_pipeline(request_data)
|
||||
else:
|
||||
result_dict = current_runner.run_pipeline(config)
|
||||
elif _modern_build_runner is not None and _modern_build_startup_config is not None:
|
||||
request_data = self._build_modern_request(config, current_runner)
|
||||
input_info = current_runner.prepare_request(request_data)
|
||||
result_dict = current_runner.run_request(input_info)
|
||||
else:
|
||||
input_data = init_empty_input_info(config.task)
|
||||
update_input_info_from_dict(input_data, config)
|
||||
if _legacy_init_empty_input_info is None or _legacy_update_input_info_from_dict is None:
|
||||
raise RuntimeError("No compatible LightX2V input-info API found in the installed LightX2V submodule")
|
||||
input_data = _legacy_init_empty_input_info(config.task)
|
||||
_legacy_update_input_info_from_dict(input_data, config)
|
||||
current_runner.set_config(config)
|
||||
result_dict = current_runner.run_pipeline(input_data)
|
||||
|
||||
@@ -168,7 +285,7 @@ class LightX2VModularInferenceV2:
|
||||
if images.dtype != torch.float32:
|
||||
images = images.float()
|
||||
|
||||
if getattr(config, "unload_after_inference", False):
|
||||
if config.get("unload_after_inference", False):
|
||||
current_runner = None # drop local ref so __del__ can run
|
||||
self._release_runner()
|
||||
else:
|
||||
@@ -181,5 +298,7 @@ class LightX2VModularInferenceV2:
|
||||
logging.error(f"Error during inference: {e}")
|
||||
raise
|
||||
finally:
|
||||
if _image_sentinel is not None:
|
||||
_release_image_sentinel(_image_sentinel)
|
||||
if _audio_sentinel is not None:
|
||||
_release_audio_sentinel(_audio_sentinel)
|
||||
|
||||
+46
-86
@@ -13,7 +13,6 @@ IMAGE tensor (no temp file, no re-encode); the runner's segmenting logic still
|
||||
runs and slices our in-memory tensor.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import gc
|
||||
import logging
|
||||
import math
|
||||
@@ -30,6 +29,7 @@ import folder_paths
|
||||
import torch
|
||||
from comfy.utils import ProgressBar
|
||||
|
||||
from ._lightx2v_runner import build_runner, run_request
|
||||
from .file_input import probe_video_file, resolve_input_video_path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -209,7 +209,7 @@ def _install_tensor_input_shim(runner, frames_u8, fps):
|
||||
|
||||
def _probe_video(self, video_path): # noqa: ARG001
|
||||
total = self._tensor_input.shape[0]
|
||||
self._set_output_fps(self._tensor_input_fps)
|
||||
self.set_output_fps(self._tensor_input_fps)
|
||||
return total, self._tensor_input_fps, None
|
||||
|
||||
def _read_video_segment(self, video_path, start_idx, end_idx): # noqa: ARG001
|
||||
@@ -330,9 +330,6 @@ class LightX2VSeedVR2Loader:
|
||||
vae_causal_slice_size,
|
||||
vae_memory_limit_gb,
|
||||
):
|
||||
from ..lightx2v.lightx2v.infer import init_runner
|
||||
from ..lightx2v.lightx2v.utils.set_config import set_config
|
||||
|
||||
model_dir = _seedvr2_model_dir()
|
||||
if ckpt_name == "None":
|
||||
raise FileNotFoundError(f"No .safetensors checkpoints found in {model_dir}")
|
||||
@@ -376,8 +373,7 @@ class LightX2VSeedVR2Loader:
|
||||
else:
|
||||
config["dit_original_ckpt"] = str(ckpt_path)
|
||||
|
||||
formatted = set_config(argparse.Namespace(**config))
|
||||
runner = init_runner(formatted)
|
||||
runner = build_runner(config)
|
||||
logger.info(
|
||||
"[SeedVR2Loader] loaded %s (%s); cpu_offload=%s, tile_vae=%s, tile=%s, overlap=%s, slice=%s, mem_limit=%sGiB",
|
||||
ckpt_name,
|
||||
@@ -467,10 +463,11 @@ class LightX2VSeedVR2Sampler:
|
||||
filename_prefix,
|
||||
color_fix,
|
||||
):
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info, update_input_info_from_dict
|
||||
|
||||
runner = model["runner"]
|
||||
|
||||
if int(infer_steps) != 1:
|
||||
raise ValueError("SeedVR2 requires infer_steps=1")
|
||||
|
||||
if images.dim() != 4 or images.shape[-1] not in (3, 4):
|
||||
raise ValueError(f"Expected IMAGE [T, H, W, C], got shape {tuple(images.shape)}")
|
||||
# ComfyUI IMAGE: [T, H, W, C] float[0,1] → [T, C, H, W] uint8 (read_video's contract)
|
||||
@@ -496,49 +493,31 @@ class LightX2VSeedVR2Sampler:
|
||||
full_path, output_file, output_subfolder = _prepare_output_video(filename_prefix, target_width, target_height)
|
||||
save_path = str(full_path)
|
||||
|
||||
# runner.config is a LockableDict (locked after init); set_config uses temporarily_unlocked.
|
||||
runner.set_config(
|
||||
{
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_height": int(target_height),
|
||||
"target_width": int(target_width),
|
||||
"target_video_length": int(segment_length), # vestigial for SR; keep aligned with segment_length
|
||||
"sr_segment_length": int(segment_length),
|
||||
"sr_overlap": int(segment_overlap),
|
||||
"infer_steps": int(infer_steps),
|
||||
"seed": int(seed),
|
||||
"fps": float(source_fps),
|
||||
"video_path": "<tensor>", # truthy sentinel so segmenting logic runs; shim bypasses file I/O
|
||||
"image_path": "",
|
||||
"prompt": "",
|
||||
"negative_prompt": "",
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": not bool(save_to_output_file),
|
||||
"color_fix": str(color_fix),
|
||||
}
|
||||
)
|
||||
|
||||
input_info = init_empty_input_info("sr")
|
||||
update_input_info_from_dict(
|
||||
input_info,
|
||||
{
|
||||
"video_path": "<tensor>",
|
||||
"image_path": "",
|
||||
"prompt": "",
|
||||
"negative_prompt": "",
|
||||
"seed": int(seed),
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": not bool(save_to_output_file),
|
||||
},
|
||||
)
|
||||
request = {
|
||||
"task": "sr",
|
||||
"video_path": "<tensor>",
|
||||
"image_path": "",
|
||||
"size": [int(target_height), int(target_width)],
|
||||
"seed": int(seed),
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": not bool(save_to_output_file),
|
||||
}
|
||||
runtime_config = {
|
||||
"sr_segment_length": int(segment_length),
|
||||
"sr_overlap": int(segment_overlap),
|
||||
"fps": float(source_fps),
|
||||
"color_fix": str(color_fix),
|
||||
"stream_save_video": bool(save_to_output_file),
|
||||
}
|
||||
|
||||
progress = ProgressBar(100)
|
||||
if hasattr(runner, "set_progress_callback"):
|
||||
runner.set_progress_callback(lambda cur, _tot: progress.update_absolute(cur))
|
||||
|
||||
try:
|
||||
result = runner.run_pipeline(input_info)
|
||||
with _SEEDVR_RUN_LOCK:
|
||||
result = run_request(runner, request, runtime_config=runtime_config)
|
||||
finally:
|
||||
_clear_tensor_input_shim(runner)
|
||||
torch.cuda.empty_cache()
|
||||
@@ -628,8 +607,6 @@ class LightX2VSeedVR2FileSampler:
|
||||
filename_prefix,
|
||||
color_fix,
|
||||
):
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info, update_input_info_from_dict
|
||||
|
||||
input_path = resolve_input_video_path(video_path)
|
||||
source_width, source_height, source_fps = probe_video_file(input_path)
|
||||
effective_fps = source_fps if source_fps > 0 else 16.0
|
||||
@@ -647,20 +624,25 @@ class LightX2VSeedVR2FileSampler:
|
||||
|
||||
full_path, output_file, output_subfolder = _prepare_output_video(filename_prefix, target_width, target_height)
|
||||
save_path = str(full_path)
|
||||
input_info = init_empty_input_info("sr")
|
||||
update_input_info_from_dict(
|
||||
input_info,
|
||||
{
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"prompt": "",
|
||||
"negative_prompt": "",
|
||||
"seed": int(seed),
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
},
|
||||
)
|
||||
if int(infer_steps) != 1:
|
||||
raise ValueError("SeedVR2 requires infer_steps=1")
|
||||
request = {
|
||||
"task": "sr",
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"size": [int(target_height), int(target_width)],
|
||||
"seed": int(seed),
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
}
|
||||
runtime_config = {
|
||||
"sr_segment_length": int(segment_length),
|
||||
"sr_overlap": int(segment_overlap),
|
||||
"stream_save_video": True,
|
||||
"fps": float(effective_fps),
|
||||
"color_fix": str(color_fix),
|
||||
}
|
||||
|
||||
runner = model["runner"]
|
||||
progress = ProgressBar(100)
|
||||
@@ -677,31 +659,9 @@ class LightX2VSeedVR2FileSampler:
|
||||
)
|
||||
try:
|
||||
with _SEEDVR_RUN_LOCK:
|
||||
_clear_tensor_input_shim(runner)
|
||||
runner.set_config(
|
||||
{
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_height": int(target_height),
|
||||
"target_width": int(target_width),
|
||||
"target_video_length": int(segment_length),
|
||||
"sr_segment_length": int(segment_length),
|
||||
"sr_overlap": int(segment_overlap),
|
||||
"stream_save_video": True,
|
||||
"infer_steps": int(infer_steps),
|
||||
"seed": int(seed),
|
||||
"fps": float(effective_fps),
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"prompt": "",
|
||||
"negative_prompt": "",
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
"color_fix": str(color_fix),
|
||||
}
|
||||
)
|
||||
if hasattr(runner, "set_progress_callback"):
|
||||
runner.set_progress_callback(lambda current, _total: progress.update_absolute(current))
|
||||
runner.run_pipeline(input_info)
|
||||
run_request(runner, request, runtime_config=runtime_config)
|
||||
finally:
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
@@ -741,7 +701,7 @@ class LightX2VOutputVideoPreview:
|
||||
"AUDIO",
|
||||
{"tooltip": "Optional audio from Load Video/Get Video Components to merge into the output MP4."},
|
||||
),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
|
||||
+37
-70
@@ -1,6 +1,5 @@
|
||||
"""SwiftVR restoration nodes for ComfyUI."""
|
||||
|
||||
import argparse
|
||||
import gc
|
||||
import logging
|
||||
import tempfile
|
||||
@@ -12,6 +11,7 @@ import folder_paths
|
||||
import torch
|
||||
from comfy.utils import ProgressBar
|
||||
|
||||
from ._lightx2v_runner import build_runner, run_request
|
||||
from .file_input import probe_video_file, resolve_input_video_path
|
||||
from .seedvr import _prepare_output_video
|
||||
|
||||
@@ -81,10 +81,7 @@ def _resolve_output_size(source_width: int, source_height: int, target_short_edg
|
||||
|
||||
scale = target_short_edge / source_short_edge
|
||||
if scale > _MAX_SR_RATIO:
|
||||
raise ValueError(
|
||||
f"SwiftVR scale {scale:.3f}x exceeds the supported maximum {_MAX_SR_RATIO:.1f}x; "
|
||||
f"lower target_short_edge"
|
||||
)
|
||||
raise ValueError(f"SwiftVR scale {scale:.3f}x exceeds the supported maximum {_MAX_SR_RATIO:.1f}x; lower target_short_edge")
|
||||
if source_width <= source_height:
|
||||
output_width = target_short_edge
|
||||
output_height = int(round(source_height * scale))
|
||||
@@ -97,8 +94,7 @@ def _resolve_output_size(source_width: int, source_height: int, target_short_edg
|
||||
output_height = max(2, (output_height + 1) // 2 * 2)
|
||||
if max(output_width, output_height) > _MAX_OUTPUT_DIMENSION:
|
||||
raise ValueError(
|
||||
f"SwiftVR output {output_width}x{output_height} exceeds the maximum supported dimension "
|
||||
f"{_MAX_OUTPUT_DIMENSION}; lower target_short_edge"
|
||||
f"SwiftVR output {output_width}x{output_height} exceeds the maximum supported dimension {_MAX_OUTPUT_DIMENSION}; lower target_short_edge"
|
||||
)
|
||||
return output_height, output_width, scale
|
||||
|
||||
@@ -189,9 +185,6 @@ class LightX2VSwiftVRLoader:
|
||||
CATEGORY = "LightX2V/SwiftVR"
|
||||
|
||||
def load(self, model_name, attention_backend, rope_type, clip_length, dit_overlap, reae_frame_batch_size, use_compile):
|
||||
from ..lightx2v.lightx2v.infer import init_runner
|
||||
from ..lightx2v.lightx2v.utils.set_config import set_config
|
||||
|
||||
model_path = _swiftvr_model_root() / model_name
|
||||
if model_name == "None" or not _is_swiftvr_model(model_path):
|
||||
missing = [str(model_path / relative_path) for relative_path in _REQUIRED_MODEL_FILES if not (model_path / relative_path).is_file()]
|
||||
@@ -218,7 +211,7 @@ class LightX2VSwiftVRLoader:
|
||||
"parallel": False,
|
||||
"use_compile": bool(use_compile),
|
||||
}
|
||||
runner = init_runner(set_config(argparse.Namespace(**config)))
|
||||
runner = build_runner(config)
|
||||
logger.info(
|
||||
"[SwiftVRLoader] loaded %s; attention=%s, rope=%s, clip=%s, overlap=%s, reae_batch=%s, compile=%s",
|
||||
model_name,
|
||||
@@ -288,8 +281,6 @@ class LightX2VSwiftVRSampler:
|
||||
):
|
||||
from lightx2v.models.runners.swiftvr import swiftvr_runner as swiftvr_module
|
||||
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info, update_input_info_from_dict
|
||||
|
||||
if images.dim() != 4 or images.shape[-1] not in (3, 4):
|
||||
raise ValueError(f"Expected IMAGE [T, H, W, C], got shape {tuple(images.shape)}")
|
||||
if images.shape[0] < 1:
|
||||
@@ -308,7 +299,7 @@ class LightX2VSwiftVRSampler:
|
||||
target_short_edge,
|
||||
require_even=not is_image,
|
||||
)
|
||||
target_shape = [output_height, output_width]
|
||||
target_size = [output_height, output_width]
|
||||
logger.info(
|
||||
"[SwiftVRSampler] aligned input=%sx%s, target_short_edge=%s, output=%sx%s, scale=%.4f",
|
||||
source_width,
|
||||
@@ -340,18 +331,21 @@ class LightX2VSwiftVRSampler:
|
||||
video_path = "" if is_image else "<tensor>"
|
||||
return_result_tensor = is_image
|
||||
|
||||
input_info = init_empty_input_info("sr")
|
||||
update_input_info_from_dict(
|
||||
input_info,
|
||||
{
|
||||
"video_path": video_path,
|
||||
"image_path": image_path,
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_shape": target_shape,
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": return_result_tensor,
|
||||
},
|
||||
)
|
||||
request = {
|
||||
"task": "sr",
|
||||
"video_path": video_path,
|
||||
"image_path": image_path,
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"size": target_size,
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": return_result_tensor,
|
||||
}
|
||||
runtime_config = {
|
||||
"fps": float(source_fps),
|
||||
"video_codec": str(video_codec),
|
||||
"quality": int(quality),
|
||||
"ffmpeg_preset": str(ffmpeg_preset),
|
||||
}
|
||||
|
||||
progress = ProgressBar(100)
|
||||
restored_images = None
|
||||
@@ -364,19 +358,6 @@ class LightX2VSwiftVRSampler:
|
||||
had_instance_image_reader = "read_image_frame" in runner.__dict__
|
||||
original_instance_image_reader = runner.__dict__.get("read_image_frame")
|
||||
try:
|
||||
runner.set_config(
|
||||
{
|
||||
"fps": float(source_fps),
|
||||
"video_codec": str(video_codec),
|
||||
"quality": int(quality),
|
||||
"ffmpeg_preset": str(ffmpeg_preset),
|
||||
"video_path": video_path,
|
||||
"image_path": image_path,
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_shape": target_shape,
|
||||
"return_result_tensor": return_result_tensor,
|
||||
}
|
||||
)
|
||||
if hasattr(runner, "set_progress_callback"):
|
||||
runner.set_progress_callback(lambda current, _total: progress.update_absolute(current))
|
||||
if is_image:
|
||||
@@ -390,7 +371,7 @@ class LightX2VSwiftVRSampler:
|
||||
swiftvr_module.mux_audio_from_video = lambda *_args, **_kwargs: None
|
||||
if not is_image and memory_writer is not None:
|
||||
runner.open_video_writer = types.MethodType(lambda _runner, _path, _fps: memory_writer, runner)
|
||||
result = runner.run_pipeline(input_info)
|
||||
result = run_request(runner, request, runtime_config=runtime_config)
|
||||
if is_image:
|
||||
restored_images = result.get("images") if isinstance(result, dict) else result
|
||||
finally:
|
||||
@@ -474,8 +455,6 @@ class LightX2VSwiftVRFileSampler:
|
||||
CATEGORY = "LightX2V/SwiftVR"
|
||||
|
||||
def sample(self, model, video_path, target_short_edge, filename_prefix, video_codec, quality, ffmpeg_preset):
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info, update_input_info_from_dict
|
||||
|
||||
input_path = resolve_input_video_path(video_path)
|
||||
raw_width, raw_height, source_fps = probe_video_file(input_path)
|
||||
source_height, source_width = raw_height // 8 * 8, raw_width // 8 * 8
|
||||
@@ -490,20 +469,21 @@ class LightX2VSwiftVRFileSampler:
|
||||
)
|
||||
full_path, output_file, output_subfolder = _prepare_output_video(filename_prefix, output_width, output_height)
|
||||
save_path = str(full_path)
|
||||
target_shape = [output_height, output_width]
|
||||
|
||||
input_info = init_empty_input_info("sr")
|
||||
update_input_info_from_dict(
|
||||
input_info,
|
||||
{
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_shape": target_shape,
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
},
|
||||
)
|
||||
request = {
|
||||
"task": "sr",
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"size": [output_height, output_width],
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
}
|
||||
runtime_config = {
|
||||
"fps": 0.0,
|
||||
"video_codec": str(video_codec),
|
||||
"quality": int(quality),
|
||||
"ffmpeg_preset": str(ffmpeg_preset),
|
||||
}
|
||||
|
||||
runner = model["runner"]
|
||||
progress = ProgressBar(100)
|
||||
@@ -518,22 +498,9 @@ class LightX2VSwiftVRFileSampler:
|
||||
)
|
||||
try:
|
||||
with _SWIFTVR_RUN_LOCK:
|
||||
runner.set_config(
|
||||
{
|
||||
"fps": 0.0,
|
||||
"video_codec": str(video_codec),
|
||||
"quality": int(quality),
|
||||
"ffmpeg_preset": str(ffmpeg_preset),
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_shape": target_shape,
|
||||
"return_result_tensor": False,
|
||||
}
|
||||
)
|
||||
if hasattr(runner, "set_progress_callback"):
|
||||
runner.set_progress_callback(lambda current, _total: progress.update_absolute(current))
|
||||
runner.run_pipeline(input_info)
|
||||
run_request(runner, request, runtime_config=runtime_config)
|
||||
finally:
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
+3
-3
@@ -2,6 +2,8 @@
|
||||
name = "lightx2vwrapper"
|
||||
description = "ComfyUI-Lightx2vWrapper is an inference wrapper for Lightx2v designed for use with ComfyUI."
|
||||
version = "1.1.0"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
license = { file = "LICENSE" }
|
||||
|
||||
[project.urls]
|
||||
@@ -19,10 +21,8 @@ line-length = 150
|
||||
indent-width = 4
|
||||
|
||||
[tool.ruff.lint]
|
||||
extend-select = ["I", "F401"]
|
||||
ignore = ["F"]
|
||||
extend-select = ["I"]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"**/__init__.py" = ["F401"]
|
||||
"**/lightx2v_kernel/*" = ["F401"]
|
||||
"**/{cookbook,docs}/*" = ["E402", "F401", "F811", "F841"]
|
||||
|
||||
Reference in New Issue
Block a user