增加图生视频节点
This commit is contained in:
@@ -2,10 +2,12 @@ from .nodes.api_key import KLingAIAPIKey
|
||||
from .nodes.text2video import KLingAIText2Video
|
||||
from .nodes.query_status import KLingAIQueryStatus
|
||||
from .nodes.video_downloader import KLingAIVideoDownloader
|
||||
from .nodes.image2video import KLingAIImage2Video
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"JM-KLingAI-API/api-key": KLingAIAPIKey,
|
||||
"JM-KLingAI-API/text2video": KLingAIText2Video,
|
||||
"JM-KLingAI-API/image2video": KLingAIImage2Video,
|
||||
"JM-KLingAI-API/query-status": KLingAIQueryStatus,
|
||||
"JM-KLingAI-API/video-downloader": KLingAIVideoDownloader
|
||||
}
|
||||
@@ -13,6 +15,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"JM-KLingAI-API/api-key": "KLingAI API Key",
|
||||
"JM-KLingAI-API/text2video": "KLingAI Text to Video",
|
||||
"JM-KLingAI-API/image2video": "KLingAI Image to Video",
|
||||
"JM-KLingAI-API/query-status": "KLingAI Query Status",
|
||||
"JM-KLingAI-API/video-downloader": "KLingAI Video Downloader"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
import json
|
||||
import random
|
||||
import requests
|
||||
import base64
|
||||
import os
|
||||
from PIL import Image
|
||||
import io
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
class KLingAIImage2Video:
|
||||
"""
|
||||
KLingAI Image to Video Node
|
||||
创建图生视频任务节点
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.api_base = "https://api.klingai.com"
|
||||
self.endpoint = "/v1/videos/image2video"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "", "multiline": False}),
|
||||
"image": ("IMAGE",),
|
||||
},
|
||||
"optional": {
|
||||
"model_name": (["kling-v1", "kling-v1-5", "kling-v1-6"], {"default": "kling-v1"}),
|
||||
"positive_prompt": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"placeholder": "正向提示词 (最多2500字符)"
|
||||
}),
|
||||
"negative_prompt": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"placeholder": "负向提示词 (最多2500字符)"
|
||||
}),
|
||||
"cfg_scale": ("FLOAT", {
|
||||
"default": 0.5,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.1
|
||||
}),
|
||||
"mode": (["std", "pro"], {"default": "std"}),
|
||||
"duration": (["5", "10"], {"default": "5"}),
|
||||
"image_tail": ("IMAGE",),
|
||||
"use_camera_control": ("BOOLEAN", {"default": False}),
|
||||
"camera_type": (["simple", "down_back", "forward_up", "right_turn_forward", "left_turn_forward"], {"default": "simple"}),
|
||||
"camera_horizontal": ("FLOAT", {
|
||||
"default": 0.0,
|
||||
"min": -10.0,
|
||||
"max": 10.0,
|
||||
"step": 0.5
|
||||
}),
|
||||
"camera_vertical": ("FLOAT", {
|
||||
"default": 0.0,
|
||||
"min": -10.0,
|
||||
"max": 10.0,
|
||||
"step": 0.5
|
||||
}),
|
||||
"camera_pan": ("FLOAT", {
|
||||
"default": 0.0,
|
||||
"min": -10.0,
|
||||
"max": 10.0,
|
||||
"step": 0.5
|
||||
}),
|
||||
"camera_tilt": ("FLOAT", {
|
||||
"default": 0.0,
|
||||
"min": -10.0,
|
||||
"max": 10.0,
|
||||
"step": 0.5
|
||||
}),
|
||||
"camera_roll": ("FLOAT", {
|
||||
"default": 0.0,
|
||||
"min": -10.0,
|
||||
"max": 10.0,
|
||||
"step": 0.5
|
||||
}),
|
||||
"camera_zoom": ("FLOAT", {
|
||||
"default": 0.0,
|
||||
"min": -10.0,
|
||||
"max": 10.0,
|
||||
"step": 0.5
|
||||
}),
|
||||
"external_task_id": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": False,
|
||||
"placeholder": "可选:自定义任务ID(需要确保唯一性)"
|
||||
}),
|
||||
"callback_url": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": False,
|
||||
"placeholder": "可选:任务结果回调通知地址"
|
||||
}),
|
||||
"seed": ("INT", {
|
||||
"default": -1,
|
||||
"min": -1,
|
||||
"max": 0xffffffffffffffff
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "INT")
|
||||
RETURN_NAMES = ("task_id", "task_status", "created_at", "updated_at", "seed")
|
||||
FUNCTION = "create_image2video_task"
|
||||
CATEGORY = "JM-KLingAI-API/image-2-video"
|
||||
|
||||
def tensor_to_pil(self, tensor):
|
||||
"""将ComfyUI的Tensor图像转换为PIL图像"""
|
||||
if tensor is None:
|
||||
return None
|
||||
|
||||
# 确保是一个单一的图像,不是批次
|
||||
if len(tensor.shape) == 4:
|
||||
tensor = tensor[0]
|
||||
|
||||
# 转换为适合PIL的格式
|
||||
i = 255. * tensor.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||||
return img
|
||||
|
||||
def image_to_base64(self, image):
|
||||
"""将ComfyUI图像转换为base64字符串"""
|
||||
if image is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
# 处理tensor格式的图像
|
||||
if isinstance(image, torch.Tensor):
|
||||
pil_image = self.tensor_to_pil(image)
|
||||
if pil_image is None:
|
||||
return None
|
||||
else:
|
||||
# 原来的处理方法 (已不再使用,保留以防万一)
|
||||
img = Image.fromarray((image[0] * 255).astype('uint8'), 'RGB')
|
||||
pil_image = img
|
||||
|
||||
# 转换为base64
|
||||
buffered = io.BytesIO()
|
||||
pil_image.save(buffered, format="JPEG")
|
||||
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
||||
|
||||
except Exception as e:
|
||||
print(f"图像转换base64错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_camera_control(self, camera_type, h, v, pan, tilt, roll, zoom):
|
||||
"""构建摄像机控制参数"""
|
||||
camera_control = {"type": camera_type}
|
||||
|
||||
# 如果是simple类型,添加config配置
|
||||
if camera_type == "simple":
|
||||
# 确保只有一个方向参数不为0
|
||||
non_zero_params = sum(1 for p in [h, v, pan, tilt, roll, zoom] if abs(p) > 0.001)
|
||||
|
||||
if non_zero_params > 1:
|
||||
print("警告:simple类型摄像机控制只能设置一个方向参数,将使用第一个非零参数")
|
||||
|
||||
camera_control["config"] = {
|
||||
"horizontal": h,
|
||||
"vertical": v,
|
||||
"pan": pan,
|
||||
"tilt": tilt,
|
||||
"roll": roll,
|
||||
"zoom": zoom
|
||||
}
|
||||
|
||||
return camera_control
|
||||
|
||||
def validate_camera_params(self, camera_type, h, v, pan, tilt, roll, zoom):
|
||||
"""验证摄像机参数合法性"""
|
||||
if camera_type == "simple":
|
||||
non_zero_count = sum(1 for p in [h, v, pan, tilt, roll, zoom] if abs(p) > 0.001)
|
||||
if non_zero_count > 1:
|
||||
return False, "simple类型摄像机控制只能设置一个方向参数不为0"
|
||||
if non_zero_count == 0:
|
||||
return False, "simple类型摄像机控制至少需要一个方向参数不为0"
|
||||
return True, ""
|
||||
|
||||
def create_image2video_task(self, api_token, image, model_name="kling-v1",
|
||||
positive_prompt="", negative_prompt="",
|
||||
cfg_scale=0.5, mode="std", duration="5",
|
||||
image_tail=None, use_camera_control=False,
|
||||
camera_type="simple", camera_horizontal=0.0,
|
||||
camera_vertical=0.0, camera_pan=0.0,
|
||||
camera_tilt=0.0, camera_roll=0.0,
|
||||
camera_zoom=0.0, external_task_id="",
|
||||
callback_url="", seed=-1):
|
||||
"""
|
||||
创建图生视频任务
|
||||
"""
|
||||
try:
|
||||
# 验证必要参数
|
||||
if not api_token:
|
||||
raise ValueError("API令牌不能为空")
|
||||
if image is None:
|
||||
raise ValueError("输入图像不能为空")
|
||||
|
||||
# 生成随机种子(本地使用,不发送给API)
|
||||
if seed == -1:
|
||||
seed = random.randint(0, 0xffffffffffffffff)
|
||||
|
||||
# 验证文本提示词长度
|
||||
if positive_prompt and len(positive_prompt) > 2500:
|
||||
raise ValueError("正向提示词不能超过2500字符")
|
||||
if negative_prompt and len(negative_prompt) > 2500:
|
||||
raise ValueError("负向提示词不能超过2500字符")
|
||||
|
||||
# 验证摄像机参数
|
||||
if use_camera_control:
|
||||
valid, msg = self.validate_camera_params(
|
||||
camera_type, camera_horizontal, camera_vertical,
|
||||
camera_pan, camera_tilt, camera_roll, camera_zoom
|
||||
)
|
||||
if not valid:
|
||||
raise ValueError(f"摄像机参数错误: {msg}")
|
||||
|
||||
# 转换图像为base64
|
||||
image_base64 = self.image_to_base64(image)
|
||||
if not image_base64:
|
||||
raise ValueError("图像转换为base64失败")
|
||||
|
||||
image_tail_base64 = self.image_to_base64(image_tail) if image_tail is not None else None
|
||||
|
||||
# 准备请求头
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_token.strip()}"
|
||||
}
|
||||
|
||||
# 准备请求体
|
||||
payload = {
|
||||
"model_name": model_name,
|
||||
"image": image_base64,
|
||||
"cfg_scale": float(cfg_scale),
|
||||
"mode": mode,
|
||||
"duration": duration
|
||||
}
|
||||
|
||||
# 添加可选参数
|
||||
if positive_prompt:
|
||||
payload["prompt"] = positive_prompt
|
||||
if negative_prompt:
|
||||
payload["negative_prompt"] = negative_prompt
|
||||
if image_tail_base64:
|
||||
payload["image_tail"] = image_tail_base64
|
||||
if external_task_id:
|
||||
payload["external_task_id"] = external_task_id
|
||||
if callback_url:
|
||||
payload["callback_url"] = callback_url
|
||||
|
||||
# 添加摄像机控制参数
|
||||
if use_camera_control:
|
||||
payload["camera_control"] = self.get_camera_control(
|
||||
camera_type, camera_horizontal, camera_vertical,
|
||||
camera_pan, camera_tilt, camera_roll, camera_zoom
|
||||
)
|
||||
|
||||
# 发送API请求
|
||||
url = f"{self.api_base}{self.endpoint}"
|
||||
print(f"正在发送请求到: {url}")
|
||||
print(f"使用本地种子: {seed} (仅用于本地,未发送给API)")
|
||||
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
response_data = response.json()
|
||||
|
||||
print(f"响应状态码: {response.status_code}")
|
||||
print(f"响应数据: {json.dumps(response_data, indent=2)}")
|
||||
|
||||
# 检查错误并提供详细错误信息
|
||||
if response.status_code != 200:
|
||||
error_code = response_data.get('code')
|
||||
error_message = response_data.get('message')
|
||||
request_id = response_data.get('request_id')
|
||||
raise Exception(f"API请求失败 (错误码: {error_code}): {error_message} (请求ID: {request_id})")
|
||||
|
||||
# 提取响应数据
|
||||
data = response_data.get("data", {})
|
||||
task_id = data.get("task_id", "")
|
||||
task_status = data.get("task_status", "")
|
||||
created_at = str(data.get("created_at", ""))
|
||||
updated_at = str(data.get("updated_at", ""))
|
||||
|
||||
if not task_id:
|
||||
raise Exception("API未返回任务ID")
|
||||
|
||||
print(f"成功创建图生视频任务,任务ID: {task_id} (本地种子: {seed})")
|
||||
return (task_id, task_status, created_at, updated_at, seed)
|
||||
|
||||
except ValueError as ve:
|
||||
print(f"参数验证错误: {str(ve)}")
|
||||
return (None, None, None, None, seed)
|
||||
except Exception as e:
|
||||
print(f"创建图生视频任务错误: {str(e)}")
|
||||
return (None, None, None, None, seed)
|
||||
|
||||
def IS_CHANGED(self, api_token, image, model_name="kling-v1",
|
||||
positive_prompt="", negative_prompt="",
|
||||
cfg_scale=0.5, mode="std", duration="5",
|
||||
image_tail=None, use_camera_control=False,
|
||||
camera_type="simple", camera_horizontal=0.0,
|
||||
camera_vertical=0.0, camera_pan=0.0,
|
||||
camera_tilt=0.0, camera_roll=0.0,
|
||||
camera_zoom=0.0, external_task_id="",
|
||||
callback_url="", seed=-1):
|
||||
"""
|
||||
此方法用于判断节点是否需要重新执行
|
||||
我们使用种子控制重新执行逻辑
|
||||
"""
|
||||
if seed == -1:
|
||||
return random.randint(0, 0xffffffffffffffff)
|
||||
return seed
|
||||
+163
-52
@@ -1,17 +1,29 @@
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
from threading import Thread, Event
|
||||
|
||||
|
||||
# 定义一个带有结果存储的线程类
|
||||
class TaskStatusThread(Thread):
|
||||
def __init__(self, target, args):
|
||||
Thread.__init__(self, target=target, args=args)
|
||||
self.result = None
|
||||
|
||||
def run(self):
|
||||
self.result = self._target(*self._args)
|
||||
|
||||
|
||||
class KLingAIQueryStatus:
|
||||
"""
|
||||
KLingAI Query Task Status Node
|
||||
Queries the status of a text-to-video generation task
|
||||
查询文生视频或图生视频任务状态
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.api_base = "https://api.klingai.com"
|
||||
self.endpoint = "/v1/videos/text2video/{}"
|
||||
self.text2video_endpoint = "/v1/videos/text2video/{}"
|
||||
self.image2video_endpoint = "/v1/videos/image2video/{}"
|
||||
self.stop_thread = Event()
|
||||
self.current_thread = None
|
||||
|
||||
@@ -26,7 +38,24 @@ class KLingAIQueryStatus:
|
||||
"external_task_id": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": False,
|
||||
"placeholder": "Optional: External task ID"
|
||||
"placeholder": "可选: 自定义任务ID"
|
||||
}),
|
||||
"task_type": (["auto", "text2video", "image2video"], {
|
||||
"default": "auto"
|
||||
}),
|
||||
"initial_delay_seconds": ("INT", {
|
||||
"default": 10,
|
||||
"min": 0,
|
||||
"max": 60,
|
||||
"step": 1,
|
||||
"display": "slider"
|
||||
}),
|
||||
"poll_interval_seconds": ("INT", {
|
||||
"default": 10,
|
||||
"min": 5,
|
||||
"max": 30,
|
||||
"step": 1,
|
||||
"display": "slider"
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -36,100 +65,182 @@ class KLingAIQueryStatus:
|
||||
FUNCTION = "query_task_status"
|
||||
CATEGORY = "JM-KLingAI-API"
|
||||
|
||||
def poll_status(self, api_token, task_id, external_task_id):
|
||||
def poll_status(self, api_token, task_id, external_task_id, task_type="auto", initial_delay_seconds=10, poll_interval_seconds=10):
|
||||
"""
|
||||
Polls the task status every 10 seconds until completion
|
||||
轮询任务状态直到完成
|
||||
"""
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_token}"
|
||||
}
|
||||
|
||||
# 初始等待时间,给服务器处理任务的时间
|
||||
if initial_delay_seconds > 0:
|
||||
print(f"等待 {initial_delay_seconds} 秒后开始查询任务状态...")
|
||||
if self.stop_thread.wait(initial_delay_seconds):
|
||||
return None
|
||||
|
||||
print("开始查询任务状态...")
|
||||
|
||||
# 确定使用哪个查询ID
|
||||
query_id = task_id if task_id else external_task_id
|
||||
|
||||
# 根据任务类型确定查询端点
|
||||
endpoints = []
|
||||
if task_type == "auto":
|
||||
# 根据任务ID长度初步判断可能是哪种类型
|
||||
if len(query_id) > 10: # 可灵AI的任务ID通常很长
|
||||
endpoints = [self.image2video_endpoint, self.text2video_endpoint]
|
||||
else:
|
||||
endpoints = [self.text2video_endpoint, self.image2video_endpoint]
|
||||
elif task_type == "text2video":
|
||||
endpoints = [self.text2video_endpoint]
|
||||
elif task_type == "image2video":
|
||||
endpoints = [self.image2video_endpoint]
|
||||
|
||||
# 记录最后找到的有效端点
|
||||
valid_endpoint = None
|
||||
|
||||
while not self.stop_thread.is_set():
|
||||
try:
|
||||
# Determine which ID to use
|
||||
query_id = task_id if task_id else external_task_id
|
||||
url = f"{self.api_base}{self.endpoint.format(query_id)}"
|
||||
success = False
|
||||
|
||||
# 如果上一次查询找到了有效端点,只使用该端点
|
||||
if valid_endpoint:
|
||||
current_endpoints = [valid_endpoint]
|
||||
else:
|
||||
current_endpoints = endpoints.copy()
|
||||
|
||||
for endpoint in current_endpoints:
|
||||
try:
|
||||
url = f"{self.api_base}{endpoint.format(query_id)}"
|
||||
print(f"查询任务 {query_id} 的状态,使用端点: {endpoint}...")
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
# 首先检查响应状态码
|
||||
if response.status_code == 404:
|
||||
print(f"在 {endpoint} 未找到任务,尝试其他端点...")
|
||||
continue
|
||||
|
||||
# 解析响应数据
|
||||
try:
|
||||
response_data = response.json()
|
||||
except Exception as e:
|
||||
print(f"解析响应数据失败: {str(e)},尝试其他端点...")
|
||||
continue
|
||||
|
||||
# 检查响应中的错误信息
|
||||
if response.status_code != 200:
|
||||
error_message = response_data.get('message', '未知错误')
|
||||
print(f"查询状态错误: {error_message}")
|
||||
|
||||
# 任务未找到,这可能是端点错误
|
||||
if "not found" in error_message.lower():
|
||||
print(f"在 {endpoint} 未找到任务,尝试其他端点...")
|
||||
continue
|
||||
# 其他API错误情况
|
||||
else:
|
||||
print(f"API返回错误: {error_message}")
|
||||
continue
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
response_data = response.json()
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Error in status query: {response_data.get('message', 'Unknown error')}")
|
||||
# 获取任务状态
|
||||
data = response_data.get("data", {})
|
||||
status = data.get("task_status")
|
||||
|
||||
# 如果能获取到状态,说明端点正确
|
||||
if status:
|
||||
valid_endpoint = endpoint
|
||||
success = True
|
||||
print(f"当前任务状态: {status}")
|
||||
|
||||
# 任务成功完成
|
||||
if status == "succeed":
|
||||
videos = data.get("task_result", {}).get("videos", [])
|
||||
if videos and videos[0].get("url"):
|
||||
return videos[0].get("url")
|
||||
print("任务成功但未返回视频URL")
|
||||
return None
|
||||
# 任务失败
|
||||
elif status == "failed":
|
||||
print(f"任务失败: {data.get('task_status_msg', '未知错误')}")
|
||||
return None
|
||||
# 任务仍在处理中,中断当前端点循环,等待下次查询
|
||||
break
|
||||
else:
|
||||
print(f"API返回数据中缺少任务状态")
|
||||
|
||||
except Exception as e:
|
||||
print(f"查询端点 {endpoint} 出错: {str(e)}")
|
||||
|
||||
# 如果尝试了所有端点但都未成功
|
||||
if not success and not valid_endpoint:
|
||||
if not current_endpoints:
|
||||
print("没有可用的端点进行查询")
|
||||
return None
|
||||
|
||||
data = response_data.get("data", {})
|
||||
status = data.get("task_status")
|
||||
|
||||
print(f"Current task status: {status}")
|
||||
|
||||
if status == "succeed":
|
||||
videos = data.get("task_result", {}).get("videos", [])
|
||||
if videos:
|
||||
return videos[0].get("url")
|
||||
return None
|
||||
elif status == "failed":
|
||||
print(f"Task failed: {data.get('task_status_msg', 'Unknown error')}")
|
||||
return None
|
||||
|
||||
# Wait 10 seconds before next poll
|
||||
self.stop_thread.wait(10)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during status polling: {str(e)}")
|
||||
print("所有端点查询失败,稍后将重试...")
|
||||
|
||||
# 按照配置的时间间隔等待再次查询
|
||||
print(f"等待 {poll_interval_seconds} 秒后再次查询...")
|
||||
if self.stop_thread.wait(poll_interval_seconds):
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def query_task_status(self, api_token, task_id, external_task_id=""):
|
||||
def query_task_status(self, api_token, task_id, external_task_id="", task_type="auto", initial_delay_seconds=10, poll_interval_seconds=10):
|
||||
"""
|
||||
Start polling task status
|
||||
开始轮询任务状态
|
||||
"""
|
||||
try:
|
||||
# Validate inputs
|
||||
# 验证输入
|
||||
if not api_token:
|
||||
raise ValueError("API token is required")
|
||||
raise ValueError("API令牌不能为空")
|
||||
if not task_id and not external_task_id:
|
||||
raise ValueError("Either task_id or external_task_id must be provided")
|
||||
raise ValueError("必须提供task_id或external_task_id")
|
||||
|
||||
# Stop any existing polling thread
|
||||
# 显示查询配置信息
|
||||
print(f"任务ID: {task_id or external_task_id}")
|
||||
print(f"任务类型: {task_type}")
|
||||
print(f"初始等待时间: {initial_delay_seconds}秒")
|
||||
print(f"查询间隔: {poll_interval_seconds}秒")
|
||||
|
||||
# 停止任何正在进行的轮询线程
|
||||
if self.current_thread and self.current_thread.is_alive():
|
||||
self.stop_thread.set()
|
||||
self.current_thread.join()
|
||||
|
||||
# Reset stop event
|
||||
# 重置停止事件
|
||||
self.stop_thread.clear()
|
||||
|
||||
# Start new polling thread
|
||||
self.current_thread = Thread(
|
||||
# 创建并启动新的轮询线程
|
||||
self.current_thread = TaskStatusThread(
|
||||
target=self.poll_status,
|
||||
args=(api_token, task_id, external_task_id)
|
||||
args=(api_token, task_id, external_task_id, task_type, initial_delay_seconds, poll_interval_seconds)
|
||||
)
|
||||
self.current_thread.start()
|
||||
|
||||
# Wait for result
|
||||
# 等待结果
|
||||
self.current_thread.join()
|
||||
|
||||
# Get result
|
||||
video_url = self.poll_status(api_token, task_id, external_task_id)
|
||||
# 获取结果
|
||||
video_url = self.current_thread.result
|
||||
|
||||
if video_url:
|
||||
print(f"Task completed successfully. Video URL: {video_url}")
|
||||
print(f"任务成功完成。视频URL: {video_url}")
|
||||
return (video_url,)
|
||||
else:
|
||||
print("Task completed but no video URL available")
|
||||
print("任务完成但没有可用的视频URL")
|
||||
return (None,)
|
||||
|
||||
except ValueError as ve:
|
||||
print(f"Validation Error: {str(ve)}")
|
||||
print(f"参数验证错误: {str(ve)}")
|
||||
return (None,)
|
||||
except Exception as e:
|
||||
print(f"Error querying task status: {str(e)}")
|
||||
print(f"查询任务状态错误: {str(e)}")
|
||||
return (None,)
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
Cleanup when node is destroyed
|
||||
节点销毁时清理资源
|
||||
"""
|
||||
if self.current_thread and self.current_thread.is_alive():
|
||||
self.stop_thread.set()
|
||||
|
||||
Reference in New Issue
Block a user