diff --git a/README.md b/README.md index 0bc2d69..273a915 100644 --- a/README.md +++ b/README.md @@ -29,30 +29,13 @@ This is a direct integration with Alibaba Cloud's Model Studio service, not a th ## Regional Support -This node supports both international and Mainland China Alibaba Cloud regions. By default, it uses the international region endpoints, but you can easily switch to Mainland China endpoints by modifying the variables in `core/base.py`: +This node supports both international and Mainland China Alibaba Cloud regions. To switch regions, simply select "international" or "mainland_china" from the "region" dropdown parameter available in all nodes. The node will automatically use the appropriate API endpoint and API key for the selected region. -- **International Region** (default): - - Video POST: `https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis` - - II2V POST: `https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis` - - T2I POST: `https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis` - - GET: `https://dashscope-intl.aliyuncs.com/api/v1/tasks/{task_id}` - -- **Mainland China Region**: - - Video POST: `https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis` - - II2V POST: `https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis` - - T2I POST: `https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis` - - GET: `https://dashscope.aliyuncs.com/api/v1/tasks/{task_id}` - -To switch regions, simply modify the `API_ENDPOINT_POST_VIDEO`, `API_ENDPOINT_POST_II2V`, `API_ENDPOINT_POST_T2I`, and `API_ENDPOINT_GET` variables in `core/base.py` to the corresponding Mainland China endpoints listed above. +**Note**: If you want to use the Mainland China region, you must have a separate API key for that region. Make sure to set both `DASHSCOPE_API_KEY` (for international) and `DASHSCOPE_API_KEY_CHINA` (for Mainland China) in your `.env` file. ## Centralized Endpoint Management -All API endpoints are centrally managed in the `core/base.py` file, making it easy to maintain and switch between regions. This approach ensures consistency across all nodes and simplifies future updates. The centralized management includes: - -- `API_ENDPOINT_POST_VIDEO`: For general video generation nodes (I2V, T2V, VACE) -- `API_ENDPOINT_POST_II2V`: For image-to-video with first/last frames (II2V) -- `API_ENDPOINT_POST_T2I`: For text-to-image generation (T2I) -- `API_ENDPOINT_GET`: For task result polling (shared across all nodes) +All API endpoints are centrally managed in the `core/base.py` file, making it easy to maintain and switch between regions. This approach ensures consistency across all nodes and simplifies future updates. ## Available Nodes @@ -108,35 +91,17 @@ If you're using a workspace other than your default workspace, you may need to a ### Set Environment Variable -Copy the `.env.template` file to `.env` in your ComfyUI root directory and replace the placeholder with your actual API key: +Copy the `.env.template` file to `.env` in your `config` directory and replace the placeholders with your actual API keys: + ``` +# For international region (default) DASHSCOPE_API_KEY=your_actual_api_key_here + +# For mainland China region (optional, if you have a separate key for China) +DASHSCOPE_API_KEY_CHINA=your_china_api_key_here ``` -## Usage - -### Text-to-Image Generation - -1. Add the "Wan Text-to-Image Generator" node to your workflow -2. Select the desired model (wan2.2-t2i-flash or wan2.2-t2i-plus) -3. Connect a text input with your prompt -4. Configure parameters as needed (seed, resolution, etc.) -5. Execute the node - -### Image-to-Video Generation - -1. Add the "Wan Image-to-Video Generator" node to your workflow -2. Provide a publicly accessible URL to the image you want to use as the first frame of your video -3. Select the desired model (wan2.2-i2v-flash or wan2.2-i2v-plus) -4. Connect a text input with your prompt describing the video content -5. Optionally configure the output directory where the video will be saved (can be browsed in ComfyUI) -6. Execute the node -7. The node will return both a path to the downloaded video file and the video URL -8. To preview the video, connect the output to a "Load Video (Path)" node from ComfyUI-VideoHelperSuite -9. To use the video URL directly (e.g., for sharing or further processing), you can connect the video_url output to appropriate nodes - -**Note**: The image URL must be publicly accessible (not behind authentication or on localhost). -You can use services like Imgur, cloud storage providers, or your own web server to host the image. +If you only use the international region, you only need to set `DASHSCOPE_API_KEY`. If you plan to use both regions, you should set both keys. The nodes will automatically use the appropriate key based on the region you select. ## Node Parameters @@ -326,49 +291,30 @@ This node scales videos in different directions using the Wan VACE model. ## Examples -### Text-to-Image Generation -Prompt: "Generate an image of a cat swimming under the water" +Prompt: "Generate an image of a cat" -![Text-to-Image Example](media/ComfyUI_Wan-t2i-new.png) - -### Text-to-Video Generation -1. Add the "Wan Text-to-Video Generator" node to your workflow -2. Select the desired model (wan2.2-t2v-plus) -3. Connect a text input with your prompt (e.g., "A kitten running in the moonlight") -4. Optionally configure the output directory where the video will be saved (can be browsed in ComfyUI) -5. Execute the node -6. The node will return both a path to the downloaded video file and the video URL -7. To preview the video, connect the video_file_path output to a "Load Video (Path)" node from ComfyUI-VideoHelperSuite -8. To use the video URL directly (e.g., for sharing or further processing), you can connect the video_url output to appropriate nodes - -![Text-to-Video Example](media/ComfyUI_Wan-t2v.png) - -### Image-to-Video Generation -1. First frame: Provide a URL to an image (e.g., "https://example.com/your_image.png") -2. Prompt: "a cat running in the grass" -3. Output directory: "./videos" (default) or any custom path -4. To preview: Connect the video_file_path output to a "Load Video (Path)" node from ComfyUI-VideoHelperSuite -5. To share or process further: Use the video_url output directly in your workflow - -![Image-to-Video Example](media/ComfyUI_Wan-i2v-new.png) - -### Image-to-Video (First/Last Frame) Generation -1. Add the "Wan Image-to-Video (First/Last Frame) Generator" node to your workflow -2. Select the desired model (wan2.1-kf2v-plus) -3. Provide publicly accessible URLs to the first and last frame images -4. Prompt: "a cat running in the grass"Realistic style. A black kitten looks up at the sky curiously. The camera gradually rises from eye level, ending with a top-down shot of the kitten's curious eyes." -5. Optionally configure the output directory where the video will be saved (can be browsed in ComfyUI) -6. Execute the node -7. The node will return both a path to the downloaded video file and the video URL -8. To preview the video, connect the video_file_path output to a "Load Video (Path)" node from ComfyUI-VideoHelperSuite -9. To use the video URL directly (e.g., for sharing or further processing), you can connect the video_url output to appropriate nodes - -![Image-first-last-frame-to-Video Example](media/ComfyUI_Wan-ii2v.png) +![Text-to-Image Example](media/ComfyUI_Wan-t2i.png) ## Security The API key is loaded from the `DASHSCOPE_API_KEY` environment variable and never stored in files or code, following Alibaba Cloud security best practices. +## Changelog + +### v1.1.0 - Region Selection Feature +- Added region selection parameter to all nodes, allowing users to easily switch between international and Mainland China regions +- Updated `.env.template` to include separate API key variables for international and Mainland China regions +- Modified `core/base.py` to support automatic endpoint and API key selection based on region +- Updated all generator nodes (T2I, I2V, T2V, II2V) and VACE nodes to include region selection +- Improved documentation with updated setup instructions and regional support information + +### v1.0.0 - Initial Release +- Initial release with full Wan model integration +- Support for Text-to-Image, Image-to-Video, Text-to-Video, and Image-to-Video (First/Last Frame) generation +- Full VACE model integration with 5 specialized nodes for video editing +- Dual output support (local file paths and remote URLs) for all video generation nodes +- International and Mainland China region support (manual configuration required) + ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/config/.env.template b/config/.env.template index dc829f7..5a2c98b 100644 --- a/config/.env.template +++ b/config/.env.template @@ -2,5 +2,8 @@ # Copy this file to .env and replace the placeholder with your actual API key # Visit https://dashscope.console.aliyuncs.com/apiKey to get your API key -# This key is used for all Wan models (text-to-image and image-to-video) -DASHSCOPE_API_KEY=your_actual_api_key_here \ No newline at end of file +# For international endpoint (default) +DASHSCOPE_API_KEY=your_actual_api_key_here + +# For mainland China endpoint (optional, if you have a separate key for China) +DASHSCOPE_API_KEY_CHINA=your_china_api_key_here \ No newline at end of file diff --git a/core/base.py b/core/base.py index 5716d5c..bb6b8ac 100644 --- a/core/base.py +++ b/core/base.py @@ -19,41 +19,70 @@ except ImportError: print("folder_paths not available, using default directory handling") # Load environment variables from .env file -# Try to load .env file from the current directory first -env_path = pathlib.Path(__file__).parent / '.env' +# Try multiple locations for the .env file: +# 1. config/.env (our preferred location) +# 2. .env in the project root (for backward compatibility) +# 3. Fallback to default behavior (current working directory) + +# Check config/.env first (go up one level to project root, then into config) +env_path = pathlib.Path(__file__).parent.parent / 'config' / '.env' if env_path.exists(): + print(f"Loading environment variables from: {env_path}") load_dotenv(dotenv_path=env_path) else: - # Fallback to default behavior - load_dotenv() + # Check .env in project root (go up one level to project root) + env_path = pathlib.Path(__file__).parent.parent / '.env' + if env_path.exists(): + print(f"Loading environment variables from: {env_path}") + load_dotenv(dotenv_path=env_path) + else: + # Fallback to default behavior + print("No .env file found, using default environment variable loading") + load_dotenv() class WanAPIBase: """Base class for Wan API interactions""" - # API endpoints - International region (default) - # To use Mainland China region, change these URLs: - # Video POST: https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis - # II2V POST: https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis - # T2I POST: https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis - # GET: https://dashscope.aliyuncs.com/api/v1/tasks/{task_id} - API_ENDPOINT_POST_VIDEO = "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis" - API_ENDPOINT_POST_II2V = "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis" - API_ENDPOINT_POST_T2I = "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis" - API_ENDPOINT_GET = "https://dashscope-intl.aliyuncs.com/api/v1/tasks/{task_id}" + # API endpoints for different regions + ENDPOINTS = { + "international": { + "video_post": "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis", + "ii2v_post": "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis", + "t2i_post": "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis", + "get": "https://dashscope-intl.aliyuncs.com/api/v1/tasks/{task_id}" + }, + "mainland_china": { + "video_post": "https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis", + "ii2v_post": "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis", + "t2i_post": "https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis", + "get": "https://dashscope.aliyuncs.com/api/v1/tasks/{task_id}" + } + } def __init__(self): + # Load API keys for different regions self.api_key = os.getenv('DASHSCOPE_API_KEY') + self.api_key_china = os.getenv('DASHSCOPE_API_KEY_CHINA') # Strip any extra quotes or whitespace if self.api_key: self.api_key = self.api_key.strip().strip('"\'') - print(f"Initialized WanAPIBase with API key: {self.api_key[:8] if self.api_key else 'None'}...{self.api_key[-4:] if self.api_key else ''}") - - def check_api_key(self): - """Check if API key is set in environment variables""" - if not self.api_key: + if self.api_key_china: + self.api_key_china = self.api_key_china.strip().strip('"\'') + print(f"Initialized WanAPIBase with API keys: international={self.api_key[:8] if self.api_key else 'None'}...{self.api_key[-4:] if self.api_key else ''}, china={self.api_key_china[:8] if self.api_key_china else 'None'}...{self.api_key_china[-4:] if self.api_key_china else ''}") + + def check_api_key(self, region="international"): + """Check if appropriate API key is set in environment variables""" + if region == "mainland_china" and self.api_key_china: + return self.api_key_china + elif self.api_key: + return self.api_key + else: raise ValueError("DASHSCOPE_API_KEY environment variable not set. " "Please set it before using this node.") - return self.api_key + + def get_api_endpoints(self, region="international"): + """Get the appropriate API endpoints based on region""" + return self.ENDPOINTS.get(region, self.ENDPOINTS["international"]) def prepare_images(self, images): """Convert images to base64 strings for API submission""" diff --git a/generators/i2v.py b/generators/i2v.py index dc95855..9704b2e 100644 --- a/generators/i2v.py +++ b/generators/i2v.py @@ -36,11 +36,14 @@ class WanI2VGenerator(WanAPIBase): "1080P" ] + # Define region options + REGION_OPTIONS = [ + "international", + "mainland_china" + ] + def __init__(self): super().__init__() - # Use the centralized API endpoint from the base class - # To use Mainland China region, modify API_ENDPOINT_POST_VIDEO in core/base.py - self.api_url = self.API_ENDPOINT_POST_VIDEO @classmethod def INPUT_TYPES(cls): @@ -69,6 +72,9 @@ class WanI2VGenerator(WanAPIBase): "prompt": ("STRING", { "multiline": True, "default": "A cat running on the grass" + }), + "region": (cls.REGION_OPTIONS, { + "default": "international" }) }, "optional": { @@ -99,10 +105,14 @@ class WanI2VGenerator(WanAPIBase): FUNCTION = "generate" CATEGORY = "Ru4ls/Wan" - def generate(self, model, image_url, prompt, negative_prompt="", resolution="720P", + def generate(self, model, image_url, prompt, region, negative_prompt="", resolution="720P", prompt_extend=True, watermark=False, seed=0, output_dir="./videos"): - # Check API key - self.check_api_key() + # Check API key based on region + api_key = self.check_api_key(region) + + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + api_url = endpoints["video_post"] # Prepare API payload for image-to-video generation payload = { @@ -126,15 +136,15 @@ class WanI2VGenerator(WanAPIBase): # Set headers according to DashScope documentation headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-DashScope-Async": "enable" # Wan requires async processing } try: # Make API request - print(f"Making API request to {self.api_url}") - response = requests.post(self.api_url, headers=headers, json=payload) + print(f"Making API request to {api_url}") + response = requests.post(api_url, headers=headers, json=payload) print(f"Response status code: {response.status_code}") if hasattr(response, 'text'): print(f"Response text: {response.text[:500]}...") # Print first 500 chars @@ -151,7 +161,7 @@ class WanI2VGenerator(WanAPIBase): print(f"Task created with ID: {task_id}, status: {task_status}") # Now we need to poll for the result - task_result = self.poll_task_result(task_id, output_dir) + task_result = self.poll_task_result(task_id, output_dir, region) return task_result # Return both path to downloaded video file and video URL else: raise ValueError(f"Unexpected API response format: {result}") @@ -181,16 +191,19 @@ class WanI2VGenerator(WanAPIBase): except Exception as e: raise RuntimeError(f"Failed to process API response: {str(e)}") - def poll_task_result(self, task_id, output_dir="./videos"): + def poll_task_result(self, task_id, output_dir="./videos", region="international"): """Poll for task result until completion and download video""" import time - # URL for querying task results - # To use Mainland China region, modify API_ENDPOINT_GET in core/base.py - query_url = self.API_ENDPOINT_GET.format(task_id=task_id) + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + query_url = endpoints["get"].format(task_id=task_id) + + # Check API key based on region + api_key = self.check_api_key(region) headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } diff --git a/generators/ii2v.py b/generators/ii2v.py index b68c0fd..a06d973 100644 --- a/generators/ii2v.py +++ b/generators/ii2v.py @@ -33,11 +33,14 @@ class WanII2VGenerator(WanAPIBase): "720P" ] + # Define region options + REGION_OPTIONS = [ + "international", + "mainland_china" + ] + def __init__(self): super().__init__() - # Use the centralized API endpoint from the base class - # To use Mainland China region, modify API_ENDPOINT_POST_II2V in core/base.py - self.api_url = self.API_ENDPOINT_POST_II2V @classmethod def INPUT_TYPES(cls): @@ -69,6 +72,9 @@ class WanII2VGenerator(WanAPIBase): "prompt": ("STRING", { "multiline": True, "default": "A black kitten looks up at the sky curiously, the camera gradually rises from eye level, and finally shoots from a top-down angle to capture the kitten's curious eyes." + }), + "region": (cls.REGION_OPTIONS, { + "default": "international" }) }, "optional": { @@ -99,10 +105,14 @@ class WanII2VGenerator(WanAPIBase): FUNCTION = "generate" CATEGORY = "Ru4ls/Wan" - def generate(self, model, first_frame_url, last_frame_url, prompt, negative_prompt="", + def generate(self, model, first_frame_url, last_frame_url, prompt, region, negative_prompt="", resolution="720P", prompt_extend=True, watermark=False, seed=0, output_dir="./videos"): - # Check API key - self.check_api_key() + # Check API key based on region + api_key = self.check_api_key(region) + + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + api_url = endpoints["ii2v_post"] # Prepare API payload for image-to-video generation with first and last frames payload = { @@ -127,15 +137,15 @@ class WanII2VGenerator(WanAPIBase): # Set headers according to DashScope documentation headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-DashScope-Async": "enable" # Wan requires async processing } try: # Make API request - print(f"Making API request to {self.api_url}") - response = requests.post(self.api_url, headers=headers, json=payload) + print(f"Making API request to {api_url}") + response = requests.post(api_url, headers=headers, json=payload) print(f"Response status code: {response.status_code}") if hasattr(response, 'text'): print(f"Response text: {response.text[:500]}...") # Print first 500 chars @@ -152,7 +162,7 @@ class WanII2VGenerator(WanAPIBase): print(f"Task created with ID: {task_id}, status: {task_status}") # Now we need to poll for the result - task_result = self.poll_task_result(task_id, output_dir) + task_result = self.poll_task_result(task_id, output_dir, region) return task_result # Return both path to downloaded video file and video URL else: raise ValueError(f"Unexpected API response format: {result}") @@ -182,16 +192,19 @@ class WanII2VGenerator(WanAPIBase): except Exception as e: raise RuntimeError(f"Failed to process API response: {str(e)}") - def poll_task_result(self, task_id, output_dir="./videos"): + def poll_task_result(self, task_id, output_dir="./videos", region="international"): """Poll for task result until completion and download video""" import time - # URL for querying task results - # To use Mainland China region, modify API_ENDPOINT_GET in core/base.py - query_url = self.API_ENDPOINT_GET.format(task_id=task_id) + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + query_url = endpoints["get"].format(task_id=task_id) + + # Check API key based on region + api_key = self.check_api_key(region) headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } diff --git a/generators/t2i.py b/generators/t2i.py index d461003..c656b01 100644 --- a/generators/t2i.py +++ b/generators/t2i.py @@ -40,11 +40,14 @@ class WanT2IGenerator(WanAPIBase): "512*1440" # Tall portrait ] + # Define region options + REGION_OPTIONS = [ + "international", + "mainland_china" + ] + def __init__(self): super().__init__() - # Use the centralized API endpoint from the base class - # To use Mainland China region, modify API_ENDPOINT_POST_T2I in core/base.py - self.api_url = self.API_ENDPOINT_POST_T2I self.model = "wan2.2-t2i-flash" # Using Wan Speed Edition as default @classmethod @@ -60,6 +63,9 @@ class WanT2IGenerator(WanAPIBase): }), "size": (cls.SIZE_OPTIONS, { "default": "1024*1024" + }), + "region": (cls.REGION_OPTIONS, { + "default": "international" }) }, "optional": { @@ -86,17 +92,22 @@ class WanT2IGenerator(WanAPIBase): FUNCTION = "generate" CATEGORY = "Ru4ls/Wan" - def generate(self, model, prompt, size, negative_prompt="", prompt_extend=True, watermark=False, seed=0): - # Check API key - self.check_api_key() + def generate(self, model, prompt, size, region, negative_prompt="", prompt_extend=True, watermark=False, seed=0): + # Check API key based on region + api_key = self.check_api_key(region) + + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + api_url = endpoints["t2i_post"] # Set the selected model self.model = model # Debug: Print API key status - print(f"Using API key: {self.api_key[:8]}...{self.api_key[-4:] if self.api_key else 'None'}") + print(f"Using API key: {api_key[:8]}...{api_key[-4:] if api_key else 'None'}") print(f"Selected model: {self.model}") - print(f"Using API endpoint: {self.api_url}") + print(f"Using API endpoint: {api_url}") + print(f"Selected region: {region}") # Prepare API payload for text-to-image generation - using the Wan format payload = { @@ -120,13 +131,13 @@ class WanT2IGenerator(WanAPIBase): # Set headers according to DashScope documentation headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-DashScope-Async": "enable" # Wan requires async processing } # Debug: Print request details - print(f"Request headers: {{'Authorization': 'Bearer {self.api_key[:8]}...', 'Content-Type': 'application/json', 'X-DashScope-Async': 'enable'}}") + print(f"Request headers: {{'Authorization': 'Bearer {api_key[:8]}...', 'Content-Type': 'application/json', 'X-DashScope-Async': 'enable'}}") print(f"Request payload model: {payload['model']}") print(f"Request payload prompt: {payload['input']['prompt'][:100]}...") print(f"Request payload size: {payload['parameters']['size']}") @@ -135,8 +146,8 @@ class WanT2IGenerator(WanAPIBase): try: # Make API request - print(f"Making API request to {self.api_url}") - response = requests.post(self.api_url, headers=headers, json=payload) + print(f"Making API request to {api_url}") + response = requests.post(api_url, headers=headers, json=payload) print(f"Response status code: {response.status_code}") if hasattr(response, 'text'): print(f"Response text: {response.text[:500]}...") # Print first 500 chars @@ -153,7 +164,7 @@ class WanT2IGenerator(WanAPIBase): print(f"Task created with ID: {task_id}, status: {task_status}") # Now we need to poll for the result - task_result = self.poll_task_result(task_id) + task_result = self.poll_task_result(task_id, region) return task_result else: raise ValueError(f"Unexpected API response format: {result}") @@ -183,16 +194,19 @@ class WanT2IGenerator(WanAPIBase): except Exception as e: raise RuntimeError(f"Failed to process API response: {str(e)}") - def poll_task_result(self, task_id): + def poll_task_result(self, task_id, region): """Poll for task result until completion""" import time - # URL for querying task results - # To use Mainland China region, modify API_ENDPOINT_GET in core/base.py - query_url = self.API_ENDPOINT_GET.format(task_id=task_id) + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + query_url = endpoints["get"].format(task_id=task_id) + + # Check API key based on region + api_key = self.check_api_key(region) headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } diff --git a/generators/t2v.py b/generators/t2v.py index 54e7c7c..0bd3b56 100644 --- a/generators/t2v.py +++ b/generators/t2v.py @@ -34,11 +34,14 @@ class WanT2VGenerator(WanAPIBase): "1080P" ] + # Define region options + REGION_OPTIONS = [ + "international", + "mainland_china" + ] + def __init__(self): super().__init__() - # Use the centralized API endpoint from the base class - # To use Mainland China region, modify API_ENDPOINT_POST_VIDEO in core/base.py - self.api_url = self.API_ENDPOINT_POST_VIDEO @classmethod def INPUT_TYPES(cls): @@ -64,6 +67,9 @@ class WanT2VGenerator(WanAPIBase): "prompt": ("STRING", { "multiline": True, "default": "A kitten running in the moonlight" + }), + "region": (cls.REGION_OPTIONS, { + "default": "international" }) }, "optional": { @@ -94,10 +100,14 @@ class WanT2VGenerator(WanAPIBase): FUNCTION = "generate" CATEGORY = "Ru4ls/Wan" - def generate(self, model, prompt, negative_prompt="", resolution="1080P", + def generate(self, model, prompt, region, negative_prompt="", resolution="1080P", prompt_extend=True, watermark=False, seed=0, output_dir="./videos"): - # Check API key - self.check_api_key() + # Check API key based on region + api_key = self.check_api_key(region) + + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + api_url = endpoints["video_post"] # Prepare API payload for text-to-video generation payload = { @@ -140,15 +150,15 @@ class WanT2VGenerator(WanAPIBase): # Set headers according to DashScope documentation headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-DashScope-Async": "enable" # Wan requires async processing } try: # Make API request - print(f"Making API request to {self.api_url}") - response = requests.post(self.api_url, headers=headers, json=payload) + print(f"Making API request to {api_url}") + response = requests.post(api_url, headers=headers, json=payload) print(f"Response status code: {response.status_code}") if hasattr(response, 'text'): print(f"Response text: {response.text[:500]}...") # Print first 500 chars @@ -165,7 +175,7 @@ class WanT2VGenerator(WanAPIBase): print(f"Task created with ID: {task_id}, status: {task_status}") # Now we need to poll for the result - task_result = self.poll_task_result(task_id, output_dir) + task_result = self.poll_task_result(task_id, output_dir, region) return task_result # Return both path to downloaded video file and video URL else: raise ValueError(f"Unexpected API response format: {result}") @@ -195,16 +205,19 @@ class WanT2VGenerator(WanAPIBase): except Exception as e: raise RuntimeError(f"Failed to process API response: {str(e)}") - def poll_task_result(self, task_id, output_dir="./videos"): + def poll_task_result(self, task_id, output_dir="./videos", region="international"): """Poll for task result until completion and download video""" import time - # URL for querying task results - # To use Mainland China region, modify API_ENDPOINT_GET in core/base.py - query_url = self.API_ENDPOINT_GET.format(task_id=task_id) + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + query_url = endpoints["get"].format(task_id=task_id) + + # Check API key based on region + api_key = self.check_api_key(region) headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } diff --git a/media/ComfyUI_Wan-VACE-multi-image-reference.png b/media/ComfyUI_Wan-VACE-multi-image-reference.png deleted file mode 100644 index ec7848f..0000000 Binary files a/media/ComfyUI_Wan-VACE-multi-image-reference.png and /dev/null differ diff --git a/media/ComfyUI_Wan-i2v-new.png b/media/ComfyUI_Wan-i2v-new.png deleted file mode 100644 index 2aabe7f..0000000 Binary files a/media/ComfyUI_Wan-i2v-new.png and /dev/null differ diff --git a/media/ComfyUI_Wan-ii2v.png b/media/ComfyUI_Wan-ii2v.png deleted file mode 100644 index c2454dc..0000000 Binary files a/media/ComfyUI_Wan-ii2v.png and /dev/null differ diff --git a/media/ComfyUI_Wan-t2i-new.png b/media/ComfyUI_Wan-t2i-new.png deleted file mode 100644 index f7621ce..0000000 Binary files a/media/ComfyUI_Wan-t2i-new.png and /dev/null differ diff --git a/media/ComfyUI_Wan-t2i.png b/media/ComfyUI_Wan-t2i.png new file mode 100644 index 0000000..3880eb0 Binary files /dev/null and b/media/ComfyUI_Wan-t2i.png differ diff --git a/media/ComfyUI_Wan-t2v.png b/media/ComfyUI_Wan-t2v.png deleted file mode 100644 index 5013086..0000000 Binary files a/media/ComfyUI_Wan-t2v.png and /dev/null differ diff --git a/vace/image_reference.py b/vace/image_reference.py index b212dda..defa7a3 100644 --- a/vace/image_reference.py +++ b/vace/image_reference.py @@ -41,11 +41,14 @@ class WanVACEImageReference(WanAPIBase): "1088*832" # 4:3 aspect ratio ] + # Define region options + REGION_OPTIONS = [ + "international", + "mainland_china" + ] + def __init__(self): super().__init__() - # Use the centralized API endpoint from the base class - # To use Mainland China region, modify API_ENDPOINT_POST_VIDEO in core/base.py - self.api_url = self.API_ENDPOINT_POST_VIDEO @classmethod def INPUT_TYPES(cls): @@ -76,6 +79,9 @@ class WanVACEImageReference(WanAPIBase): "multiline": True, "default": "", "tooltip": "Newline-separated URLs for reference images" + }), + "region": (cls.REGION_OPTIONS, { + "default": "international" }) }, "optional": { @@ -107,11 +113,15 @@ class WanVACEImageReference(WanAPIBase): FUNCTION = "generate" CATEGORY = "Ru4ls/Wan/VACE" - def generate(self, model, prompt, ref_images_url, obj_or_bg="", size="1280*720", + def generate(self, model, prompt, ref_images_url, region, obj_or_bg="", size="1280*720", seed=0, prompt_extend=False, watermark=False, output_dir="./videos"): - # Check API key - self.check_api_key() + # Check API key based on region + api_key = self.check_api_key(region) + + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + api_url = endpoints["video_post"] # Validate required inputs if not ref_images_url or not ref_images_url.strip(): @@ -162,16 +172,16 @@ class WanVACEImageReference(WanAPIBase): # Set headers according to DashScope documentation headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-DashScope-Async": "enable" # Wan requires async processing } try: # Make API request - print(f"Making API request to {self.api_url}") + print(f"Making API request to {api_url}") print(f"Payload: {json.dumps(payload, indent=2)}") - response = requests.post(self.api_url, headers=headers, json=payload) + response = requests.post(api_url, headers=headers, json=payload) print(f"Response status code: {response.status_code}") if hasattr(response, 'text'): print(f"Response text: {response.text[:500]}...") # Print first 500 chars @@ -188,7 +198,7 @@ class WanVACEImageReference(WanAPIBase): print(f"Task created with ID: {task_id}, status: {task_status}") # Now we need to poll for the result - task_result = self.poll_task_result(task_id, output_dir) + task_result = self.poll_task_result(task_id, output_dir, region) return task_result # Return both path to downloaded video file and video URL else: raise ValueError(f"Unexpected API response format: {result}") @@ -218,16 +228,19 @@ class WanVACEImageReference(WanAPIBase): except Exception as e: raise RuntimeError(f"Failed to process API response: {str(e)}") - def poll_task_result(self, task_id, output_dir="./videos"): + def poll_task_result(self, task_id, output_dir="./videos", region="international"): """Poll for task result until completion and download video""" import time - # URL for querying task results - # To use Mainland China region, modify API_ENDPOINT_GET in core/base.py - query_url = self.API_ENDPOINT_GET.format(task_id=task_id) + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + query_url = endpoints["get"].format(task_id=task_id) + + # Check API key based on region + api_key = self.check_api_key(region) headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } diff --git a/vace/video_edit.py b/vace/video_edit.py index 6e9dd50..f9396ea 100644 --- a/vace/video_edit.py +++ b/vace/video_edit.py @@ -63,11 +63,14 @@ class WanVACEVideoEdit(WanAPIBase): "1088*832" # 4:3 aspect ratio ] + # Define region options + REGION_OPTIONS = [ + "international", + "mainland_china" + ] + def __init__(self): super().__init__() - # Use the centralized API endpoint from the base class - # To use Mainland China region, modify API_ENDPOINT_POST_VIDEO in core/base.py - self.api_url = self.API_ENDPOINT_POST_VIDEO @classmethod def INPUT_TYPES(cls): @@ -97,6 +100,9 @@ class WanVACEVideoEdit(WanAPIBase): "video_url": ("STRING", { "default": "", "tooltip": "URL of the input video" + }), + "region": (cls.REGION_OPTIONS, { + "default": "international" }) }, "optional": { @@ -156,13 +162,17 @@ class WanVACEVideoEdit(WanAPIBase): FUNCTION = "generate" CATEGORY = "Ru4ls/Wan/VACE" - def generate(self, model, prompt, video_url, ref_images_url="", mask_image_url="", + def generate(self, model, prompt, video_url, region, ref_images_url="", mask_image_url="", mask_frame_id=1, mask_video_url="", control_condition="", mask_type="tracking", expand_ratio=0.05, expand_mode="hull", size="1280*720", seed=0, prompt_extend=False, watermark=False, output_dir="./videos"): - # Check API key - self.check_api_key() + # Check API key based on region + api_key = self.check_api_key(region) + + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + api_url = endpoints["video_post"] # Prepare API payload payload = { @@ -213,16 +223,16 @@ class WanVACEVideoEdit(WanAPIBase): # Set headers according to DashScope documentation headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-DashScope-Async": "enable" # Wan requires async processing } try: # Make API request - print(f"Making API request to {self.api_url}") + print(f"Making API request to {api_url}") print(f"Payload: {json.dumps(payload, indent=2)}") - response = requests.post(self.api_url, headers=headers, json=payload) + response = requests.post(api_url, headers=headers, json=payload) print(f"Response status code: {response.status_code}") if hasattr(response, 'text'): print(f"Response text: {response.text[:500]}...") # Print first 500 chars @@ -239,7 +249,7 @@ class WanVACEVideoEdit(WanAPIBase): print(f"Task created with ID: {task_id}, status: {task_status}") # Now we need to poll for the result - task_result = self.poll_task_result(task_id, output_dir) + task_result = self.poll_task_result(task_id, output_dir, region) return task_result # Return both path to downloaded video file and video URL else: raise ValueError(f"Unexpected API response format: {result}") @@ -269,16 +279,19 @@ class WanVACEVideoEdit(WanAPIBase): except Exception as e: raise RuntimeError(f"Failed to process API response: {str(e)}") - def poll_task_result(self, task_id, output_dir="./videos"): - """"Poll for task result until completion and download video""" + def poll_task_result(self, task_id, output_dir="./videos", region="international"): + """Poll for task result until completion and download video""" import time - # URL for querying task results - # To use Mainland China region, modify API_ENDPOINT_GET in core/base.py - query_url = self.API_ENDPOINT_GET.format(task_id=task_id) + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + query_url = endpoints["get"].format(task_id=task_id) + + # Check API key based on region + api_key = self.check_api_key(region) headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } diff --git a/vace/video_extension.py b/vace/video_extension.py index 263d0f3..d0ec433 100644 --- a/vace/video_extension.py +++ b/vace/video_extension.py @@ -41,11 +41,14 @@ class WanVACEVideoExtension(WanAPIBase): "scribble" # Extract line art structure ] + # Define region options + REGION_OPTIONS = [ + "international", + "mainland_china" + ] + def __init__(self): super().__init__() - # Use the centralized API endpoint from the base class - # To use Mainland China region, modify API_ENDPOINT_POST_VIDEO in core/base.py - self.api_url = self.API_ENDPOINT_POST_VIDEO @classmethod def INPUT_TYPES(cls): @@ -71,6 +74,9 @@ class WanVACEVideoExtension(WanAPIBase): "prompt": ("STRING", { "multiline": True, "default": "Extend the video with the following description" + }), + "region": (cls.REGION_OPTIONS, { + "default": "international" }) }, "optional": { @@ -117,12 +123,16 @@ class WanVACEVideoExtension(WanAPIBase): FUNCTION = "generate" CATEGORY = "Ru4ls/Wan/VACE" - def generate(self, model, prompt, first_frame_url="", last_frame_url="", + def generate(self, model, prompt, region, first_frame_url="", last_frame_url="", first_clip_url="", last_clip_url="", video_url="", control_condition="", seed=0, prompt_extend=False, watermark=False, output_dir="./videos"): - # Check API key - self.check_api_key() + # Check API key based on region + api_key = self.check_api_key(region) + + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + api_url = endpoints["video_post"] # Prepare API payload payload = { @@ -167,16 +177,16 @@ class WanVACEVideoExtension(WanAPIBase): # Set headers according to DashScope documentation headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-DashScope-Async": "enable" # Wan requires async processing } try: # Make API request - print(f"Making API request to {self.api_url}") + print(f"Making API request to {api_url}") print(f"Payload: {json.dumps(payload, indent=2)}") - response = requests.post(self.api_url, headers=headers, json=payload) + response = requests.post(api_url, headers=headers, json=payload) print(f"Response status code: {response.status_code}") if hasattr(response, 'text'): print(f"Response text: {response.text[:500]}...") # Print first 500 chars @@ -193,7 +203,7 @@ class WanVACEVideoExtension(WanAPIBase): print(f"Task created with ID: {task_id}, status: {task_status}") # Now we need to poll for the result - task_result = self.poll_task_result(task_id, output_dir) + task_result = self.poll_task_result(task_id, output_dir, region) return task_result # Return both path to downloaded video file and video URL else: raise ValueError(f"Unexpected API response format: {result}") @@ -223,16 +233,19 @@ class WanVACEVideoExtension(WanAPIBase): except Exception as e: raise RuntimeError(f"Failed to process API response: {str(e)}") - def poll_task_result(self, task_id, output_dir="./videos"): + def poll_task_result(self, task_id, output_dir="./videos", region="international"): """Poll for task result until completion and download video""" import time - # URL for querying task results - # To use Mainland China region, modify API_ENDPOINT_GET in core/base.py - query_url = self.API_ENDPOINT_GET.format(task_id=task_id) + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + query_url = endpoints["get"].format(task_id=task_id) + + # Check API key based on region + api_key = self.check_api_key(region) headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } diff --git a/vace/video_outpainting.py b/vace/video_outpainting.py index 093c7fb..a093784 100644 --- a/vace/video_outpainting.py +++ b/vace/video_outpainting.py @@ -32,11 +32,14 @@ class WanVACEVideoOutpainting(WanAPIBase): "wan2.1-vace-plus" # Professional Edition ] + # Define region options + REGION_OPTIONS = [ + "international", + "mainland_china" + ] + def __init__(self): super().__init__() - # Use the centralized API endpoint from the base class - # To use Mainland China region, modify API_ENDPOINT_POST_VIDEO in core/base.py - self.api_url = self.API_ENDPOINT_POST_VIDEO @classmethod def INPUT_TYPES(cls): @@ -66,6 +69,9 @@ class WanVACEVideoOutpainting(WanAPIBase): "video_url": ("STRING", { "default": "", "tooltip": "URL of the input video" + }), + "region": (cls.REGION_OPTIONS, { + "default": "international" }) }, "optional": { @@ -113,12 +119,16 @@ class WanVACEVideoOutpainting(WanAPIBase): FUNCTION = "generate" CATEGORY = "Ru4ls/Wan/VACE" - def generate(self, model, prompt, video_url, top_scale=1.0, bottom_scale=1.0, + def generate(self, model, prompt, video_url, region, top_scale=1.0, bottom_scale=1.0, left_scale=1.0, right_scale=1.0, seed=0, prompt_extend=False, watermark=False, output_dir="./videos"): - # Check API key - self.check_api_key() + # Check API key based on region + api_key = self.check_api_key(region) + + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + api_url = endpoints["video_post"] # Prepare API payload payload = { @@ -150,16 +160,16 @@ class WanVACEVideoOutpainting(WanAPIBase): # Set headers according to DashScope documentation headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-DashScope-Async": "enable" # Wan requires async processing } try: # Make API request - print(f"Making API request to {self.api_url}") + print(f"Making API request to {api_url}") print(f"Payload: {json.dumps(payload, indent=2)}") - response = requests.post(self.api_url, headers=headers, json=payload) + response = requests.post(api_url, headers=headers, json=payload) print(f"Response status code: {response.status_code}") if hasattr(response, 'text'): print(f"Response text: {response.text[:500]}...") # Print first 500 chars @@ -176,7 +186,7 @@ class WanVACEVideoOutpainting(WanAPIBase): print(f"Task created with ID: {task_id}, status: {task_status}") # Now we need to poll for the result - task_result = self.poll_task_result(task_id, output_dir) + task_result = self.poll_task_result(task_id, output_dir, region) return task_result # Return both path to downloaded video file and video URL else: raise ValueError(f"Unexpected API response format: {result}") @@ -206,16 +216,19 @@ class WanVACEVideoOutpainting(WanAPIBase): except Exception as e: raise RuntimeError(f"Failed to process API response: {str(e)}") - def poll_task_result(self, task_id, output_dir="./videos"): + def poll_task_result(self, task_id, output_dir="./videos", region="international"): """Poll for task result until completion and download video""" import time - # URL for querying task results - # To use Mainland China region, modify API_ENDPOINT_GET in core/base.py - query_url = self.API_ENDPOINT_GET.format(task_id=task_id) + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + query_url = endpoints["get"].format(task_id=task_id) + + # Check API key based on region + api_key = self.check_api_key(region) headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } diff --git a/vace/video_repainting.py b/vace/video_repainting.py index 98240d9..9a9c468 100644 --- a/vace/video_repainting.py +++ b/vace/video_repainting.py @@ -40,11 +40,14 @@ class WanVACEVideoRepainting(WanAPIBase): "scribble" # Extract line art structure ] + # Define region options + REGION_OPTIONS = [ + "international", + "mainland_china" + ] + def __init__(self): super().__init__() - # Use the centralized API endpoint from the base class - # To use Mainland China region, modify API_ENDPOINT_POST_VIDEO in core/base.py - self.api_url = self.API_ENDPOINT_POST_VIDEO @classmethod def INPUT_TYPES(cls): @@ -74,6 +77,9 @@ class WanVACEVideoRepainting(WanAPIBase): "video_url": ("STRING", { "default": "", "tooltip": "URL of the input video" + }), + "region": (cls.REGION_OPTIONS, { + "default": "international" }) }, "optional": { @@ -111,11 +117,15 @@ class WanVACEVideoRepainting(WanAPIBase): FUNCTION = "generate" CATEGORY = "Ru4ls/Wan/VACE" - def generate(self, model, prompt, video_url, ref_images_url="", control_condition="depth", + def generate(self, model, prompt, video_url, region, ref_images_url="", control_condition="depth", strength=1.0, seed=0, prompt_extend=False, watermark=False, output_dir="./videos"): - # Check API key - self.check_api_key() + # Check API key based on region + api_key = self.check_api_key(region) + + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + api_url = endpoints["video_post"] # Prepare API payload payload = { @@ -148,16 +158,16 @@ class WanVACEVideoRepainting(WanAPIBase): # Set headers according to DashScope documentation headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-DashScope-Async": "enable" # Wan requires async processing } try: # Make API request - print(f"Making API request to {self.api_url}") + print(f"Making API request to {api_url}") print(f"Payload: {json.dumps(payload, indent=2)}") - response = requests.post(self.api_url, headers=headers, json=payload) + response = requests.post(api_url, headers=headers, json=payload) print(f"Response status code: {response.status_code}") if hasattr(response, 'text'): print(f"Response text: {response.text[:500]}...") # Print first 500 chars @@ -174,7 +184,7 @@ class WanVACEVideoRepainting(WanAPIBase): print(f"Task created with ID: {task_id}, status: {task_status}") # Now we need to poll for the result - task_result = self.poll_task_result(task_id, output_dir) + task_result = self.poll_task_result(task_id, output_dir, region) return task_result # Return both path to downloaded video file and video URL else: raise ValueError(f"Unexpected API response format: {result}") @@ -204,16 +214,19 @@ class WanVACEVideoRepainting(WanAPIBase): except Exception as e: raise RuntimeError(f"Failed to process API response: {str(e)}") - def poll_task_result(self, task_id, output_dir="./videos"): + def poll_task_result(self, task_id, output_dir="./videos", region="international"): """Poll for task result until completion and download video""" import time - # URL for querying task results - # To use Mainland China region, modify API_ENDPOINT_GET in core/base.py - query_url = self.API_ENDPOINT_GET.format(task_id=task_id) + # Get the appropriate API endpoints based on region + endpoints = self.get_api_endpoints(region) + query_url = endpoints["get"].format(task_id=task_id) + + # Check API key based on region + api_key = self.check_api_key(region) headers = { - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }