fix bug
This commit is contained in:
@@ -156,9 +156,48 @@ When using T2V-01-Director or I2V-01-Director models, you can use movement instr
|
||||
3. Select an I2V model (I2V-01-Director recommended)
|
||||
4. Enter API key and optionally write a prompt with movement instructions
|
||||
5. Run to get a task_id
|
||||
6. Use **Check Video Status** node to monitor progress
|
||||
7. Once status is "success", use **Download Video** node to save the video
|
||||
6. Use **Check Video Status** node to monitor progress (it will automatically wait until completion)
|
||||
7. Once status is "success", use **Download Video** node with the file_id to save the video
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
### Check Video Status Node
|
||||
|
||||
This node checks the status of video generation tasks and waits until completion.
|
||||
|
||||
#### Input Parameters:
|
||||
- **api_key**: MiniMax API key
|
||||
- **task_id**: Task ID from Video Generation node
|
||||
- **check_interval**: Check interval in seconds (default: 30, range: 10-300)
|
||||
- **max_wait_time**: Maximum wait time in seconds (default: 1800/30 minutes, range: 5 minutes-2 hours)
|
||||
|
||||
#### Output:
|
||||
- **status**: Task status (processing, success, failed)
|
||||
- **file_id**: File ID of the generated video (required for downloading)
|
||||
- **video_url**: Download URL for the generated video (may be empty)
|
||||
- **cover_image_url**: URL for the video cover image
|
||||
|
||||
#### Features:
|
||||
- **Automatic polling**: Continuously checks status until completion or failure
|
||||
- **Progress tracking**: Shows elapsed time, remaining time, and attempt count
|
||||
- **Timeout protection**: Prevents infinite waiting with configurable maximum wait time
|
||||
- **Smart intervals**: Customizable check intervals to balance responsiveness and API usage
|
||||
|
||||
### Download Video Node
|
||||
|
||||
This node downloads generated videos using the file_id from the status check.
|
||||
|
||||
#### Input Parameters:
|
||||
- **api_key**: MiniMax API key
|
||||
- **file_id**: File ID from Check Video Status node
|
||||
- **filename_prefix**: Prefix for the downloaded video file
|
||||
|
||||
#### Output:
|
||||
- **video_path**: Absolute path to the downloaded video file
|
||||
|
||||
#### Process:
|
||||
1. Uses file_id to retrieve download URL from MiniMax file API
|
||||
2. Downloads the video file with progress tracking
|
||||
3. Saves to ComfyUI output directory with timestamp
|
||||
|
||||
+21
-7
@@ -56,29 +56,43 @@
|
||||
|
||||
### 检查视频状态节点
|
||||
|
||||
此节点检查视频生成任务的状态。
|
||||
此节点检查视频生成任务的状态并等待直到完成。
|
||||
|
||||
#### 输入参数:
|
||||
- **api_key**: MiniMax API 密钥
|
||||
- **task_id**: 来自视频生成节点的任务 ID
|
||||
- **check_interval**: 检查间隔(秒)(默认:30,范围:10-300)
|
||||
- **max_wait_time**: 最大等待时间(秒)(默认:1800/30分钟,范围:5分钟-2小时)
|
||||
|
||||
#### 输出:
|
||||
- **status**: 任务状态(processing, success, failed)
|
||||
- **file_id**: 生成视频的文件 ID
|
||||
- **video_url**: 生成视频的下载 URL
|
||||
- **file_id**: 生成视频的文件 ID(下载时必需)
|
||||
- **video_url**: 生成视频的下载 URL(可能为空)
|
||||
- **cover_image_url**: 视频封面图像的 URL
|
||||
|
||||
#### 功能特性:
|
||||
- **自动轮询**: 持续检查状态直到完成或失败
|
||||
- **进度跟踪**: 显示已用时间、剩余时间和尝试次数
|
||||
- **超时保护**: 通过可配置的最大等待时间防止无限等待
|
||||
- **智能间隔**: 可自定义检查间隔,平衡响应性和 API 使用
|
||||
|
||||
### 下载视频节点
|
||||
|
||||
此节点将生成的视频从 URL 下载到本地存储。
|
||||
此节点使用状态检查的 file_id 下载生成的视频。
|
||||
|
||||
#### 输入参数:
|
||||
- **video_url**: 来自检查视频状态节点的视频 URL
|
||||
- **api_key**: MiniMax API 密钥
|
||||
- **file_id**: 来自检查视频状态节点的文件 ID
|
||||
- **filename_prefix**: 下载视频文件的前缀
|
||||
|
||||
#### 输出:
|
||||
- **video_path**: 下载视频文件的绝对路径
|
||||
|
||||
#### 处理流程:
|
||||
1. 使用 file_id 从 MiniMax 文件 API 获取下载 URL
|
||||
2. 带进度跟踪的视频文件下载
|
||||
3. 保存到 ComfyUI 输出目录并添加时间戳
|
||||
|
||||
## 视频工作流示例
|
||||
|
||||
### 文生视频工作流:
|
||||
@@ -94,8 +108,8 @@
|
||||
3. 选择 I2V 模型(推荐 I2V-01-Director)
|
||||
4. 输入 API 密钥,可选择性地编写带有运镜指令的提示词
|
||||
5. 运行以获取 task_id
|
||||
6. 使用**检查视频状态**节点监控进度
|
||||
7. 状态为 "success" 后,使用**下载视频**节点保存视频
|
||||
6. 使用**检查视频状态**节点监控进度(将自动等待直到完成)
|
||||
7. 状态为 "success" 后,使用**下载视频**节点和 file_id 保存视频
|
||||
|
||||
## 安装
|
||||
|
||||
|
||||
+75
-19
@@ -1,21 +1,23 @@
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
import folder_paths
|
||||
from urllib.parse import urlparse
|
||||
|
||||
class DownloadVideo:
|
||||
"""
|
||||
Download Video from URL node for ComfyUI
|
||||
Download Video using file_id from MiniMax API
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
self.retrieve_api = "https://api.minimaxi.chat/v1/files/retrieve"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"video_url": ("STRING", {"multiline": False, "placeholder": "Video URL from status check"}),
|
||||
"api_key": ("STRING", {"multiline": False}),
|
||||
"file_id": ("STRING", {"multiline": False, "placeholder": "File ID from video status check"}),
|
||||
"filename_prefix": ("STRING", {"default": "minimax_video", "multiline": False}),
|
||||
}
|
||||
}
|
||||
@@ -25,19 +27,69 @@ class DownloadVideo:
|
||||
FUNCTION = "download_video"
|
||||
CATEGORY = "JM-MiniMax-API/Video"
|
||||
|
||||
def download_video(self, video_url, filename_prefix):
|
||||
if not video_url or not video_url.strip():
|
||||
raise ValueError("Video URL must be provided")
|
||||
def download_video(self, api_key, file_id, filename_prefix):
|
||||
if not api_key or not file_id:
|
||||
raise ValueError("API Key and File ID must be provided")
|
||||
|
||||
video_url = video_url.strip()
|
||||
file_id = file_id.strip()
|
||||
|
||||
try:
|
||||
# Validate URL
|
||||
parsed_url = urlparse(video_url)
|
||||
if not parsed_url.scheme or not parsed_url.netloc:
|
||||
raise ValueError("Invalid video URL provided")
|
||||
print(f"Step 1: Retrieving download URL for file_id: {file_id}")
|
||||
|
||||
print(f"Downloading video from: {video_url}")
|
||||
# Step 1: Get download URL using file_id
|
||||
retrieve_url = f"{self.retrieve_api}?file_id={file_id}"
|
||||
headers = {
|
||||
'authorization': f'Bearer {api_key}',
|
||||
}
|
||||
|
||||
print(f"Retrieve URL: {retrieve_url}")
|
||||
|
||||
response = requests.get(retrieve_url, headers=headers, timeout=30)
|
||||
|
||||
print(f"Retrieve response status code: {response.status_code}")
|
||||
|
||||
if response.status_code != 200:
|
||||
raise RuntimeError(f"Failed to retrieve file info. Status code: {response.status_code}")
|
||||
|
||||
try:
|
||||
retrieve_data = response.json()
|
||||
print(f"Retrieve response data: {json.dumps(retrieve_data, indent=2)}")
|
||||
except json.JSONDecodeError:
|
||||
print(f"Raw retrieve response content: {response.content}")
|
||||
raise RuntimeError("Failed to decode JSON response from file retrieve API")
|
||||
|
||||
# Check for API errors
|
||||
base_resp = retrieve_data.get("base_resp", {})
|
||||
status_code = base_resp.get("status_code")
|
||||
status_msg = base_resp.get("status_msg", "Unknown error")
|
||||
|
||||
if status_code is not None and status_code != 0:
|
||||
error_messages = {
|
||||
1002: "Rate limit exceeded, please try again later",
|
||||
1004: "Authentication failed, please check your API key",
|
||||
1008: "Insufficient account balance",
|
||||
2013: "Invalid parameters, please check your file_id",
|
||||
2049: "Invalid API key, please check your API key"
|
||||
}
|
||||
error_msg = error_messages.get(status_code, f"API Error {status_code}: {status_msg}")
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
# Extract download URL
|
||||
file_info = retrieve_data.get("file", {})
|
||||
download_url = file_info.get("download_url", "")
|
||||
|
||||
if not download_url:
|
||||
raise RuntimeError("No download URL found in API response")
|
||||
|
||||
print(f"Download URL retrieved: {download_url}")
|
||||
|
||||
# Step 2: Download the video file
|
||||
print(f"Step 2: Downloading video from URL")
|
||||
|
||||
# Validate download URL
|
||||
parsed_url = urlparse(download_url)
|
||||
if not parsed_url.scheme or not parsed_url.netloc:
|
||||
raise ValueError("Invalid download URL received from API")
|
||||
|
||||
# Create output directory
|
||||
output_dir = folder_paths.get_output_directory()
|
||||
@@ -66,11 +118,13 @@ class DownloadVideo:
|
||||
video_filepath = os.path.join(output_dir, video_filename)
|
||||
|
||||
# Download video with progress tracking
|
||||
with requests.get(video_url, stream=True, timeout=60) as response:
|
||||
response.raise_for_status()
|
||||
print(f"Downloading to: {video_filepath}")
|
||||
|
||||
with requests.get(download_url, stream=True, timeout=120) as download_response:
|
||||
download_response.raise_for_status()
|
||||
|
||||
# Get content length for progress tracking
|
||||
content_length = response.headers.get('content-length')
|
||||
content_length = download_response.headers.get('content-length')
|
||||
if content_length:
|
||||
total_size = int(content_length)
|
||||
print(f"Video file size: {total_size / (1024*1024):.1f} MB")
|
||||
@@ -80,7 +134,7 @@ class DownloadVideo:
|
||||
|
||||
downloaded_size = 0
|
||||
with open(video_filepath, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
for chunk in download_response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
@@ -90,13 +144,15 @@ class DownloadVideo:
|
||||
progress = (downloaded_size / total_size) * 100
|
||||
print(f"Download progress: {progress:.1f}%")
|
||||
|
||||
print(f"Video downloaded successfully to: {video_filepath}")
|
||||
print(f"Final file size: {os.path.getsize(video_filepath) / (1024*1024):.1f} MB")
|
||||
final_size = os.path.getsize(video_filepath)
|
||||
print(f"Video downloaded successfully!")
|
||||
print(f"File path: {video_filepath}")
|
||||
print(f"Final file size: {final_size / (1024*1024):.1f} MB")
|
||||
|
||||
return (os.path.abspath(video_filepath),)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Download error: {str(e)}")
|
||||
print(f"Request error: {str(e)}")
|
||||
raise RuntimeError(f"Failed to download video: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {str(e)}")
|
||||
|
||||
Reference in New Issue
Block a user