Skip to content

Commit 024655b

Browse files
committed
feat: kling lip sync
1 parent cd6d882 commit 024655b

File tree

2 files changed

+380
-0
lines changed

2 files changed

+380
-0
lines changed
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
---
2+
title: 可灵 Kling 视频口型同步案例
3+
gitChangelog: false
4+
updatedAt: 2025-05-11
5+
---
6+
7+
# 可灵 Kling 视频口型同步
8+
9+
这是一个视频口型同步的示例,使用 kling 口型同步视频。
10+
11+
> [!TIP]
12+
> 视频口型同步是计算密集型任务,特别是高质量、高分辨率视频可能需要数十秒甚至数分钟处理时间,为了让用户发送请求后可以立即收到响应(任务ID),而不必等待整个生成过程,因此用户可以同时提交多个生成任务,然后异步查询结果。
13+
> 同时这样的队列系统允许服务提供商根据可用GPU/TPU资源智能调度任务。
14+
15+
通常来说,视频口型同步的常见流程是:
16+
17+
1. `POST`: 调用 `视频口型同步api` 提交视频口型同步任务,返回获取 `task_id`
18+
2. `GET`: 根据 `task_id` 调用 `查询视频api` 查看视频口型同步任务是否完成。
19+
20+
本示例实现了每隔一秒轮询任务状态,直到任务完成,然后返回视频 url 和 id。
21+
22+
## 代码示例
23+
24+
> 深色背景为可以修改的参数,非必选参数已经注释,可以按照自己的需求启用。
25+
26+
27+
<<< @/zh/snippets/kling-lip-sync.py{247-248,255-262,270-272,277-278,283-285}
28+
29+
30+
## 返回结果
31+
32+
返回结果为视频的 url 和 id,视频的有效期一般为 30 天,推荐尽快下载或者转存。
33+
34+
```
35+
36+
```
37+
38+
## 流程图
39+
40+
```mermaid
41+
flowchart TD
42+
A[开始] --> B[初始化 KlingLipSync 实例]
43+
44+
%% 文本转视频口型同步路径
45+
B --> C1[调用 generate_text2video_lip_sync]
46+
C1 --> D1[准备输入参数]
47+
D1 --> E1{检查视频来源}
48+
49+
E1 -->|URL格式| F1[设置为video_url]
50+
E1 -->|任务ID格式| G1[设置为task_id和video_id]
51+
E1 -->|使用task_id和video_id| H1[直接使用提供的参数]
52+
53+
F1 --> I1[设置文本和语音参数]
54+
G1 --> I1
55+
H1 --> I1
56+
57+
%% 音频转视频口型同步路径
58+
B --> C2[调用 generate_audio2video_lip_sync]
59+
C2 --> D2[准备输入参数]
60+
D2 --> E2{检查视频来源}
61+
62+
E2 -->|URL格式| F2[设置为video_url]
63+
E2 -->|任务ID格式| G2[设置为task_id和video_id]
64+
E2 -->|使用task_id和video_id| H2[直接使用提供的参数]
65+
66+
F2 --> I2{检查音频来源}
67+
G2 --> I2
68+
H2 --> I2
69+
70+
I2 -->|URL格式| J2[设置为audio_url]
71+
I2 -->|本地文件| K2[转换为base64并设置]
72+
73+
%% 共同的API调用和结果处理部分
74+
I1 --> L[调用_kling_lip_sync提交任务]
75+
J2 --> L
76+
K2 --> L
77+
78+
L --> M[获取task_id]
79+
M --> N[开始轮询任务结果]
80+
81+
N --> O[调用_query_lip_sync_result]
82+
O --> P{任务是否完成?}
83+
84+
P -->|否| Q{是否超时?}
85+
Q -->|否| R[等待2秒]
86+
R --> O
87+
Q -->|是| S[返回超时信息]
88+
89+
P -->|是| T[获取video_url和video_id]
90+
T --> U[返回结果]
91+
92+
S --> V[结束]
93+
U --> V
94+
```

docs/zh/snippets/kling-lip-sync.py

