Mask-in-video (the masks now travel with the video file): - New videometa module: write/read a JSON payload under the lanpaint-mask mp4 metadata tag via PyAV (stream copy, movflags=use_metadata_tags), so exporting produces a NEW file and the source is never modified. - Two server routes: GET /lanpaint/video_mask_meta (read the payload back) and POST /lanpaint/export_mask_video (remux into input/<base>_masked.mp4). - The mask editor exports the painted keyframes (base64 PNGs) + audio intervals into the video; loading a video with the tag auto-restores the masks (editor and node preview). A video without the tag clears the workflow's masks - the video is the source of truth. The export also offers a native save dialog (showSaveFilePicker) when available. - The video widget upload now uses video_upload so MP4s are selectable in the file chooser (was image_upload). - LanPaint_ImageEncode accepts 5D latents from video VAEs encoding a single image (e.g. the Hunyuan video VAE) - fixes the Hunyuan example. Example set conversion (one example per supported model, new files, old pairs removed after being superseded): - New *ImageEncode/*EncodeDecode example workflows for SDXL, Flux.1, Flux.2 Dev, Flux2 Klein, SD3.5, Hunyuan, Ideogram4, Krea2, Qwen Edit, Qwen Image, Z-Image, Wan 2.2 and MiniMax H3 (AV), all using LanPaint_ImageEncode/ImageDecode instead of VAEEncode + SetLatentNoiseMask + VAEDecode + MaskBlend; every example references its own example folder's masked input (hash-verified); preview jpgs generated from the test runs; the H3 example ships its output mp4 as a preview. - HiDream example removed on request; Example_29 added with the H3 masked input video. Fixes along the way: normalize node input.link/output.links fields against the links arrays in all converted workflows (the frontend renders null-link nodes as disconnected); stale Inpainting_mode widget removed from the Flux.2 sampler; Lambda defaults 5.0 (first-order scheme); torchaudio resample test assertion relaxed (edge ringing); the videometa tests updated to the final export signature.
109 lines
3.5 KiB
Python
109 lines
3.5 KiB
Python
"""Top-level package for LanPaint."""
|
|
|
|
__all__ = [
|
|
"NODE_CLASS_MAPPINGS",
|
|
"NODE_DISPLAY_NAME_MAPPINGS",
|
|
"WEB_DIRECTORY",
|
|
]
|
|
|
|
__author__ = """LanPaint"""
|
|
__email__ = "czhengac@connect.ust.hk"
|
|
__version__ = "0.0.1"
|
|
|
|
|
|
def _install_lightweight_runtime_stubs() -> None:
|
|
"""Install lightweight stubs so tooling can import this package without ComfyUI.
|
|
|
|
This is used by CI tooling (e.g., comfy-org/node-diff) that imports NODE_CLASS_MAPPINGS
|
|
in an environment where ComfyUI isn't installed.
|
|
"""
|
|
import sys
|
|
import types
|
|
|
|
# `src/LanPaint/nodes.py` uses `torch.Tensor` in type annotations.
|
|
try:
|
|
import torch # noqa: F401
|
|
except ModuleNotFoundError:
|
|
torch_mod = types.ModuleType("torch")
|
|
|
|
class Tensor: # noqa: N801 (match torch naming)
|
|
pass
|
|
|
|
torch_mod.Tensor = Tensor
|
|
torch_mod.nn = types.SimpleNamespace(functional=types.SimpleNamespace())
|
|
sys.modules["torch"] = torch_mod
|
|
|
|
if "comfyui_version" not in sys.modules:
|
|
comfyui_version_mod = types.ModuleType("comfyui_version")
|
|
comfyui_version_mod.__version__ = "0.0.0"
|
|
sys.modules["comfyui_version"] = comfyui_version_mod
|
|
|
|
sys.modules.setdefault("nodes", types.ModuleType("nodes"))
|
|
sys.modules.setdefault("latent_preview", types.ModuleType("latent_preview"))
|
|
|
|
if "comfy" not in sys.modules:
|
|
comfy_mod = types.ModuleType("comfy")
|
|
comfy_mod.__path__ = []
|
|
|
|
comfy_utils_mod = types.ModuleType("comfy.utils")
|
|
|
|
def repeat_to_batch_size(tensor, batch_size): # type: ignore[no-untyped-def]
|
|
if getattr(tensor, "shape", ())[0] == batch_size:
|
|
return tensor
|
|
return tensor
|
|
|
|
comfy_utils_mod.repeat_to_batch_size = repeat_to_batch_size
|
|
|
|
comfy_samplers_mod = types.ModuleType("comfy.samplers")
|
|
|
|
class DummyKSAMPLER: # noqa: N801 (match ComfyUI naming)
|
|
pass
|
|
|
|
comfy_samplers_mod.KSAMPLER = DummyKSAMPLER
|
|
|
|
comfy_model_base_mod = types.ModuleType("comfy.model_base")
|
|
|
|
class ModelType: # noqa: N801 (match ComfyUI naming)
|
|
FLUX = "FLUX"
|
|
FLOW = "FLOW"
|
|
|
|
class WAN22: # noqa: N801 (match ComfyUI naming)
|
|
pass
|
|
|
|
comfy_model_base_mod.ModelType = ModelType
|
|
comfy_model_base_mod.WAN22 = WAN22
|
|
|
|
comfy_mod.utils = comfy_utils_mod
|
|
comfy_mod.samplers = comfy_samplers_mod
|
|
comfy_mod.model_base = comfy_model_base_mod
|
|
|
|
sys.modules["comfy"] = comfy_mod
|
|
sys.modules["comfy.utils"] = comfy_utils_mod
|
|
sys.modules["comfy.samplers"] = comfy_samplers_mod
|
|
sys.modules["comfy.model_base"] = comfy_model_base_mod
|
|
|
|
|
|
try:
|
|
from .src.LanPaint.nodes import NODE_CLASS_MAPPINGS
|
|
from .src.LanPaint.nodes import NODE_DISPLAY_NAME_MAPPINGS
|
|
except ModuleNotFoundError:
|
|
_install_lightweight_runtime_stubs()
|
|
from .src.LanPaint.nodes import NODE_CLASS_MAPPINGS
|
|
from .src.LanPaint.nodes import NODE_DISPLAY_NAME_MAPPINGS
|
|
|
|
WEB_DIRECTORY = "./web"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Server routes — registered only when running inside ComfyUI (the ``server``
|
|
# module and ``folder_paths`` are ComfyUI internals not available in CI/tests).
|
|
# ---------------------------------------------------------------------------
|
|
try:
|
|
from server import PromptServer # noqa: F811 (re-export for convenience)
|
|
|
|
from .src.LanPaint.videometa import register_routes
|
|
|
|
register_routes(PromptServer.instance)
|
|
except Exception:
|
|
pass # not running inside ComfyUI — routes are not needed
|