2025-09-02 06:18:26 +08:00
|
|
|
|
import requests
|
|
|
|
|
import time
|
|
|
|
|
from typing import Dict, Optional
|
|
|
|
|
|
|
|
|
|
from Config import Config
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VideoRetalk:
|
|
|
|
|
"""
|
|
|
|
|
阿里云DashScope LivePortrait视频生成类
|
|
|
|
|
videoretalk是一个人物视频生成模型,可基于人物视频和人声音频,生成人物讲话口型与输入音频相匹配的新视频。
|
|
|
|
|
视频口型替换-声动人像VideoRetalk
|
|
|
|
|
实现图像和音频合成视频功能
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, api_key: str):
|
|
|
|
|
"""
|
|
|
|
|
初始化视频生成类
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
api_key: 阿里云DashScope API密钥
|
|
|
|
|
"""
|
|
|
|
|
self.api_key = api_key
|
|
|
|
|
self.base_url = "https://dashscope.aliyuncs.com/api/v1"
|
|
|
|
|
self.video_synthesis_url = f"{self.base_url}/services/aigc/image2video/video-synthesis"
|
|
|
|
|
|
|
|
|
|
def submit_video_task(self, image_url: str, audio_url: str,
|
|
|
|
|
template_id: str = "normal",
|
|
|
|
|
eye_move_freq: float = 0.5,
|
|
|
|
|
video_fps: int = 30,
|
|
|
|
|
mouth_move_strength: float = 1.0,
|
|
|
|
|
paste_back: bool = True,
|
|
|
|
|
head_move_strength: float = 0.7) -> Dict:
|
|
|
|
|
"""
|
|
|
|
|
提交视频生成任务
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
image_url: 输入图片URL
|
|
|
|
|
audio_url: 输入音频URL
|
|
|
|
|
template_id: 模板ID,默认为"normal"
|
|
|
|
|
eye_move_freq: 眼睛移动频率,默认0.5
|
|
|
|
|
video_fps: 视频帧率,默认30
|
|
|
|
|
mouth_move_strength: 嘴巴移动强度,默认1.0
|
|
|
|
|
paste_back: 是否粘贴背景,默认True
|
|
|
|
|
head_move_strength: 头部移动强度,默认0.7
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 包含task_id和task_status的响应数据
|
|
|
|
|
"""
|
|
|
|
|
headers = {
|
|
|
|
|
'X-DashScope-Async': 'enable',
|
|
|
|
|
'Authorization': f'Bearer {self.api_key}',
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
payload = {
|
|
|
|
|
"model": "liveportrait",
|
|
|
|
|
"input": {
|
|
|
|
|
"image_url": image_url,
|
|
|
|
|
"audio_url": audio_url
|
|
|
|
|
},
|
|
|
|
|
"parameters": {
|
|
|
|
|
"template_id": template_id,
|
|
|
|
|
"eye_move_freq": eye_move_freq,
|
|
|
|
|
"video_fps": video_fps,
|
|
|
|
|
"mouth_move_strength": mouth_move_strength,
|
|
|
|
|
"paste_back": paste_back,
|
|
|
|
|
"head_move_strength": head_move_strength
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
response = requests.post(self.video_synthesis_url,
|
|
|
|
|
headers=headers,
|
|
|
|
|
json=payload)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
return response.json()
|
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
|
raise Exception(f"提交视频任务失败: {e}")
|
|
|
|
|
|
|
|
|
|
def get_task_status(self, task_id: str) -> Dict:
|
|
|
|
|
"""
|
|
|
|
|
查询任务状态
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: 任务ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 任务状态信息
|
|
|
|
|
"""
|
|
|
|
|
headers = {
|
|
|
|
|
'Authorization': f'Bearer {self.api_key}'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
task_url = f"{self.base_url}/tasks/{task_id}"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
response = requests.get(task_url, headers=headers)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
return response.json()
|
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
|
raise Exception(f"查询任务状态失败: {e}")
|
|
|
|
|
|
|
|
|
|
def wait_for_task_completion(self, task_id: str,
|
|
|
|
|
poll_interval: int = 5,
|
|
|
|
|
timeout: int = 300) -> Dict:
|
|
|
|
|
"""
|
|
|
|
|
等待任务完成
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: 任务ID
|
|
|
|
|
poll_interval: 轮询间隔(秒),默认5秒
|
|
|
|
|
timeout: 超时时间(秒),默认300秒
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 任务完成后的结果
|
|
|
|
|
"""
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
|
|
|
|
while time.time() - start_time < timeout:
|
|
|
|
|
task_status = self.get_task_status(task_id)
|
|
|
|
|
|
|
|
|
|
status = task_status.get('output', {}).get('task_status')
|
|
|
|
|
|
|
|
|
|
if status == 'SUCCEEDED':
|
|
|
|
|
return task_status
|
|
|
|
|
elif status == 'FAILED':
|
|
|
|
|
error_code = task_status.get('output', {}).get('code', '未知错误')
|
|
|
|
|
error_message = task_status.get('output', {}).get('message', '无错误信息')
|
|
|
|
|
raise Exception(f"任务执行失败: {error_code} - {error_message}")
|
|
|
|
|
elif status in ['PENDING', 'RUNNING']:
|
|
|
|
|
print(f"任务状态: {status}, 等待中...")
|
|
|
|
|
time.sleep(poll_interval)
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(f"未知的任务状态: {status}")
|
|
|
|
|
|
|
|
|
|
raise Exception(f"任务超时,未在{timeout}秒内完成")
|
|
|
|
|
|
|
|
|
|
def generate_video(self, image_url: str, audio_url: str,
|
|
|
|
|
**kwargs) -> Optional[str]:
|
|
|
|
|
"""
|
|
|
|
|
生成视频的完整流程
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
image_url: 输入图片URL
|
|
|
|
|
audio_url: 输入音频URL
|
|
|
|
|
**kwargs: 其他参数,同submit_video_task
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
str: 生成的视频URL,失败返回None
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# 提交任务
|
|
|
|
|
submit_response = self.submit_video_task(image_url, audio_url, **kwargs)
|
|
|
|
|
task_id = submit_response.get('output', {}).get('task_id')
|
|
|
|
|
|
|
|
|
|
if not task_id:
|
|
|
|
|
print("提交任务失败,未获取到task_id")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
print(f"任务已提交,task_id: {task_id}")
|
|
|
|
|
|
|
|
|
|
# 等待任务完成
|
|
|
|
|
result = self.wait_for_task_completion(task_id)
|
|
|
|
|
|
|
|
|
|
# 获取视频URL
|
|
|
|
|
video_url = result.get('output', {}).get('results', {}).get('video_url')
|
|
|
|
|
|
|
|
|
|
if video_url:
|
|
|
|
|
print(f"视频生成成功: {video_url}")
|
|
|
|
|
# 获取使用情况信息
|
|
|
|
|
usage = result.get('usage', {})
|
2025-09-02 08:48:33 +08:00
|
|
|
|
video_info = {
|
|
|
|
|
'video_url': video_url,
|
|
|
|
|
'video_duration': usage.get('video_duration'),
|
|
|
|
|
'video_ratio': usage.get('video_ratio')
|
|
|
|
|
}
|
2025-09-02 06:18:26 +08:00
|
|
|
|
if usage:
|
2025-09-02 08:48:33 +08:00
|
|
|
|
print(f"视频时长: {video_info['video_duration']}秒")
|
|
|
|
|
print(f"视频比例: {video_info['video_ratio']}")
|
|
|
|
|
return video_info
|
2025-09-02 06:18:26 +08:00
|
|
|
|
else:
|
|
|
|
|
print("未找到生成的视频URL")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"视频生成失败: {e}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 使用示例
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
# 替换为您的实际API密钥
|
|
|
|
|
API_KEY = Config.ALY_LLM_API_KEY
|
|
|
|
|
|
|
|
|
|
# 创建视频生成实例
|
|
|
|
|
video_retalk = VideoRetalk(API_KEY)
|
|
|
|
|
|
|
|
|
|
# 示例:生成视频
|
|
|
|
|
try:
|
|
|
|
|
video_url = video_retalk.generate_video(
|
|
|
|
|
image_url="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/LiBai.jpg",
|
|
|
|
|
audio_url="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/JiangJinJiu.mp3",
|
|
|
|
|
template_id="normal",
|
|
|
|
|
eye_move_freq=0.5,
|
|
|
|
|
video_fps=30,
|
|
|
|
|
mouth_move_strength=1.0,
|
|
|
|
|
paste_back=True,
|
|
|
|
|
head_move_strength=0.7
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if video_url:
|
|
|
|
|
print(f"最终视频URL: {video_url}")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"执行过程中发生错误: {e}")
|