This commit is contained in:
2025-08-21 09:57:14 +08:00
parent 51afc9eaa7
commit 006ac0bea5
7 changed files with 272 additions and 125 deletions

View File

@@ -0,0 +1,182 @@
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"
default_max_retries = 10000 # 最大查询次数
default_retry_interval = 3000 # 查询间隔(毫秒)
def __init__(self, max_retries=None, retry_interval=None):
"""
初始化JmImg2Video实例
:param max_retries: 最大查询次数,默认使用类的默认值
:param retry_interval: 查询间隔(毫秒),默认使用类的默认值
"""
self.max_retries = max_retries if max_retries is not None else self.default_max_retries
self.retry_interval = retry_interval if retry_interval is not None else self.default_retry_interval
def create_video_task(self, image_urls, prompt):
"""
根据首帧图片和分镜头脚本生成视频任务
:param image_urls: 图片URL列表
:param prompt: 提示词(分镜头脚本)
:return: 任务ID
:raises Exception: 任务创建失败时抛出异常
"""
# 创建请求体
req = {
"req_key": self.req_key,
"image_urls": image_urls,
"prompt": prompt
}
try:
response_body = self.do_request(
method="POST",
query_list={},
body=json.dumps(req).encode('utf-8'),
action=self.action
)
result = json.loads(response_body)
code = result.get("code")
if not JmErrorCode.is_success(code):
error_msg = f"提交任务失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}"
logger.error(error_msg)
raise Exception(error_msg)
# 获取任务ID
task_id = result.get("data", {}).get("task_id")
if not task_id:
error_msg = "提交任务成功但未返回任务ID"
logger.error(error_msg)
raise Exception(error_msg)
logger.info(f"任务创建成功任务ID: {task_id}")
return task_id
except Exception as e:
logger.error(f"创建视频任务异常: {str(e)}", exc_info=True)
raise
def query_task_status(self, task_id):
"""
根据任务ID查询视频生成状态
:param task_id: 视频生成任务ID
:return: 字典,包含任务状态信息
- status: 'completed' 表示完成, 'processing' 表示处理中, 'failed' 表示失败
- video_url: 视频URL如果任务完成
- error_msg: 错误信息,如果任务失败
"""
try:
result = self.query_task_result(task_id)
code = result.get("code")
if not JmErrorCode.is_success(code):
error_msg = f"查询任务失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}"
logger.error(error_msg)
return {
"status": "failed",
"error_msg": error_msg
}
data = result.get("data", {})
if data and data.get("video_url"):
video_url = data.get("video_url")
logger.info(f"任务已完成,视频地址: {video_url}")
return {
"status": "completed",
"video_url": video_url
}
else:
logger.info(f"任务处理中任务ID: {task_id}")
return {
"status": "processing"
}
except Exception as e:
logger.error(f"查询任务状态异常: {str(e)}", exc_info=True)
return {
"status": "failed",
"error_msg": str(e)
}
def wait_for_task_completion(self, task_id, download_path=None):
"""
等待任务完成并可选地下载视频
:param task_id: 视频生成任务ID
:param download_path: 视频下载路径如果为None则不下载
:return: 视频URL如果任务成功完成
:raises Exception: 任务失败或超时
"""
query_retry_count = 0
while query_retry_count < self.max_retries:
status_info = self.query_task_status(task_id)
if status_info["status"] == "completed":
video_url = status_info["video_url"]
if download_path:
logger.info(f"开始下载视频到: {download_path}")
self.download_file(video_url, download_path)
logger.info(f"视频已下载完成")
return video_url
elif status_info["status"] == "failed":
raise Exception(f"任务失败: {status_info.get('error_msg', '未知错误')}")
else:
logger.info(f"任务处理中,等待{self.retry_interval}毫秒后重试...")
time.sleep(self.retry_interval / 1000)
query_retry_count += 1
error_msg = f"任务查询超时,已达到最大查询次数: {self.max_retries}"
logger.error(error_msg)
raise Exception(error_msg)
def main():
try:
# 示例用法
image_urls = [
"https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/Text2Img.jpg"
]
prompt = """
|镜号|运镜|画面内容|
| :---: | :---: | --- |
|1|固定镜头|穿着黄色安全帽、反光背心的工人背着小女孩,左手抱着小熊玩偶,两人脸上洋溢着幸福的笑容,背景是老旧居民楼和湿漉漉的路面。|
|2|推镜头|镜头从两人全身慢慢推近至两人上半身,聚焦两人亲密温馨的神态。|
|3|平移镜头|镜头从两人正面平移至侧面,展现工人的朴实与小女孩的可爱。|
|4|拉镜头|镜头逐渐拉远,展现两人在居民楼间道路上的整体场景。|
|5|固定镜头|定格在两人互动的画面,突出亲情的温暖氛围。|
"""
# 创建实例
img2video = JmImg2Video(max_retries=100, retry_interval=2000)
# 创建视频任务
task_id = img2video.create_video_task(image_urls, prompt)
print(f"任务ID: {task_id}")
# 等待任务完成并下载视频
mp4_file_name = "image2video.mp4"
save_video_path = fr"D:\dsWork\dsProject\dsLightRag\JiMeng\{mp4_file_name}"
video_url = img2video.wait_for_task_completion(task_id, save_video_path)
print(f"视频生成完成,地址: {video_url}")
except Exception as e:
logger.error(f"程序执行异常: {str(e)}", exc_info=True)
if __name__ == "__main__":
main()

