项目文件夹

文件
wehub-resource-sync 7d6672c63e
Deploy Documentation / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:19:20 +08:00

334 行
11 KiB
Python

"""
统一视频生成客户端
根据 model 名称自动路由到对应后端:
- wan* → DashscopeVideoClient (DashScope VideoSynthesis)
- kling* → KlingVideoClient (可灵 AI)
"""
import os
import logging
from typing import Optional
from .config import Config
try:
from .video_dashscope import DashscopeVideoClient
from .video_kling import KlingVideoClient
from .video_seedance import SeedanceVideoClient
except ImportError:
from video_dashscope import DashscopeVideoClient
from video_kling import KlingVideoClient
from video_seedance import SeedanceVideoClient
logger = logging.getLogger(__name__)
class VideoClient:
"""
统一视频生成客户端
参照 ImageClient 模式,按模型名路由到不同后端
"""
def __init__(
self,
dashscope_api_key: Optional[str] = None,
dashscope_base_url: Optional[str] = None,
dashscope_local_proxy: Optional[str] = None,
kling_access_key: Optional[str] = None,
kling_secret_key: Optional[str] = None,
kling_base_url: Optional[str] = None,
kling_local_proxy: Optional[str] = None,
ark_api_key: Optional[str] = None,
ark_base_url: Optional[str] = None,
ark_local_proxy: Optional[str] = None,
):
self._dashscope_api_key = dashscope_api_key or Config.DASHSCOPE_API_KEY
self._dashscope_base_url = dashscope_base_url or Config.DASHSCOPE_BASE_URL
self._dashscope_local_proxy = dashscope_local_proxy
self._kling_access_key = kling_access_key or Config.KLING_ACCESS_KEY
self._kling_secret_key = kling_secret_key or Config.KLING_SECRET_KEY
self._kling_base_url = kling_base_url or Config.KLING_BASE_URL
self._kling_local_proxy = kling_local_proxy
self._ark_api_key = ark_api_key or Config.ARK_API_KEY or os.getenv("ARK_API_KEY")
self._ark_base_url = ark_base_url or Config.ARK_BASE_URL or os.getenv("ARK_BASE_URL")
self._ark_local_proxy = ark_local_proxy
self._dashscope_client = None
self._kling_client = None
self._seedance_client = None
@property
def Dashscope_client(self):
"""Create DashScope client only when a Wan/HappyHorse model is selected."""
if self._dashscope_client is None:
self._dashscope_client = DashscopeVideoClient(
api_key=self._dashscope_api_key,
base_url=self._dashscope_base_url,
local_proxy=self._dashscope_local_proxy,
)
return self._dashscope_client
@property
def kling_client(self):
"""Create Kling client only when a Kling model is selected."""
if self._kling_client is None:
self._kling_client = KlingVideoClient(
access_key=self._kling_access_key,
secret_key=self._kling_secret_key,
base_url=self._kling_base_url,
local_proxy=self._kling_local_proxy,
)
return self._kling_client
@property
def seedance_client(self):
"""Create Seedance client only when a Seedance/ARK model is selected."""
if self._seedance_client is None:
self._seedance_client = SeedanceVideoClient(
api_key=self._ark_api_key,
base_url=self._ark_base_url,
local_proxy=self._ark_local_proxy,
)
return self._seedance_client
def generate_video(
self,
prompt: str,
image_path: Optional[str],
save_path: str,
model: str = "wan2.7-i2v",
duration: int = 5,
shot_type: str = "multi",
sound: str = "",
video_ratio: str = "16:9",
resolution: Optional[str] = None,
last_image_path: Optional[str] = None,
first_clip_path: Optional[str] = None,
reference_image_path: Optional[str] = None,
reference_image_paths: Optional[list[str]] = None,
reference_video_paths: Optional[list[str]] = None,
reference_audio_path: Optional[str] = None,
audio_path: Optional[str] = None,
negative_prompt: Optional[str] = None,
prompt_extend: Optional[bool] = None,
watermark: Optional[bool] = None,
seed: Optional[int] = None,
mode: str = "pro",
cfg_scale: float = 0.5,
generate_audio: Optional[bool] = None,
audio: Optional[bool] = None,
) -> str:
"""
生成视频
Args:
prompt: 视频描述提示词
image_path: 输入图片本地路径;DashScope wan2.7 视频续写可为空并使用 first_clip_path
save_path: 输出视频保存路径
model: 模型名,决定使用哪个后端
duration: 视频时长(秒)
shot_type: 镜头类型 "single" / "multi"
Returns:
video_url: 远端视频 URL
Raises:
FileNotFoundError: 输入图片不存在
RuntimeError: 生成或下载失败
"""
if not model:
model = "wan2.7-i2v"
if Config.PRINT_MODEL_INPUT:
print("---- VIDEO GENERATION REQUEST ----")
print(f"Prompt: {prompt}")
if image_path and str(image_path).startswith("data:"):
print(f"Image: [Base64图片]")
else:
print(f"Image: {image_path}")
print(f"Model: {model}")
print(f"Duration: {duration}s")
print(f"Shot Type: {shot_type}")
print(f"Video Ratio: {video_ratio}")
if resolution:
print(f"Resolution: {resolution}")
if last_image_path:
print(f"Last Image: {last_image_path}")
if first_clip_path:
print(f"First Clip: {first_clip_path}")
if reference_image_path:
print(f"Reference Image: {reference_image_path}")
if reference_image_paths:
print(f"Reference Images: {reference_image_paths}")
if reference_video_paths:
print(f"Reference Videos: {reference_video_paths}")
if reference_audio_path:
print(f"Reference Audio: {reference_audio_path}")
if audio_path:
print(f"Audio: {audio_path}")
if negative_prompt:
print(f"Negative Prompt: {negative_prompt}")
if sound:
print(f"Sound: {sound}")
print(f"Save: {save_path}")
print("-" * 30)
model_lower = model.lower()
if "kling" in model_lower:
return self._generate_kling(
prompt,
image_path,
save_path,
model,
duration,
sound,
mode,
cfg_scale,
negative_prompt or "",
video_ratio,
)
elif "seedance" in model_lower:
return self._generate_seedance(
prompt,
image_path,
save_path,
model,
duration,
video_ratio,
resolution,
seed,
watermark,
generate_audio,
)
elif "wan" in model_lower or "happyhorse" in model_lower:
return self._generate_wan(
prompt,
image_path,
save_path,
model,
duration,
shot_type,
video_ratio,
last_image_path,
first_clip_path,
reference_image_path,
reference_image_paths,
reference_video_paths,
reference_audio_path,
audio_path,
negative_prompt,
resolution,
prompt_extend,
watermark if watermark is not None else False,
seed,
audio,
)
else:
raise ValueError(f"未知的视频生成模型: {model}")
def _generate_wan(
self,
prompt: str,
image_path: Optional[str],
save_path: str,
model: str,
duration: int,
shot_type: str,
video_ratio: str,
last_image_path: Optional[str],
first_clip_path: Optional[str],
reference_image_path: Optional[str],
reference_image_paths: Optional[list[str]],
reference_video_paths: Optional[list[str]],
reference_audio_path: Optional[str],
audio_path: Optional[str],
negative_prompt: Optional[str],
resolution: Optional[str],
prompt_extend: Optional[bool],
watermark: bool,
seed: Optional[int],
audio: Optional[bool],
) -> str:
"""通过万象模型生成视频"""
logger.info(f"VideoClient: 路由至万象 model={model}")
return self.Dashscope_client.generate_video(
prompt=prompt,
image_path=image_path,
save_path=save_path,
model=model,
duration=duration,
shot_type=shot_type,
video_ratio=video_ratio,
last_image_path=last_image_path,
first_clip_path=first_clip_path,
reference_image_path=reference_image_path,
reference_image_paths=reference_image_paths,
reference_video_paths=reference_video_paths,
reference_audio_path=reference_audio_path,
audio_path=audio_path,
negative_prompt=negative_prompt,
resolution=resolution,
prompt_extend=prompt_extend,
watermark=watermark,
seed=seed,
audio=audio,
)
def _generate_kling(
self,
prompt: str,
image_path: Optional[str],
save_path: str,
model: str,
duration: int = 5,
sound: str = "",
mode: str = "pro",
cfg_scale: float = 0.5,
negative_prompt: str = "",
video_ratio: str = "16:9",
) -> str:
"""通过可灵模型生成视频"""
logger.info(f"VideoClient: 路由至可灵 model={model}")
return self.kling_client.generate_video(
prompt=prompt,
image_path=image_path,
save_path=save_path,
model=model,
duration=duration,
sound=sound,
mode=mode,
cfg_scale=cfg_scale,
negative_prompt=negative_prompt,
aspect_ratio=video_ratio,
)
def _generate_seedance(
self,
prompt: str,
image_path: Optional[str],
save_path: str,
model: str,
duration: int = 5,
video_ratio: str = "16:9",
resolution: Optional[str] = None,
seed: Optional[int] = None,
watermark: Optional[bool] = None,
generate_audio: Optional[bool] = None,
) -> str:
"""通过 Seedance 模型生成视频"""
logger.info(f"VideoClient: 路由至 Seedance model={model}")
return self.seedance_client.generate_video(
prompt=prompt,
image_path=image_path,
save_path=save_path,
model=model,
duration=duration,
ratio=video_ratio,
resolution=resolution or "720p",
seed=seed,
watermark=watermark,
generate_audio=generate_audio,
)