From b9c8d30cbb4485ed7aa6386ed400d8cd2fe98f43 Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Tue, 19 Aug 2025 15:38:05 +0800 Subject: [PATCH] 'commit' --- .../com/dsideal/Ai/Util/JiMeng/JmTxt2Img.py | 89 -------- .../dsideal/Ai/Util/JiMeng/Kit/JmCommon.py | 193 ------------------ dsLightRag/JiMeng/JmImg2Video.py | 129 ++++++++++++ dsLightRag/JiMeng/JmTxt2Video.py | 122 +++++++++++ 4 files changed, 251 insertions(+), 282 deletions(-) delete mode 100644 dsAi/src/main/python/com/dsideal/Ai/Util/JiMeng/JmTxt2Img.py delete mode 100644 dsAi/src/main/python/com/dsideal/Ai/Util/JiMeng/Kit/JmCommon.py create mode 100644 dsLightRag/JiMeng/JmImg2Video.py create mode 100644 dsLightRag/JiMeng/JmTxt2Video.py diff --git a/dsAi/src/main/python/com/dsideal/Ai/Util/JiMeng/JmTxt2Img.py b/dsAi/src/main/python/com/dsideal/Ai/Util/JiMeng/JmTxt2Img.py deleted file mode 100644 index 5018ee71..00000000 --- a/dsAi/src/main/python/com/dsideal/Ai/Util/JiMeng/JmTxt2Img.py +++ /dev/null @@ -1,89 +0,0 @@ -import json -import os -import time -import base64 -from com.dsideal.Ai.Util.JiMeng.Kit.JmCommon import JmCommon -from com.dsideal.Ai.Util.JiMeng.Kit.JmErrorCode import JmErrorCode - - -class JmTxt2Img: - action = "CVProcess" - req_key = "jimeng_high_aes_general_v21_L" - - @staticmethod - def generate_image(prompt, save_img_path): - """生成图片""" - # 创建请求体 - req = { - "req_key": JmTxt2Img.req_key, - "prompt": prompt - } - - response_body = JmCommon.do_request("POST", {}, json.dumps(req).encode('utf-8'), JmTxt2Img.action) - jo = json.loads(response_body) - - # 检查响应状态码 - code = jo.get("code") - if not JmErrorCode.is_success(code): - error_msg = JmErrorCode.get_message_by_code(code) - print(f"生成图片失败: 错误码={code}, 错误信息={error_msg}") - raise Exception(f"生成图片失败: {error_msg}") - - # 获取图片Base64数据 - img_base64 = jo.get("data", {}).get("binary_data_base64", [""])[0] - - # 确保目录存在 - os.makedirs(os.path.dirname(save_img_path), exist_ok=True) - - # 对 Base64 字符串进行解码并保存为文件 - try: - # 注意:有些Base64字符串可能有前缀,需要去除 - if img_base64.startswith('data:image'): - img_base64 = img_base64.split(',')[1] - bytes_data = base64.b64decode(img_base64) - with open(save_img_path, 'wb') as f: - f.write(bytes_data) - print(f"文件保存成功!文件位置: {save_img_path}") - except Exception as e: - print(f"保存图片失败: {str(e)}") - raise Exception(f"保存图片失败: {str(e)}") - - @staticmethod - def main(): - """主函数,用于测试""" - # 示例提示词 - prompt = "鸿门宴,室内场景图。非真人,是一幅画。远景镜头,让人一看就知道是在讲鸿门宴," - prompt += "1、要亮!" - prompt += "2、不要有人出现" - prompt += "3、有大厅、墙面,桌子,不要有台阶,否则项庄没机会刺杀刘邦" - prompt += "4、西周时期特点" - prompt += "5、宏大,雄伟,颜色鲜明,不要灰色调的,不要有雾霾之类的" - prompt += "6、超高清画质" - - # 保存图片路径 - 使用绝对路径避免相对路径问题 - save_image_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "..", "..", "Text2Img.jpg")) - print(f"保存图片路径:{save_image_path}") - - # 添加重试逻辑,处理API并发限制错误 - retry_count = 0 - max_retries = 5 # 减少重试次数以便更快排查问题 - retry_interval = 5000 # 重试间隔(毫秒) - - while True: - try: - JmTxt2Img.generate_image(prompt, save_image_path) - # 成功生成图片,跳出循环 - break - except Exception as e: - print(f"生成图片异常: {str(e)}") - retry_count += 1 - if retry_count < max_retries: - print(f"等待{retry_interval}毫秒后重试...") - time.sleep(retry_interval / 1000) - else: - print(f"已达到最大重试次数: {max_retries}") - raise e # 达到最大重试次数,抛出异常 - - -if __name__ == "__main__": - JmTxt2Img.main() \ No newline at end of file diff --git a/dsAi/src/main/python/com/dsideal/Ai/Util/JiMeng/Kit/JmCommon.py b/dsAi/src/main/python/com/dsideal/Ai/Util/JiMeng/Kit/JmCommon.py deleted file mode 100644 index 4b1288d5..00000000 --- a/dsAi/src/main/python/com/dsideal/Ai/Util/JiMeng/Kit/JmCommon.py +++ /dev/null @@ -1,193 +0,0 @@ -import hashlib -import hmac -import json -import os -import time -from datetime import datetime, timezone -import requests -import base64 - - -class JmCommon: - # 请求域名和相关配置 - host = "visual.volcengineapi.com" - path = "/" - service = "cv" - region = "cn-north-1" - schema = "https" - version = "2022-08-31" - - # API访问凭证 - 请替换为您自己的凭证 - ak = "AKLTZjVlOGU1NzA1YWZkNDExMzkzYzY5YTNlOTRmMTMxODg" - sk = "WkdabU9UTXdNVEJpTmpWbE5HVTJZVGxtTnpWbU5XSTBaRGN5TW1NMk5tRQ==" - - # 项目路径 - project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "..", "..")) - base_path = os.path.join(project_root, "src", "main", "python", "com", "dsideal", "aiSupport", "Util", "JiMeng", "Example") - - @staticmethod - def sign_string_encoder(source): - """URL编码字符串,用于签名""" - if source is None: - return None - - # 不需要编码的字符 - safe_chars = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~") - result = [] - - for char in source: - if char in safe_chars: - result.append(char) - elif char == ' ': - result.append("%20") - else: - # 对其他字符进行URL编码 - result.append(f"%{ord(char):02X}") - - return ''.join(result) - - @staticmethod - def hash_sha256(content): - """计算内容的SHA256哈希值""" - try: - md = hashlib.sha256() - md.update(content) - return md.hexdigest() - except Exception as e: - raise Exception(f"计算哈希值失败: {str(e)}") - - @staticmethod - def hmac_sha256(key, content): - """使用HMAC-SHA256算法计算消息认证码""" - try: - h = hmac.new(key, content.encode('utf-8'), hashlib.sha256) - return h.digest() - except Exception as e: - raise Exception(f"计算HMAC-SHA256失败: {str(e)}") - - @staticmethod - def gen_signing_secret_key_v4(date, region, service): - """生成V4版本的签名密钥""" - try: - # 解码SK (Base64) - sk_bytes = base64.b64decode(JmCommon.sk) - k_date = JmCommon.hmac_sha256(sk_bytes, date) - k_region = JmCommon.hmac_sha256(k_date, region) - k_service = JmCommon.hmac_sha256(k_region, service) - k_signing = JmCommon.hmac_sha256(k_service, "request") - return k_signing - except Exception as e: - raise Exception(f"生成签名密钥失败: {str(e)}") - - @staticmethod - def do_request(method, query_list, body, action): - """发送HTTP请求到火山引擎API""" - date = datetime.now(timezone.utc) - if body is None: - body = b'' - - # 计算请求体的SHA256哈希值 - x_content_sha256 = JmCommon.hash_sha256(body) - - # 格式化日期时间,用于请求头 - x_date = date.strftime("%Y%m%dT%H%M%SZ") - short_x_date = x_date[:8] # 提取日期部分,用于凭证范围 - - content_type = "application/json" - sign_header = "host;x-date;x-content-sha256;content-type" - - # 构建查询参数字符串 - real_query_list = query_list.copy() if query_list else {} - real_query_list["Action"] = action - real_query_list["Version"] = JmCommon.version - - # 排序查询参数 - sorted_query = sorted(real_query_list.items()) - query_sb = [] - for key, value in sorted_query: - encoded_key = JmCommon.sign_string_encoder(key) - encoded_value = JmCommon.sign_string_encoder(str(value)) - query_sb.append(f"{encoded_key}={encoded_value}") - query_string = "&" .join(query_sb) - - # 构建规范请求字符串,用于签名 - canonical_string = f"{method}\n{JmCommon.path}\n{query_string}\n" - canonical_string += f"host:{JmCommon.host}\n" - canonical_string += f"x-date:{x_date}\n" - canonical_string += f"x-content-sha256:{x_content_sha256}\n" - canonical_string += f"content-type:{content_type}\n\n" - canonical_string += f"{sign_header}\n{x_content_sha256}" - - # 计算规范请求字符串的哈希值 - hash_canonical_string = JmCommon.hash_sha256(canonical_string.encode('utf-8')) - - # 构建凭证范围和签名字符串 - credential_scope = f"{short_x_date}/{JmCommon.region}/{JmCommon.service}/request" - sign_string = f"HMAC-SHA256\n{x_date}\n{credential_scope}\n{hash_canonical_string}" - - # 生成签名密钥并计算签名 - sign_key = JmCommon.gen_signing_secret_key_v4(short_x_date, JmCommon.region, JmCommon.service) - signature = hmac.new(sign_key, sign_string.encode('utf-8'), hashlib.sha256).hexdigest() - - # 构建URL - url = f"{JmCommon.schema}://{JmCommon.host}{JmCommon.path}?{query_string}" - - # 设置请求头 - headers = { - "Host": JmCommon.host, - "X-Date": x_date, - "X-Content-Sha256": x_content_sha256, - "Content-Type": content_type, - "Authorization": f"HMAC-SHA256 Credential={JmCommon.ak}/{credential_scope}, SignedHeaders={sign_header}, Signature={signature}" - } - - # 发送请求 - try: - print(f"请求URL: {url}") - print(f"请求头: {headers}") - response = requests.request( - method=method, - url=url, - headers=headers, - data=body, - timeout=(30, 30) # 连接超时和读取超时 - ) - print(f"响应状态码: {response.status_code}") - print(f"响应内容: {response.text}") - response.raise_for_status() # 如果状态码不是200,抛出异常 - return response.text - except requests.exceptions.RequestException as e: - raise Exception(f"API请求失败: {str(e)}") - - @staticmethod - def download_file(file_url, save_file_path): - """从URL下载文件到指定路径""" - try: - # 确保目录存在 - os.makedirs(os.path.dirname(save_file_path), exist_ok=True) - - # 下载文件 - response = requests.get(file_url, timeout=30) - response.raise_for_status() - - with open(save_file_path, 'wb') as f: - f.write(response.content) - - file_size = os.path.getsize(save_file_path) - print(f"文件下载成功,保存路径: {save_file_path}, 文件大小: {file_size}字节") - except Exception as e: - print(f"文件下载失败: {str(e)}") - raise Exception(f"文件下载失败: {str(e)}") - - @staticmethod - def query_task_result(task_id): - """查询异步任务结果""" - req_key = "jimeng_vgfm_t2v_l20" - action = "CVSync2AsyncGetResult" - # 创建请求体 - req = { - "task_id": task_id, - "req_key": req_key - } - response_body = JmCommon.do_request("POST", {}, json.dumps(req).encode('utf-8'), action) - return json.loads(response_body) \ No newline at end of file diff --git a/dsLightRag/JiMeng/JmImg2Video.py b/dsLightRag/JiMeng/JmImg2Video.py new file mode 100644 index 00000000..471e7c6d --- /dev/null +++ b/dsLightRag/JiMeng/JmImg2Video.py @@ -0,0 +1,129 @@ +import json +import time +import logging +from JiMeng.Kit.JmCommon import JmCommon +from JiMeng.Kit.JmErrorCode import JmErrorCode + +# 配置日志 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger('JmImg2Video') + +class JmImg2Video(JmCommon): + req_key = "jimeng_vgfm_i2v_l20" + action = "CVSync2AsyncSubmitTask" + + @staticmethod + def submit_image_to_video_task(image_urls, prompt): + """ + 提交图生视频任务(使用图片URL) + + :param image_urls: 图片URL列表 + :param prompt: 提示词 + :return: 任务结果 + :raises Exception: 异常信息 + """ + # 创建请求体 + req = { + "req_key": JmImg2Video.req_key, + "image_urls": image_urls, + "prompt": prompt + } + + response_body = JmCommon.do_request( + method="POST", + query_list={}, # 添加空字典作为query_list参数 + body=json.dumps(req).encode('utf-8'), + action=JmImg2Video.action + ) + return json.loads(response_body) + + +def main(): + try: + # 玩法参考: https://www.volcengine.com/docs/85621/1544774 + image_urls = [ + "https://dsideal.obs.myhuaweicloud.com/HuangHai/%E5%A4%87%E4%BB%BD/%E5%B0%8F%E4%B9%94%E5%A4%B4%E5%83%8F.jpg" + ] + prompt = "" + + mp4_file_name = "image2video.mp4" + + # 添加重试逻辑,处理API并发限制错误 + submit_result = None + submit_retry_count = 0 + max_submit_retries = 1000 # 最大重试次数 + submit_retry_interval = 3000 # 重试间隔(毫秒) + + while submit_retry_count < max_submit_retries: + try: + submit_result = JmImg2Video.submit_image_to_video_task(image_urls, prompt) + logger.info(f"提交结果: {submit_result}") + + code = submit_result.get("code") + if code == JmErrorCode.API_CONCURRENT_LIMIT[0]: + logger.warning(f"API并发限制,等待{submit_retry_interval}毫秒后重试...") + time.sleep(submit_retry_interval / 1000) + submit_retry_count += 1 + continue + elif not JmErrorCode.is_success(code): + logger.error(f"提交任务失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}") + return + + # 成功获取结果,跳出循环 + break + except Exception as e: + logger.error(f"提交任务异常: {str(e)}", exc_info=True) + submit_retry_count += 1 + if submit_retry_count < max_submit_retries: + logger.warning(f"等待{submit_retry_interval}毫秒后重试...") + time.sleep(submit_retry_interval / 1000) + else: + raise # 达到最大重试次数,抛出异常 + + if submit_retry_count >= max_submit_retries: + logger.error(f"提交任务失败,已达到最大重试次数: {max_submit_retries}") + return + + # 获取任务ID + task_id = submit_result.get("data", {}).get("task_id") + logger.info(f"任务ID: {task_id}") + + # 检查任务是不是已经结束 + query_retry_count = 0 + max_query_retries = 10000 # 最大查询次数 + query_retry_interval = 3000 # 查询间隔(毫秒) + + while query_retry_count < max_query_retries: + result = JmCommon.query_task_result(task_id) + logger.info(f"查询结果: {result}") + + code = result.get("code") + if not JmErrorCode.is_success(code): + logger.error(f"查询失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}") + break + + data = result.get("data") + if data and data.get("video_url"): + video_url = data.get("video_url") + logger.info(f"视频地址: {video_url}") + + # 下载视频 + save_video_path = f"{JmCommon.base_path}{mp4_file_name}" + logger.info("开始下载视频...") + JmCommon.download_file(video_url, save_video_path) + logger.info(f"视频已下载到: {save_video_path}") + break + else: + logger.info(f"任务处理中,等待{query_retry_interval}毫秒后重试...") + time.sleep(query_retry_interval / 1000) + query_retry_count += 1 + + if query_retry_count >= max_query_retries: + logger.error(f"任务查询超时,已达到最大查询次数: {max_query_retries}") + + except Exception as e: + logger.error(f"程序执行异常: {str(e)}", exc_info=True) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsLightRag/JiMeng/JmTxt2Video.py b/dsLightRag/JiMeng/JmTxt2Video.py new file mode 100644 index 00000000..614b7796 --- /dev/null +++ b/dsLightRag/JiMeng/JmTxt2Video.py @@ -0,0 +1,122 @@ +import json +import time +import logging +from JiMeng.Kit.JmCommon import JmCommon +from JiMeng.Kit.JmErrorCode import JmErrorCode + +# 配置日志 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger('JmTxt2Video') + +class JmTxt2Video(JmCommon): + action = "CVSync2AsyncSubmitTask" + req_key = "jimeng_vgfm_t2v_l20" + + @staticmethod + def generate_video(prompt): + """ + 生成视频 + + :param prompt: 提示词 + :return: 任务结果 + :raises Exception: 异常信息 + """ + req = { + "req_key": JmTxt2Video.req_key, + "prompt": prompt + } + + response_body = JmCommon.do_request( + method="POST", + query_list={}, # 添加空字典作为query_list参数 + headers={}, + body=json.dumps(req).encode('utf-8'), + action=JmTxt2Video.action + ) + return json.loads(response_body) + + +def main(): + try: + # 示例提示词 + # prompt = "蓝色毛绒玩具在超市里拖地,结果拖把洒出好多五颜六色的粉末,接着把粉末洒向镜头前,镜头随之穿过粉末" + prompt = "第一个镜头-全景:身穿宇航服的狗狗左右环顾第二个镜头-近景:宇航员狗狗向操作台走去第三个镜头-特写:宇航员狗狗伸手去按操作界" + # 保存的文件名称 + mp4_file_name = "Text2Video.mp4" + + # 添加重试逻辑,处理API并发限制错误 + jo = None + generate_retry_count = 0 + max_generate_retries = 1000 # 最大重试次数 + generate_retry_interval = 5000 # 重试间隔(毫秒) + + while generate_retry_count < max_generate_retries: + try: + jo = JmTxt2Video.generate_video(prompt) + logger.info(f"结果: {jo}") + + code = jo.get("code") + if code == JmErrorCode.API_CONCURRENT_LIMIT[0]: + logger.warning(f"API并发限制,等待{generate_retry_interval}毫秒后重试...") + time.sleep(generate_retry_interval / 1000) + generate_retry_count += 1 + continue + elif not JmErrorCode.is_success(code): + logger.error(f"生成视频失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}") + return + # 成功获取结果,跳出循环 + break + except Exception as e: + logger.error(f"生成视频异常: {str(e)}", exc_info=True) + generate_retry_count += 1 + if generate_retry_count < max_generate_retries: + logger.warning(f"等待{generate_retry_interval}毫秒后重试...") + time.sleep(generate_retry_interval / 1000) + else: + raise # 达到最大重试次数,抛出异常 + + if generate_retry_count >= max_generate_retries: + logger.error(f"生成视频失败,已达到最大重试次数: {max_generate_retries}") + return + + task_id = jo.get("data", {}).get("task_id") + logger.info(f"任务ID: {task_id}") + + # 检查任务是不是已经结束 + retry_count = 0 + max_retries = 1000 # 最大重试次数 + retry_interval = 3000 # 重试间隔(毫秒) + + while retry_count < max_retries: + result = JmCommon.query_task_result(task_id) + logger.info(f"查询结果: {result}") + + code = result.get("code") + if not JmErrorCode.is_success(code): + logger.error(f"查询失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}") + break + + data = result.get("data") + if data and data.get("video_url"): + video_url = data.get("video_url") + logger.info(f"视频地址:{video_url}") + # 下载mp4 + save_video_path = f"{JmCommon.base_path}{mp4_file_name}" + logger.info("开始下载视频...") + JmCommon.download_file(video_url, save_video_path) + logger.info(f"视频已下载到: {save_video_path}") + break + else: + logger.info(f"任务处理中,等待{retry_interval}毫秒后重试...") + time.sleep(retry_interval / 1000) + retry_count += 1 + + if retry_count >= max_retries: + logger.error(f"任务查询超时,已达到最大重试次数: {max_retries}") + + except Exception as e: + logger.error(f"程序执行异常: {str(e)}", exc_info=True) + + +if __name__ == "__main__": + main() \ No newline at end of file