View File

@@ -1,107 +0,0 @@
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.cn-north-1.myhuaweicloud.com/HuangHai/Backup/Text2Img.jpg"
]
prompt = """
|镜号|运镜|画面内容|
| :---: | :---: | --- |
|1|固定镜头|穿着黄色安全帽、反光背心的工人背着小女孩,左手抱着小熊玩偶,两人脸上洋溢着幸福的笑容,背景是老旧居民楼和湿漉漉的路面。|
|2|推镜头|镜头从两人全身慢慢推近至两人上半身,聚焦两人亲密温馨的神态。|
|3|平移镜头|镜头从两人正面平移至侧面,展现工人的朴实与小女孩的可爱。|
|4|拉镜头|镜头逐渐拉远,展现两人在居民楼间道路上的整体场景。|
|5|固定镜头|定格在两人互动的画面,突出亲情的温暖氛围。|
"""
mp4_file_name = "image2video.mp4"
submit_result = JmImg2Video.submit_image_to_video_task(image_urls, prompt)
logger.info(f"提交结果: {submit_result}")
code = submit_result.get("code")
if not JmErrorCode.is_success(code):
logger.error(f"提交任务失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}")
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 = fr"D:\dsWork\dsProject\dsLightRag\JiMeng\{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()

View File

@@ -3,11 +3,10 @@ import logging
import fastapi import fastapi
from fastapi import APIRouter from fastapi import APIRouter
from fastapi import HTTPException from fastapi import HTTPException
from openai import AsyncOpenAI
from Config import Config
from JiMeng.Kit.JmTxt2ImgUtil import JmTxt2Img
from JiMeng.Kit.FenJingTouGenerator import FenJingTouGenerator # 导入分镜头生成器 from JiMeng.Kit.FenJingTouGenerator import FenJingTouGenerator # 导入分镜头生成器
from JiMeng.Kit.JmImg2VideoUtil import JmImg2Video # 导入视频生成工具
from JiMeng.Kit.JmTxt2ImgUtil import JmTxt2Img
# 创建路由路由器 # 创建路由路由器
router = APIRouter(prefix="/api/jimeng", tags=["即梦"]) router = APIRouter(prefix="/api/jimeng", tags=["即梦"])
@@ -15,12 +14,6 @@ router = APIRouter(prefix="/api/jimeng", tags=["即梦"])
# 配置日志 # 配置日志
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# 初始化异步 OpenAI 客户端
client = AsyncOpenAI(
api_key=Config.ALY_LLM_API_KEY,
base_url=Config.ALY_LLM_BASE_URL
)
@router.post("/prompt_input") @router.post("/prompt_input")
async def prompt_input(request: fastapi.Request): async def prompt_input(request: fastapi.Request):
@@ -87,3 +80,68 @@ async def generate_fenjingtou(request: fastapi.Request):
logger.error(f"分镜头脚本生成失败: {str(e)}") logger.error(f"分镜头脚本生成失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"分镜头脚本生成失败: {str(e)}") raise HTTPException(status_code=500, detail=f"分镜头脚本生成失败: {str(e)}")
@router.post("/create_video_task")
async def create_video_task(request: fastapi.Request):
try:
data = await request.json()
image_url = data.get("image_url")
prompt = data.get("prompt")
if not image_url:
raise HTTPException(status_code=400, detail="缺少图片地址参数")
if not prompt:
raise HTTPException(status_code=400, detail="缺少分镜头脚本参数")
logger.info(f"收到视频任务创建请求,图片地址: {image_url},分镜头脚本: {prompt}")
# 创建视频生成实例并调用方法
img2video = JmImg2Video()
task_id = img2video.create_video_task([image_url], prompt)
logger.info(f"视频任务创建成功任务ID: {task_id}")
return {
"code": 200,
"message": "成功",
"data": {
"task_id": task_id
}
}
except HTTPException as e:
logger.error(f"请求参数错误: {str(e.detail)}")
raise e
except Exception as e:
logger.error(f"视频任务创建失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"视频任务创建失败: {str(e)}")
@router.post("/query_video_task")
async def query_video_task(request: fastapi.Request):
try:
data = await request.json()
task_id = data.get("task_id")
if not task_id:
raise HTTPException(status_code=400, detail="缺少任务ID参数")
logger.info(f"收到视频任务查询请求任务ID: {task_id}")
# 创建视频生成实例并调用方法
img2video = JmImg2Video()
status_info = img2video.query_task_status(task_id)
logger.info(f"视频任务查询成功,状态: {status_info['status']}")
return {
"code": 200,
"message": "成功",
"data": status_info
}
except HTTPException as e:
logger.error(f"请求参数错误: {str(e.detail)}")
raise e
except Exception as e:
logger.error(f"视频任务查询失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"视频任务查询失败: {str(e)}")

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>首帧预览</title> <title>二、分镜头脚本生成</title>
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -164,7 +164,7 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>首帧预览与分镜头生成</h1> <h1>二、分镜头脚本生成</h1>
<!-- 图片预览区域 --> <!-- 图片预览区域 -->
<div class="section image-section"> <div class="section image-section">
@@ -334,6 +334,24 @@
alert('生成分镜头时出错: ' + error.message); alert('生成分镜头时出错: ' + error.message);
}); });
}); });
// 制作视频按钮
const createVideoBtn = document.getElementById('createVideoBtn');
createVideoBtn.addEventListener('click', function() {
// 显示遮罩动画
mask.classList.add('active');
maskText.textContent = '正在制作视频,请稍候...';
// 模拟视频制作过程2秒后完成
setTimeout(function() {
// 隐藏遮罩动画
mask.classList.remove('active');
alert('视频制作成功!');
// 这里可以添加视频播放或下载的逻辑
// window.location.href = 'video_player.html';
}, 2000);
});
}); });
</script> </script>
</body> </body>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能视频生成 - 首帧</title> <title>一、首帧</title>
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -329,7 +329,7 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>智能视频生成 - 首帧</h1> <h1>一、首帧</h1>
<form id="promptForm"> <form id="promptForm">
<div class="form-group"> <div class="form-group">
@@ -659,13 +659,9 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// ... existing code ...
const mask = document.getElementById('mask'); const mask = document.getElementById('mask');
const maskText = document.getElementById('maskText'); const maskText = document.getElementById('maskText');
// ... existing code ...
// 新增函数:调用 API 生成图片
function generateImage(prompt) { function generateImage(prompt) {
// 显示遮罩动画 // 显示遮罩动画
mask.classList.add('active'); mask.classList.add('active');