diff --git a/nodes.py b/nodes.py index 0852c3a..c3e7c47 100644 --- a/nodes.py +++ b/nodes.py @@ -84,18 +84,20 @@ def _to_audio(data: bytes) -> dict: return {"waveform": waveform[None,], "sample_rate": sr} -def _save_video(data: bytes, model_id: str) -> str: - """Video in den ComfyUI-Output-Ordner schreiben, Pfad zurückgeben.""" +def _save_video(data: bytes, model_id: str): + """Video in den ComfyUI-Output-Ordner schreiben → (Pfad, ui-Metadaten für /history).""" try: import folder_paths out_dir = Path(folder_paths.get_output_directory()) except Exception: out_dir = Path("output") - out_dir.mkdir(parents=True, exist_ok=True) + subfolder = "video" + (out_dir / subfolder).mkdir(parents=True, exist_ok=True) slug = model_id.replace("/", "_") - path = out_dir / f"replicate_{slug}_{int(time.time())}.mp4" - path.write_bytes(data) - return str(path) + filename = f"replicate_{slug}_{int(time.time())}.mp4" + (out_dir / subfolder / filename).write_bytes(data) + meta = {"filename": filename, "subfolder": subfolder, "type": "output"} + return str(out_dir / subfolder / filename), meta def _first_output(result): @@ -145,8 +147,11 @@ def _build_arguments(model_def: dict, kwargs: dict) -> dict: continue kind = entry["kind"] if kind == "image": - uri = _image_to_uri(value) - args[name] = [uri] if entry.get("is_list") else uri + if entry.get("is_list"): + # Batch-Bilder einzeln hochladen (z.B. Qwen Edit Plus: Source + Referenzen) + args[name] = [_image_to_uri(value[i:i + 1]) for i in range(value.shape[0])] + else: + args[name] = _image_to_uri(value) elif kind == "audio": args[name] = _audio_to_uri(value) elif kind == "combo": @@ -180,6 +185,7 @@ def _make_node(model_def: dict): FUNCTION = "run" RETURN_TYPES = return_types RETURN_NAMES = return_names + OUTPUT_NODE = output_kind == "video" @classmethod def INPUT_TYPES(cls): @@ -201,7 +207,9 @@ def _make_node(model_def: dict): return (_to_image_tensor(data),) if output_kind == "audio": return (_to_audio(data),) - return (_save_video(data, model_def["id"]),) + path, meta = _save_video(data, model_def["id"]) + # ui-Eintrag → Video erscheint in /history und ist via /view abrufbar (Client-Apps) + return {"ui": {"images": [meta], "animated": (True,)}, "result": (path,)} ReplicateModelNode.__name__ = "Replicate_" + model_def["id"].replace("/", "_").replace("-", "_").replace(".", "_") return ReplicateModelNode diff --git a/templates/LTX-2_Replicate.json b/templates/LTX-2_Replicate.json new file mode 100644 index 0000000..acaff10 --- /dev/null +++ b/templates/LTX-2_Replicate.json @@ -0,0 +1,34 @@ +{ + "name": "LTX-2 Distilled (Replicate)", + "description": "Image-to-Video mit synchronem Audio via lightricks/ltx-2-distilled. Video landet in output/video/, Node ist Output-Node.", + "defaults": { + "num_frames": 121, + "seed": 0 + }, + "nodes": { + "1": { + "class_type": "LoadImage", + "inputs": { + "image": "{{image_filename}}" + }, + "_meta": { + "title": "Load Image" + } + }, + "2": { + "class_type": "Replicate_lightricks_ltx_2_distilled", + "inputs": { + "prompt": "{{positive_prompt}}", + "image": [ + "1", + 0 + ], + "num_frames": "{{num_frames}}", + "seed": "{{seed}}" + }, + "_meta": { + "title": "LTX-2 Distilled (Replicate)" + } + } + } +} \ No newline at end of file diff --git a/templates/Qwen_Image_Edit_Plus_Replicate.json b/templates/Qwen_Image_Edit_Plus_Replicate.json new file mode 100644 index 0000000..3d0dd39 --- /dev/null +++ b/templates/Qwen_Image_Edit_Plus_Replicate.json @@ -0,0 +1,47 @@ +{ + "name": "Qwen Image Edit Plus (Replicate)", + "description": "Bild-Editing via qwen/qwen-image-edit-plus. Referenzbilder: per ImageBatch an den image-Input hängen.", + "defaults": { + "aspect_ratio": "match_input_image", + "seed": 0 + }, + "nodes": { + "1": { + "class_type": "LoadImage", + "inputs": { + "image": "uploaded_image.png [input]" + }, + "_meta": { + "title": "Load Image (Source)" + } + }, + "2": { + "class_type": "Replicate_qwen_qwen_image_edit_plus", + "inputs": { + "image": [ + "1", + 0 + ], + "prompt": "{{positive_prompt}}", + "aspect_ratio": "{{aspect_ratio}}", + "seed": "{{seed}}" + }, + "_meta": { + "title": "Qwen Image Edit Plus (Replicate)" + } + }, + "3": { + "class_type": "SaveImage", + "inputs": { + "filename_prefix": "ComfyChair", + "images": [ + "2", + 0 + ] + }, + "_meta": { + "title": "Save Image" + } + } + } +} \ No newline at end of file diff --git a/templates/Wan_2.2_i2v_fast_Replicate.json b/templates/Wan_2.2_i2v_fast_Replicate.json new file mode 100644 index 0000000..f6284ee --- /dev/null +++ b/templates/Wan_2.2_i2v_fast_Replicate.json @@ -0,0 +1,38 @@ +{ + "name": "Wan 2.2 I2V Fast (Replicate)", + "description": "Image-to-Video via wan-video/wan-2.2-i2v-fast (Lightning-destilliert von Pruna)", + "defaults": { + "resolution": "480p", + "num_frames": 81, + "frame_rate": 24, + "seed": 0 + }, + "nodes": { + "1": { + "class_type": "LoadImage", + "inputs": { + "image": "{{image_filename}}" + }, + "_meta": { + "title": "Load Image" + } + }, + "2": { + "class_type": "Replicate_wan_video_wan_2_2_i2v_fast", + "inputs": { + "prompt": "{{positive_prompt}}", + "image": [ + "1", + 0 + ], + "resolution": "{{resolution}}", + "num_frames": "{{num_frames}}", + "frames_per_second": "{{frame_rate}}", + "seed": "{{seed}}" + }, + "_meta": { + "title": "Wan 2.2 I2V Fast (Replicate)" + } + } + } +} \ No newline at end of file diff --git a/templates/Wan_2.2_t2v_fast_Replicate.json b/templates/Wan_2.2_t2v_fast_Replicate.json new file mode 100644 index 0000000..3212b8e --- /dev/null +++ b/templates/Wan_2.2_t2v_fast_Replicate.json @@ -0,0 +1,27 @@ +{ + "name": "Wan 2.2 T2V Fast (Replicate)", + "description": "Text-to-Video via wan-video/wan-2.2-t2v-fast (Lightning-destilliert von Pruna)", + "defaults": { + "aspect_ratio": "9:16", + "resolution": "480p", + "num_frames": 81, + "frame_rate": 24, + "seed": 0 + }, + "nodes": { + "1": { + "class_type": "Replicate_wan_video_wan_2_2_t2v_fast", + "inputs": { + "prompt": "{{positive_prompt}}", + "aspect_ratio": "{{aspect_ratio}}", + "resolution": "{{resolution}}", + "num_frames": "{{num_frames}}", + "frames_per_second": "{{frame_rate}}", + "seed": "{{seed}}" + }, + "_meta": { + "title": "Wan 2.2 T2V Fast (Replicate)" + } + } + } +} \ No newline at end of file diff --git a/templates/Z-Image_Turbo_Replicate.json b/templates/Z-Image_Turbo_Replicate.json new file mode 100644 index 0000000..a686fc2 --- /dev/null +++ b/templates/Z-Image_Turbo_Replicate.json @@ -0,0 +1,38 @@ +{ + "name": "Z-Image Turbo (Replicate)", + "description": "Text-to-Image via prunaai/z-image-turbo auf Replicate", + "defaults": { + "width": 832, + "height": 1216, + "steps": 9, + "seed": 0 + }, + "nodes": { + "1": { + "class_type": "Replicate_prunaai_z_image_turbo", + "inputs": { + "prompt": "{{positive_prompt}}", + "width": "{{width}}", + "height": "{{height}}", + "num_inference_steps": "{{steps}}", + "seed": "{{seed}}" + }, + "_meta": { + "title": "Z-Image Turbo (Replicate)" + } + }, + "2": { + "class_type": "SaveImage", + "inputs": { + "filename_prefix": "ComfyChair", + "images": [ + "1", + 0 + ] + }, + "_meta": { + "title": "Save Image" + } + } + } +} \ No newline at end of file diff --git a/templates/Z-Image_Turbo_img2img_Replicate.json b/templates/Z-Image_Turbo_img2img_Replicate.json new file mode 100644 index 0000000..30e13e5 --- /dev/null +++ b/templates/Z-Image_Turbo_img2img_Replicate.json @@ -0,0 +1,49 @@ +{ + "name": "Z-Image Turbo I2I (Replicate)", + "description": "Image-to-Image via prunaai/z-image-turbo-img2img (statt Inpaint: strength steuert Nähe zum Original)", + "defaults": { + "strength": 0.6, + "steps": 9, + "seed": 0 + }, + "nodes": { + "1": { + "class_type": "LoadImage", + "inputs": { + "image": "uploaded_image.png [input]" + }, + "_meta": { + "title": "Load Image" + } + }, + "2": { + "class_type": "Replicate_prunaai_z_image_turbo_img2img", + "inputs": { + "prompt": "{{positive_prompt}}", + "image": [ + "1", + 0 + ], + "strength": "{{strength}}", + "num_inference_steps": "{{steps}}", + "seed": "{{seed}}" + }, + "_meta": { + "title": "Z-Image Turbo I2I (Replicate)" + } + }, + "3": { + "class_type": "SaveImage", + "inputs": { + "filename_prefix": "ComfyChair", + "images": [ + "2", + 0 + ] + }, + "_meta": { + "title": "Save Image" + } + } + } +} \ No newline at end of file