+286
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
import http.client
2+
import json
3+
import time
4+
import base64
5+
6+
class KlingLipSync:
7+
def __init__(self, api_token, api_url):
8+
"""初始化 Kling 口型同步生成器
9+
10+
参数:
11+
api_token: API 密钥
12+
api_url: API 节点地址
13+
"""
14+
self.api_url = api_url
15+
self.api_token = api_token
16+
# 初始化 HTTP 连接
17+
self.conn = http.client.HTTPSConnection(self.api_url)
18+
self.endpoint = "/kling/v1/videos/lip-sync"
19+
# 设置请求头
20+
self.headers = {
21+
'Authorization': f'Bearer {self.api_token}',
22+
'Content-Type': 'application/json'
23+
}
24+
25+
@staticmethod
26+
def get_audio_base64(audio_path):
27+
"""将音频转换为 base64 编码形式
28+
29+
参数:
30+
audio_path: 音频文件路径
31+
返回:
32+
base64 编码后的音频字符串
33+
"""
34+
with open(audio_path, "rb") as audio_file:
35+
return base64.b64encode(audio_file.read()).decode("utf-8")
36+
37+
def _kling_lip_sync(self, input_data):
38+
"""提交口型同步任务
39+
40+
参数:
41+
input_data: dict, 包含所有口型同步所需的输入参数
42+
返回:
43+
task_id: 生成任务的 id
44+
"""
45+
# 构建请求体
46+
payload = json.dumps({
47+
"input": input_data
48+
})
49+
50+
# 发送 POST 请求,提交口型同步任务
51+
self.conn.request("POST", self.endpoint, payload, self.headers)
52+
# 获取响应
53+
res = self.conn.getresponse()
54+
# 读取响应内容并解析为 JSON
55+
json_data = json.loads(res.read().decode("utf-8"))
56+
57+
if 'code' in json_data and json_data['code'] == 0:
58+
# 成功则返回提交的任务 id
59+
return json_data['data']['task_id']
60+
else:
61+
# 失败则返回错误信息
62+
raise Exception(f"API调用失败:{json_data['message']}")
63+
64+
def _query_lip_sync_result(self, task_id):
65+
"""使用查询接口获取口型同步结果
66+
67+
参数:
68+
task_id: 生成任务的 id
69+
返回:
70+
video_url: 结果视频 url,任务未完成时返回 None
71+
video_id: 结果视频 id,任务未完成时返回 None
72+
"""
73+
# 构建查询路径
74+
query_path = f"{self.endpoint}/{task_id}"
75+
76+
# 发送 GET 请求,查询任务状态
77+
self.conn.request("GET", query_path, None, self.headers)
78+
# 获取响应
79+
res = self.conn.getresponse()
80+
# 读取响应内容并解析为 JSON
81+
json_data = json.loads(res.read().decode("utf-8"))
82+
83+
# 检查响应是否成功
84+
if json_data['code'] == 0:
85+
# 如果任务状态为成功,则返回视频 url 和 id
86+
if json_data['data']['task_status'] == "succeed":
87+
video_url = json_data['data']['task_result']['videos'][0]['url']
88+
video_id = json_data['data']['task_result']['videos'][0]['id']
89+
return video_url, video_id
90+
else:
91+
return None, None
92+
else:
93+
# 如果查询失败,抛出异常
94+
raise Exception(f"查询失败: {json_data['message']}")
95+
96+
def generate_text2video_lip_sync(self, video_source=None, video_id=None, task_id=None,
97+
text="", voice_id="", voice_language="zh", voice_speed=1.0,
98+
callback_url="", timeout=300):
99+
"""文本转口型同步视频
100+
101+
参数:
102+
video_source: str, 视频来源,可以是URL或任务ID (与task_id+video_id二选一)
103+
video_id: str, 现有视频的视频ID (与task_id一起使用)
104+
task_id: str, 现有视频的任务ID (与video_id一起使用)
105+
text: str, 要同步的文本内容
106+
voice_id: str, 音色ID
107+
voice_language: str, 音色语种,默认"zh"
108+
voice_speed: float, 语速,默认1.0
109+
callback_url: str, 回调地址
110+
timeout: int, 超时时间(秒)
111+
返回:
112+
video_url: 结果视频URL
113+
video_id: 结果视频ID
114+
"""
115+
# 准备输入参数
116+
input_data = {
117+
"mode": "text2video",
118+
"text": text,
119+
"voice_id": voice_id,
120+
"voice_language": voice_language,
121+
"voice_speed": voice_speed
122+
}
123+
124+
# 设置视频来源 - 自动识别和处理
125+
if video_source:
126+
if video_source.startswith(('http://', 'https://', 'ftp://')):
127+
# 如果是URL格式,作为视频URL处理
128+
input_data["video_url"] = video_source
129+
else:
130+
# 否则作为任务ID处理
131+
if not video_id:
132+
raise ValueError("当提供任务ID时,必须同时提供视频ID")
133+
input_data["task_id"] = video_source
134+
input_data["video_id"] = video_id
135+
elif task_id and video_id:
136+
# 如果单独提供task_id和video_id,使用它们
137+
input_data["task_id"] = task_id
138+
input_data["video_id"] = video_id
139+
else:
140+
raise ValueError("必须提供视频来源(URL或任务ID)和视频ID")
141+
142+
# 如果提供了回调地址,添加到请求中
143+
if callback_url:
144+
input_data["callback_url"] = callback_url
145+
146+
# 调用 API 提交任务
147+
task_id = self._kling_lip_sync(input_data)
148+
149+
start_time = time.time()
150+
151+
# 轮询等待生成完成
152+
while True:
153+
# 查询任务状态
154+
video_url, video_id = self._query_lip_sync_result(task_id)
155+
# 如果任务完成,返回结果
156+
if video_url is not None:
157+
return video_url, video_id
158+
# 如果超时,返回 None
159+
if time.time() - start_time > timeout:
160+
print(f"请求达到 {timeout} 秒超时")
161+
return None, None
162+
# 轮询间隔 2 秒
163+
time.sleep(2)
164+
print(f"等待口型同步结果生成,{int(time.time() - start_time)} 秒", flush=True)
165+
166+
def generate_audio2video_lip_sync(self, video_source=None, video_id=None, task_id=None,
167+
audio_source=None, callback_url="", timeout=300):
168+
"""音频转口型同步视频
169+
170+
参数:
171+
video_source: str, 视频来源,可以是URL或任务ID (与task_id+video_id二选一)
172+
video_id: str, 现有视频的视频ID (与task_id一起使用)
173+
task_id: str, 现有视频的任务ID (与video_id一起使用)
174+
audio_source: str, 音频来源,可以是URL或本地文件路径
175+
callback_url: str, 回调地址
176+
timeout: int, 超时时间(秒)
177+
返回:
178+
video_url: 结果视频URL
179+
video_id: 结果视频ID
180+
"""
181+
# 准备输入参数
182+
input_data = {
183+
"mode": "audio2video"
184+
}
185+
186+
# 设置视频来源 - 自动识别和处理
187+
if video_source:
188+
if video_source.startswith(('http://', 'https://', 'ftp://')):
189+
# 如果是URL格式,作为视频URL处理
190+
input_data["video_url"] = video_source
191+
else:
192+
# 否则作为任务ID处理
193+
if not video_id:
194+
raise ValueError("当提供任务ID时,必须同时提供视频ID")
195+
input_data["task_id"] = video_source
196+
input_data["video_id"] = video_id
197+
elif task_id and video_id:
198+
# 如果单独提供task_id和video_id,使用它们
199+
input_data["task_id"] = task_id
200+
input_data["video_id"] = video_id
201+
else:
202+
raise ValueError("必须提供视频来源(URL或任务ID)和视频ID")
203+
204+
# 设置音频来源 - 自动识别和处理
205+
if not audio_source:
206+
raise ValueError("必须提供音频来源(URL或本地文件路径)")
207+
208+
if audio_source.startswith(('http://', 'https://', 'ftp://')):
209+
# 如果是URL格式,作为音频URL处理
210+
input_data["audio_type"] = "url"
211+
input_data["audio_url"] = audio_source
212+
else:
213+
# 否则作为本地文件路径处理
214+
try:
215+
input_data["audio_type"] = "file"
216+
input_data["audio_file"] = self.get_audio_base64(audio_source)
217+
except Exception as e:
218+
raise ValueError(f"无法读取音频文件: {str(e)}")
219+
220+
# 如果提供了回调地址,添加到请求中
221+
if callback_url:
222+
input_data["callback_url"] = callback_url
223+
224+
# 调用 API 提交任务
225+
task_id = self._kling_lip_sync(input_data)
226+
227+
start_time = time.time()
228+
229+
# 轮询等待生成完成
230+
while True:
231+
# 查询任务状态
232+
video_url, video_id = self._query_lip_sync_result(task_id)
233+
# 如果任务完成,返回结果
234+
if video_url is not None:
235+
return video_url, video_id
236+
# 如果超时,返回 None
237+
if time.time() - start_time > timeout:
238+
print(f"请求达到 {timeout} 秒超时")
239+
return None, None
240+
# 轮询间隔 2 秒
241+
time.sleep(2)
242+
print(f"等待口型同步结果生成,{int(time.time() - start_time)} 秒", flush=True)
243+
244+
245+
# 使用示例
246+
if __name__ == "__main__":
247+
API_URL = "www.dmxapi.cn" # API 节点地址
248+
DMX_API_TOKEN = "sk-XXXXXXXXXXXXXX" # API 密钥
249+
250+
# 创建口型同步生成器实例
251+
kling_lip_sync = KlingLipSync(api_token=DMX_API_TOKEN, api_url=API_URL)
252+
253+
# 示例1:文本转口型同步视频 - 使用任务ID和视频ID
254+
video_url, video_id = kling_lip_sync.generate_text2video_lip_sync(
255+
video_source="Cl6kH2gHPegAAAAABJAWDA", # [必选] 视频来源(任务ID)
256+
video_id="aeba40f7-473a-47a3-ab85-02dba121970c", # [必选] 视频ID
257+
text="欢迎大家使用 DMXAPI", # [必选] 文本内容
258+
voice_id="girlfriend_1_speech02", # [必选] 音色ID
259+
# voice_language="zh", # 音色语种,默认"zh"
260+
# voice_speed=1.0, # 语速,默认1.0
261+
# callback_url="", # 回调地址
262+
# timeout=300 # 超时时间(秒)
263+
)
264+
265+
print("文本生成的口型同步视频URL:", video_url)
266+
print("文本生成的口型同步视频ID:", video_id)
267+
268+
# 示例2:文本转口型同步视频 - 使用视频URL
269+
# video_url, video_id = kling_lip_sync.generate_text2video_lip_sync(
270+
# video_source="https://dmxapi.cn/video.mp4", # [必选] 视频来源(URL)
271+
# text="欢迎大家使用 DMXAPI", # [必选] 文本内容
272+
# voice_id="girlfriend_1_speech02", # [必选] 音色ID
273+
# )
274+
275+
# 示例3:音频转口型同步视频 - 使用URL
276+
# video_url, video_id = kling_lip_sync.generate_audio2video_lip_sync(
277+
# video_source="https://dmxapi.cn/video.mp4", # [必选] 视频来源(URL)
278+
# audio_source="https://example.com/audio.mp3", # [必选] 音频来源(URL)
279+
# )
280+
281+
# 示例4:音频转口型同步视频 - 使用本地文件
282+
# video_url, video_id = kling_lip_sync.generate_audio2video_lip_sync(
283+
# task_id="Cl6kH2gHPegAAAAABJAWDA", # [必选] 任务ID
284+
# video_id="aeba40f7-473a-47a3-ab85-02dba121970c", # [必选] 视频ID
285+
# audio_source="/path/to/local/audio.mp3", # [必选] 音频来源(本地文件)
286+
# )

0 commit comments

Comments
 (0)