diff --git a/.runpod/hub.json b/.runpod/hub.json new file mode 100644 index 00000000..f54e850d --- /dev/null +++ b/.runpod/hub.json @@ -0,0 +1,19 @@ +{ + "title": "Wan2.2 Patched Return", + "description": "Custom RunPod endpoint for Wan2.2 with MP4 Base64 return output.", + "type": "serverless", + "category": "video", + "iconUrl": "https://cdn-icons-png.flaticon.com/512/2921/2921222.png", + "config": { + "runsOn": "GPU", + "containerDiskInGb": 40, + "presets": [ + { + "id": "default", + "gpu": "A10G", + "memoryInGb": 24, + "cpus": 8 + } + ] + } +} diff --git a/.runpod/tests.json b/.runpod/tests.json new file mode 100644 index 00000000..c8ae4949 --- /dev/null +++ b/.runpod/tests.json @@ -0,0 +1,13 @@ +{ + "tests": [ + { + "name": "basic_video_test", + "input": { + "prompt": "A cinematic scene of ocean waves at sunset", + "seed": 42, + "num_frames": 12 + }, + "timeout": 120000 + } + ] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..39ff58da --- /dev/null +++ b/Dockerfile @@ -0,0 +1,70 @@ +# --- Base image (pre-built ComfyUI + CUDA + Torch) --- +FROM wlsdml1114/multitalk-base:1.7 AS runtime + +# --- Install required utilities --- +RUN pip install -U "huggingface_hub[hf_transfer]" runpod websocket-client onnxruntime-gpu==1.22 + +# --- Working directory --- +WORKDIR / + +# --- Clone ComfyUI core and essential nodes --- +RUN git clone --depth=1 https://github.com/comfyanonymous/ComfyUI.git && \ + cd /ComfyUI && pip install -r requirements.txt && \ + rm -rf .git + +RUN cd /ComfyUI/custom_nodes && \ + git clone --depth=1 https://github.com/Comfy-Org/ComfyUI-Manager.git && \ + cd ComfyUI-Manager && pip install -r requirements.txt && \ + cd .. && rm -rf ComfyUI-Manager/.git + +RUN cd /ComfyUI/custom_nodes && \ + git clone --depth=1 https://github.com/kijai/ComfyUI-WanVideoWrapper && \ + cd ComfyUI-WanVideoWrapper && pip install -r requirements.txt && \ + cd .. && rm -rf ComfyUI-WanVideoWrapper/.git + +RUN cd /ComfyUI/custom_nodes && \ + git clone --depth=1 https://github.com/kijai/ComfyUI-KJNodes && \ + cd ComfyUI-KJNodes && pip install -r requirements.txt && \ + cd .. && rm -rf ComfyUI-KJNodes/.git + +RUN cd /ComfyUI/custom_nodes && \ + git clone --depth=1 https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite && \ + cd ComfyUI-VideoHelperSuite && pip install -r requirements.txt && \ + cd .. && rm -rf ComfyUI-VideoHelperSuite/.git + +RUN cd /ComfyUI/custom_nodes && \ + git clone --depth=1 https://github.com/kijai/ComfyUI-WanAnimatePreprocess && \ + cd ComfyUI-WanAnimatePreprocess && pip install -r requirements.txt && \ + cd .. && rm -rf ComfyUI-WanAnimatePreprocess/.git + +# --- Extra helper nodes --- +RUN cd /ComfyUI/custom_nodes && \ + git clone --depth=1 https://github.com/kijai/ComfyUI-segment-anything-2 && rm -rf ComfyUI-segment-anything-2/.git && \ + git clone --depth=1 https://github.com/eddyhhlure1Eddy/IntelligentVRAMNode && rm -rf IntelligentVRAMNode/.git && \ + git clone --depth=1 https://github.com/eddyhhlure1Eddy/auto_wan2.2animate_freamtowindow_server && rm -rf auto_wan2.2animate_freamtowindow_server/.git && \ + git clone --depth=1 https://github.com/eddyhhlure1Eddy/ComfyUI-AdaptiveWindowSize && \ + cd ComfyUI-AdaptiveWindowSize/ComfyUI-AdaptiveWindowSize && mv * ../ && \ + cd .. && rm -rf ComfyUI-AdaptiveWindowSize/.git + +# --- Preload required models (these stay small, the rest load dynamically from HuggingFace) --- +RUN mkdir -p /ComfyUI/models/{vae,clip_vision,text_encoders,diffusion_models,loras,detection} + +RUN wget -q https://huggingface.co/Kijai/WanVideo_comfy/resolve/main/Wan2_1_VAE_bf16.safetensors -O /ComfyUI/models/vae/Wan2_1_VAE_bf16.safetensors +RUN wget -q https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors -O /ComfyUI/models/clip_vision/clip_vision_h.safetensors +RUN wget -q https://huggingface.co/Kijai/WanVideo_comfy/resolve/main/umt5-xxl-enc-bf16.safetensors -O /ComfyUI/models/text_encoders/umt5-xxl-enc-bf16.safetensors +RUN wget -q https://huggingface.co/Kijai/WanVideo_comfy_fp8_scaled/resolve/main/Wan22Animate/Wan2_2-Animate-14B_fp8_e4m3fn_scaled_KJ.safetensors -O /ComfyUI/models/diffusion_models/Wan2_2-Animate-14B_fp8_e4m3fn_scaled_KJ.safetensors + +RUN wget -q https://huggingface.co/eddy1111111/lightx2v_it2v_adaptive_fusionv_1.safetensors/resolve/main/lightx2v_elite_it2v_animate_face.safetensors -O /ComfyUI/models/loras/lightx2v_elite_it2v_animate_face.safetensors +RUN wget -q https://huggingface.co/eddy1111111/lightx2v_it2v_adaptive_fusionv_1.safetensors/resolve/main/WAN22_MoCap_fullbodyCOPY_ED.safetensors -O /ComfyUI/models/loras/WAN22_MoCap_fullbodyCOPY_ED.safetensors +RUN wget -q https://huggingface.co/eddy1111111/lightx2v_it2v_adaptive_fusionv_1.safetensors/resolve/main/FullDynamic_Ultimate_Fusion_Elite.safetensors -O /ComfyUI/models/loras/FullDynamic_Ultimate_Fusion_Elite.safetensors +RUN wget -q https://huggingface.co/eddy1111111/lightx2v_it2v_adaptive_fusionv_1.safetensors/resolve/main/Wan2.2-Fun-A14B-InP-Fusion-Elite.safetensors -O /ComfyUI/models/loras/Wan2.2-Fun-A14B-InP-Fusion-Elite.safetensors + +RUN wget -q https://huggingface.co/Wan-AI/Wan2.2-Animate-14B/resolve/main/process_checkpoint/det/yolov10m.onnx -O /ComfyUI/models/detection/yolov10m.onnx +RUN wget -q https://huggingface.co/Kijai/vitpose_comfy/resolve/main/onnx/vitpose_h_wholebody_model.onnx -O /ComfyUI/models/detection/vitpose_h_wholebody_model.onnx +RUN wget -q https://huggingface.co/Kijai/vitpose_comfy/resolve/main/onnx/vitpose_h_wholebody_data.bin -O /ComfyUI/models/detection/vitpose_h_wholebody_data.bin + +# --- Copy your repo files and entrypoint script --- +COPY . / +RUN chmod +x /entrypoint.sh + +CMD ["/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 00000000..497dc036 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +# If a workflow path is passed in WORKFLOW_JSON, load it +if [[ -n "${WORKFLOW_JSON:-}" ]]; then + echo "Applying workflow from WORKFLOW_JSON" + cp "$WORKFLOW_JSON" /workspace/workflow.json +fi + +# Default to launching ComfyUI server +cd /ComfyUI +exec python main.py --listen 0.0.0.0 --port "${PORT:-8188}" "$@" diff --git a/generate.py b/generate.py index 3a5cbcdd..7780d313 100644 --- a/generate.py +++ b/generate.py @@ -568,6 +568,30 @@ def generate(args): dist.destroy_process_group() logging.info("Finished.") +# --- Added for RunPod serverless return --- +import os, base64, json + +def export_video_to_base64(): + """Encode the final MP4 video as base64 for RunPod endpoint return.""" + # Locate the video file + output_path = args.save_file if hasattr(args, "save_file") else "/workspace/output/video.mp4" + if not output_path.endswith(".mp4"): + output_path = f"{output_path}.mp4" + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + + if os.path.exists(output_path): + with open(output_path, "rb") as f: + encoded = base64.b64encode(f.read()).decode("utf-8") + print(json.dumps({"video_base64": encoded})) + else: + print(f"⚠️ Video not found at {output_path}") + +try: + export_video_to_base64() +except Exception as e: + print("⚠️ Error returning MP4:", e) +# --- End of patch --- if __name__ == "__main__": diff --git a/handler.py b/handler.py new file mode 100644 index 00000000..2320d219 --- /dev/null +++ b/handler.py @@ -0,0 +1,35 @@ +import os, json, base64, subprocess + +def handler(event): + try: + # Get input prompt from the RunPod payload + input_data = event.get("input", {}) + prompt = input_data.get("prompt", "Default prompt") + print(f"[RunPod] Received prompt: {prompt}") + + # Run your generate.py script + result = subprocess.run( + ["python3", "generate.py"], + capture_output=True, + text=True + ) + + # Check for saved MP4 + output_path = "/workspace/output/video.mp4" + if os.path.exists(output_path): + with open(output_path, "rb") as f: + encoded = base64.b64encode(f.read()).decode("utf-8") + + + return { + "error": "⚠️ Video not found at /workspace/output/video.mp4", + "stdout": result.stdout, + "stderr": result.stderr + } + + except Exception as e: + return {"error": str(e)} + +if __name__ == "__main__": + test = {"input": {"prompt": "A cinematic test render"}} + print(json.dumps(handler(test))) diff --git a/requirements.txt b/requirements.txt index 59274655..c57e7b96 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,5 +12,4 @@ easydict ftfy dashscope imageio-ffmpeg -flash_attn -numpy>=1.23.5,<2 \ No newline at end of file +numpy>=1.23.5,<2