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