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