350 lines
15 KiB
Python
350 lines
15 KiB
Python
"""
|
|
ComfyUI-QwenImageWanBridge
|
|
Qwen2.5-VL implementation with custom vision support
|
|
"""
|
|
|
|
NODE_CLASS_MAPPINGS = {}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {}
|
|
|
|
# Web directory for JavaScript extensions
|
|
WEB_DIRECTORY = "./web"
|
|
|
|
# ============================================================================
|
|
# CORE NODES - Qwen2.5-VL with proper vision support
|
|
# ============================================================================
|
|
|
|
try:
|
|
from .nodes.qwen_vl_encoder import QwenVLCLIPLoader, QwenVLTextEncoder, QwenLowresFixNode
|
|
NODE_CLASS_MAPPINGS["QwenVLCLIPLoader"] = QwenVLCLIPLoader
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenVLCLIPLoader"] = "Qwen2.5-VL CLIP Loader"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenVLTextEncoder"] = QwenVLTextEncoder
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenVLTextEncoder"] = "Qwen2.5-VL Text Encoder"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenLowresFixNode"] = QwenLowresFixNode
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenLowresFixNode"] = "Qwen Lowres Fix (Two-Stage)"
|
|
|
|
print("[QwenImageWanBridge] Loaded Qwen2.5-VL nodes using ComfyUI's CLIP")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load encoder nodes: {e}")
|
|
|
|
# Advanced encoder for power users
|
|
try:
|
|
from .nodes.qwen_vl_encoder_advanced import QwenVLTextEncoderAdvanced
|
|
NODE_CLASS_MAPPINGS["QwenVLTextEncoderAdvanced"] = QwenVLTextEncoderAdvanced
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenVLTextEncoderAdvanced"] = "Qwen2.5-VL Text Encoder (Advanced)"
|
|
|
|
print("[QwenImageWanBridge] Loaded Advanced Encoder with weighted resolution control")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load advanced encoder: {e}")
|
|
|
|
# Helper nodes
|
|
try:
|
|
from .nodes.qwen_vl_helpers import QwenVLEmptyLatent, QwenVLImageToLatent
|
|
|
|
NODE_CLASS_MAPPINGS["QwenVLEmptyLatent"] = QwenVLEmptyLatent
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenVLEmptyLatent"] = "Qwen VL Empty Latent"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenVLImageToLatent"] = QwenVLImageToLatent
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenVLImageToLatent"] = "Qwen VL Image to Latent"
|
|
|
|
print("[QwenImageWanBridge] Loaded Qwen VL helper nodes (2 nodes)")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load helper nodes: {e}")
|
|
|
|
# Image batching node (aspect-ratio preserving with v2.6.1 scaling)
|
|
try:
|
|
from .nodes.qwen_image_batch import QwenImageBatch
|
|
|
|
NODE_CLASS_MAPPINGS["QwenImageBatch"] = QwenImageBatch
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenImageBatch"] = "Qwen Image Batch"
|
|
|
|
print("[QwenImageWanBridge] Loaded Qwen Image Batch node")
|
|
print("[QwenImageWanBridge] FEATURE: Aspect-ratio preserving batching with v2.6.1 scaling modes")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load image batch node: {e}")
|
|
|
|
# Qwen-to-Wan Video Bridge nodes
|
|
try:
|
|
from .nodes.qwen_wan_bridge import (
|
|
QwenToWanFirstFrameLatent,
|
|
QwenToWanLatentSaver,
|
|
QwenToWanImageSaver
|
|
)
|
|
|
|
NODE_CLASS_MAPPINGS["QwenToWanFirstFrameLatent"] = QwenToWanFirstFrameLatent
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenToWanFirstFrameLatent"] = "Qwen → Wan First Frame Latent"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenToWanLatentSaver"] = QwenToWanLatentSaver
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenToWanLatentSaver"] = "Save First Frame Latent (Wan)"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenToWanImageSaver"] = QwenToWanImageSaver
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenToWanImageSaver"] = "Save First Frame Image"
|
|
|
|
print("[QwenImageWanBridge] Loaded Qwen-to-Wan Video Bridge nodes (3 nodes)")
|
|
print("[QwenImageWanBridge] FEATURE: Image-to-video bridge with first frame conditioning")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load Wan bridge nodes: {e}")
|
|
|
|
# Qwen-to-ChronoEdit Bridge node
|
|
try:
|
|
from .nodes.qwen_chronoedit_bridge import QwenToChronoEditBridge
|
|
|
|
NODE_CLASS_MAPPINGS["QwenToChronoEditBridge"] = QwenToChronoEditBridge
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenToChronoEditBridge"] = "Qwen → ChronoEdit Bridge"
|
|
|
|
print("[QwenImageWanBridge] Loaded Qwen-to-ChronoEdit Bridge node")
|
|
print("[QwenImageWanBridge] FEATURE: Qwen image editing → ChronoEdit video animation")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load ChronoEdit bridge node: {e}")
|
|
|
|
# Resolution nodes removed - functionality integrated into encoder
|
|
|
|
# Mask-based inpainting nodes
|
|
try:
|
|
from .nodes.qwen_mask_processor import QwenMaskProcessor
|
|
from .nodes.qwen_inpaint_sampler import QwenInpaintSampler
|
|
|
|
NODE_CLASS_MAPPINGS["QwenMaskProcessor"] = QwenMaskProcessor
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenMaskProcessor"] = "Qwen Mask Processor"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenInpaintSampler"] = QwenInpaintSampler
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenInpaintSampler"] = "Qwen Inpainting Sampler"
|
|
|
|
print("[QwenImageWanBridge] Loaded mask-based inpainting nodes (2 nodes)")
|
|
print("[QwenImageWanBridge] FEATURE: Mask-based spatial editing with diffusers patterns")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load mask-based inpainting nodes: {e}")
|
|
|
|
# Simplified template builder V2
|
|
try:
|
|
from .nodes.qwen_template_builder import (
|
|
QwenTemplateBuilderV2,
|
|
QwenTemplateConnector
|
|
)
|
|
|
|
NODE_CLASS_MAPPINGS["QwenTemplateBuilder"] = QwenTemplateBuilderV2
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenTemplateBuilder"] = "Qwen Template Builder"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenTemplateConnector"] = QwenTemplateConnector
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenTemplateConnector"] = "Qwen Template Connector"
|
|
|
|
print("[QwenImageWanBridge] Loaded template builder nodes")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load template nodes: {e}")
|
|
|
|
# Qwen-WAN Bridge nodes removed - not related to Qwen-Image-Edit
|
|
|
|
# V2V nodes removed - not core functionality
|
|
|
|
# Multi-Reference handler deprecated - use Image Batch node instead
|
|
|
|
# EliGen Entity Control nodes (DiffSynth-Studio feature)
|
|
try:
|
|
from .nodes.qwen_eligen_entity_control import (
|
|
QwenEliGenEntityControl,
|
|
QwenEliGenMaskPainter
|
|
)
|
|
|
|
NODE_CLASS_MAPPINGS["QwenEliGenEntityControl"] = QwenEliGenEntityControl
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenEliGenEntityControl"] = "Qwen EliGen Entity Control"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenEliGenMaskPainter"] = QwenEliGenMaskPainter
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenEliGenMaskPainter"] = "EliGen Mask Painter"
|
|
|
|
print("[QwenImageWanBridge] Loaded EliGen Entity Control nodes (2 nodes)")
|
|
print("[QwenImageWanBridge] FEATURE: Precise spatial control with masks and per-region prompts")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load EliGen nodes: {e}")
|
|
|
|
# Token Analysis nodes
|
|
try:
|
|
from .nodes.qwen_token_debugger import QwenTokenDebugger
|
|
from .nodes.qwen_token_analyzer_standalone import QwenTokenAnalyzerStandalone
|
|
from .nodes.qwen_spatial_token_generator import QwenSpatialTokenGenerator
|
|
|
|
NODE_CLASS_MAPPINGS["QwenTokenDebugger"] = QwenTokenDebugger
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenTokenDebugger"] = "Qwen Token Debugger"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenTokenAnalyzer"] = QwenTokenAnalyzerStandalone
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenTokenAnalyzer"] = "Qwen Token Analyzer"
|
|
|
|
NODE_CLASS_MAPPINGS["QwenSpatialTokenGenerator"] = QwenSpatialTokenGenerator
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenSpatialTokenGenerator"] = "Qwen Spatial Token Generator"
|
|
|
|
print("[QwenImageWanBridge] Loaded Token Analysis nodes (3 nodes)")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load Token Analysis nodes: {e}")
|
|
|
|
# Debug Controller node
|
|
try:
|
|
from .nodes.qwen_debug_controller import QwenDebugController
|
|
|
|
NODE_CLASS_MAPPINGS["QwenDebugController"] = QwenDebugController
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenDebugController"] = "Qwen Debug Controller"
|
|
|
|
print("[QwenImageWanBridge] Loaded Debug Controller node")
|
|
print("[QwenImageWanBridge] FEATURE: Holistic debugging with performance profiling and log analysis")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load Debug Controller: {e}")
|
|
|
|
# Experimental Smart Crop node
|
|
try:
|
|
from .nodes.experimental_smart_crop import QwenSmartCrop
|
|
|
|
NODE_CLASS_MAPPINGS["QwenSmartCrop"] = QwenSmartCrop
|
|
NODE_DISPLAY_NAME_MAPPINGS["QwenSmartCrop"] = "Qwen Smart Crop (Experimental)"
|
|
|
|
print("[QwenImageWanBridge] Loaded Experimental Smart Crop node")
|
|
print("[QwenImageWanBridge] EXPERIMENTAL: Intelligent face cropping with VLM detection support")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load Smart Crop node: {e}")
|
|
|
|
# C2C Vision Bridge nodes archived - see nodes/archive/c2c/
|
|
|
|
# Inpainting nodes restored - mask-based approach aligns with DiffSynth patterns
|
|
|
|
# Note: Experimental multi-frame nodes have been archived
|
|
# Use Multi-Reference Handler with "index" mode for multi-frame support
|
|
|
|
# Patches removed - functionality integrated into main encoder
|
|
|
|
# Native nodes removed - incomplete implementation
|
|
|
|
# Wrapper nodes archived - see nodes/archive/wrapper/ (not production ready, had VRAM issues)
|
|
|
|
# ============================================================================
|
|
# EXPORTS
|
|
# ============================================================================
|
|
|
|
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
|
|
|
print(f"[QwenImageWanBridge] Total nodes loaded: {len(NODE_CLASS_MAPPINGS)}")
|
|
|
|
# Engine nodes removed - incomplete implementation
|
|
|
|
# ============================================================================
|
|
# HUNYUANVIDEO 1.5 NODES - Dual text encoder (Qwen2.5-VL + byT5)
|
|
# ============================================================================
|
|
|
|
# Loader nodes
|
|
try:
|
|
from .nodes.hunyuan_video_nodes import (
|
|
HunyuanVideoCLIPLoader,
|
|
HunyuanVideoVisionLoader,
|
|
HunyuanVideoEmptyLatent,
|
|
)
|
|
|
|
NODE_CLASS_MAPPINGS["HunyuanVideoCLIPLoader"] = HunyuanVideoCLIPLoader
|
|
NODE_DISPLAY_NAME_MAPPINGS["HunyuanVideoCLIPLoader"] = "HunyuanVideo CLIP Loader"
|
|
|
|
NODE_CLASS_MAPPINGS["HunyuanVideoVisionLoader"] = HunyuanVideoVisionLoader
|
|
NODE_DISPLAY_NAME_MAPPINGS["HunyuanVideoVisionLoader"] = "HunyuanVideo Vision Loader (SigLIP)"
|
|
|
|
NODE_CLASS_MAPPINGS["HunyuanVideoEmptyLatent"] = HunyuanVideoEmptyLatent
|
|
NODE_DISPLAY_NAME_MAPPINGS["HunyuanVideoEmptyLatent"] = "HunyuanVideo Empty Latent"
|
|
|
|
print("[QwenImageWanBridge] Loaded HunyuanVideo 1.5 loader nodes (3 nodes)")
|
|
print("[QwenImageWanBridge] FEATURE: Dual CLIP loader (Qwen2.5-VL + byT5)")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load HunyuanVideo loader nodes: {e}")
|
|
|
|
# Text encoder with template system
|
|
try:
|
|
from .nodes.hunyuan_video_encoder import HunyuanVideoTextEncoder, get_templates as get_hunyuan_video_templates
|
|
|
|
NODE_CLASS_MAPPINGS["HunyuanVideoTextEncoder"] = HunyuanVideoTextEncoder
|
|
NODE_DISPLAY_NAME_MAPPINGS["HunyuanVideoTextEncoder"] = "HunyuanVideo Text Encoder"
|
|
|
|
# Register API endpoint for templates (single source of truth)
|
|
try:
|
|
from aiohttp import web
|
|
from server import PromptServer
|
|
|
|
@PromptServer.instance.routes.get("/api/hunyuan_video_templates")
|
|
async def get_hunyuan_video_templates_api(request):
|
|
"""API endpoint for HunyuanVideo templates - JS fetches from here."""
|
|
templates = get_hunyuan_video_templates()
|
|
return web.json_response(templates)
|
|
|
|
print("[QwenImageWanBridge] Loaded HunyuanVideo text encoder")
|
|
print("[QwenImageWanBridge] API: /api/hunyuan_video_templates endpoint registered")
|
|
except Exception as api_err:
|
|
print(f"[QwenImageWanBridge] Loaded HunyuanVideo encoder (API endpoint failed: {api_err})")
|
|
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load HunyuanVideo encoder: {e}")
|
|
|
|
# Prompt expander (lightweight alternative to Qwen3-235B rewriter)
|
|
try:
|
|
from .nodes.hunyuan_video_prompt_expander import HunyuanVideoPromptExpander
|
|
|
|
NODE_CLASS_MAPPINGS["HunyuanVideoPromptExpander"] = HunyuanVideoPromptExpander
|
|
NODE_DISPLAY_NAME_MAPPINGS["HunyuanVideoPromptExpander"] = "HunyuanVideo Prompt Expander"
|
|
|
|
print("[QwenImageWanBridge] Loaded HunyuanVideo prompt expander")
|
|
print("[QwenImageWanBridge] FEATURE: Lightweight prompt expansion using loaded Qwen2.5-VL")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load HunyuanVideo prompt expander: {e}")
|
|
|
|
# ============================================================================
|
|
# Z-IMAGE NODES - Proper thinking token support
|
|
# ============================================================================
|
|
|
|
try:
|
|
from .nodes.z_image_encoder import ZImageTextEncoder, ZImageMessageChain, get_templates
|
|
|
|
NODE_CLASS_MAPPINGS["ZImageTextEncoder"] = ZImageTextEncoder
|
|
NODE_DISPLAY_NAME_MAPPINGS["ZImageTextEncoder"] = "Z-Image Text Encoder"
|
|
|
|
NODE_CLASS_MAPPINGS["ZImageMessageChain"] = ZImageMessageChain
|
|
NODE_DISPLAY_NAME_MAPPINGS["ZImageMessageChain"] = "Z-Image Message Chain"
|
|
|
|
# Register API endpoint for templates (single source of truth)
|
|
try:
|
|
from aiohttp import web
|
|
from server import PromptServer
|
|
|
|
@PromptServer.instance.routes.get("/api/z_image_templates")
|
|
async def get_z_image_templates(request):
|
|
"""API endpoint for Z-Image templates - JS fetches from here."""
|
|
templates = get_templates()
|
|
return web.json_response(templates)
|
|
|
|
print("[QwenImageWanBridge] Loaded Z-Image encoder nodes (2 nodes)")
|
|
print("[QwenImageWanBridge] API: /api/z_image_templates endpoint registered")
|
|
except Exception as api_err:
|
|
print(f"[QwenImageWanBridge] Loaded Z-Image nodes (API endpoint failed: {api_err})")
|
|
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load Z-Image encoder nodes: {e}")
|
|
|
|
# ============================================================================
|
|
# EXPERIMENTAL ANALYSIS NODES
|
|
# ============================================================================
|
|
|
|
# Template Influence Analyzer - tests if system prompts affect embeddings
|
|
try:
|
|
from .nodes.template_influence_analyzer import TemplateInfluenceAnalyzer
|
|
|
|
NODE_CLASS_MAPPINGS["TemplateInfluenceAnalyzer"] = TemplateInfluenceAnalyzer
|
|
NODE_DISPLAY_NAME_MAPPINGS["TemplateInfluenceAnalyzer"] = "Template Influence Analyzer"
|
|
|
|
print("[QwenImageWanBridge] Loaded Template Influence Analyzer (experimental)")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Failed to load Template Influence Analyzer: {e}")
|
|
|
|
# ============================================================================
|
|
# DEBUG TRACING - Opt-in via environment variable
|
|
# ============================================================================
|
|
|
|
import os
|
|
if os.environ.get('QWEN_ENABLE_DEBUG_PATCHES', '').lower() in ('true', '1', 'yes'):
|
|
try:
|
|
from .nodes import debug_patch
|
|
debug_patch.apply_debug_patches()
|
|
print("[QwenImageWanBridge] Debug patches applied (requested via QWEN_ENABLE_DEBUG_PATCHES)")
|
|
print("[QwenImageWanBridge] Set QWEN_DEBUG_VERBOSE=true for verbose tracing output")
|
|
except Exception as e:
|
|
print(f"[QwenImageWanBridge] Debug patches failed: {e}")
|