195 lines
7.9 KiB
Python
195 lines
7.9 KiB
Python
# -*- coding: utf-8 -*-
|
||
import logging
|
||
import os
|
||
import subprocess
|
||
|
||
from Manim.Demos.ManimKit import SYS_PROMPT, generate_code_with_llm
|
||
|
||
# 更详细地控制日志输出
|
||
logger = logging.getLogger('T0')
|
||
logger.setLevel(logging.INFO)
|
||
handler = logging.StreamHandler()
|
||
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
||
logger.addHandler(handler)
|
||
|
||
if __name__ == '__main__':
|
||
try:
|
||
# 首次生成唯一时间戳
|
||
# timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3] # 保留到毫秒
|
||
# 后续写死时间戳
|
||
timestamp = "20250804_084910_884"
|
||
|
||
skeleton_basename = f"Skeleton/Skeleton_{timestamp}.py"
|
||
scene_class_name = f"Skeleton_{timestamp}"
|
||
|
||
# 教师输入的提示词数组(简化版)
|
||
teacher_prompts = [
|
||
{
|
||
"id": 1,
|
||
"task": "生成片头标题动画",
|
||
"requirements": [
|
||
"黑色背景",
|
||
"使用大标题文本'东师理想数学动画AI制作平台作品',字号=60,白色",
|
||
"添加副标题'正弦与余弦的关系',字号=36,蓝色",
|
||
"主标题+副标题淡入2秒,停留3秒,淡出2秒",
|
||
"最后清屏1秒"
|
||
]
|
||
},
|
||
{
|
||
"id": 2,
|
||
"task": "生成可运行的 Manim 场景骨架",
|
||
"requirements": [
|
||
"黑色背景",
|
||
"建立完整四象限坐标系(x∈[-3.5π,3.5π],y∈[-1.5,1.5])",
|
||
"不画任何曲线,只显示坐标轴,坐标轴要记得带箭头"
|
||
]
|
||
},
|
||
{
|
||
"id": 3,
|
||
"task": "在上一步基础上追加",
|
||
"requirements": [
|
||
"用绿色画 y = sin(x)",
|
||
"用 MathTex 在 (-3π,1) 位置标 'sin(x)'",
|
||
"动画:坐标轴淡入 1s → 曲线从左到右扫出 3s"
|
||
]
|
||
},
|
||
{
|
||
"id": 4,
|
||
"task": "在上一步基础上追加",
|
||
"requirements": [
|
||
"用红色画 y = cos(x)",
|
||
"用 MathTex 在 (3π,-1) 位置标 'cos(x)'",
|
||
"动画:TransformFromCopy 把正弦变成余弦 3s",
|
||
"最后在屏幕上方用黄色 MathTex 写出 cos(x) = sin(x + π/2)"
|
||
]
|
||
},
|
||
{
|
||
"id": 5,
|
||
"task": "在上一步基础上追加",
|
||
"requirements": [
|
||
"黄色虚线竖线随 x 移动",
|
||
"绿色圆点跟踪 sin(x)",
|
||
"红色圆点跟踪 cos(x)",
|
||
"ValueTracker 从 -3π 匀速扫到 3π,耗时 6s",
|
||
"其余元素保持不变"
|
||
]
|
||
}
|
||
]
|
||
|
||
# 系统补充信息(教师无需关心)
|
||
system_supplements = {
|
||
"base_info": f"文件名:{skeleton_basename},场景类:{scene_class_name}",
|
||
"output_requirement": "输出:完整代码 + `if __name__ == \"__main__\":`",
|
||
"continue_requirement": f"输出:完整替换 {skeleton_basename} 的新代码",
|
||
"video_config": "帧率:30fps,输出目录:output/",
|
||
}
|
||
|
||
# 设置保存路径
|
||
skeleton_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), skeleton_basename)
|
||
|
||
# 显示所有可用任务
|
||
print("可用任务列表:")
|
||
for prompt_info in teacher_prompts:
|
||
print(f"任务 {prompt_info['id']}: {prompt_info['task']}")
|
||
for req in prompt_info['requirements']:
|
||
print(f" - {req}")
|
||
print()
|
||
|
||
# 让用户选择要执行的任务ID
|
||
while True:
|
||
try:
|
||
selected_id = int(input("请输入要执行的任务ID (1-5): "))
|
||
if 1 <= selected_id <= 5:
|
||
break
|
||
else:
|
||
print("无效的ID,请输入1-5之间的数字。")
|
||
except ValueError:
|
||
print("请输入有效的数字。")
|
||
|
||
# 查找用户选择的任务
|
||
selected_prompt = None
|
||
for prompt_info in teacher_prompts:
|
||
if prompt_info['id'] == selected_id:
|
||
selected_prompt = prompt_info
|
||
break
|
||
|
||
# 处理用户选择的任务
|
||
prompt_id = selected_prompt["id"]
|
||
task = selected_prompt["task"]
|
||
requirements = selected_prompt["requirements"]
|
||
logger.info(f"处理任务 {prompt_id}: {task}...")
|
||
|
||
# 构建教师输入部分
|
||
teacher_part = f"任务:{task}\n要求:\n"
|
||
for req in requirements:
|
||
teacher_part += f"- {req}\n"
|
||
|
||
# 构建完整提示词
|
||
if prompt_id == 1:
|
||
# 第一个任务,包含基础信息和输出要求
|
||
full_prompt = f"""
|
||
{teacher_part}
|
||
{system_supplements['base_info']}
|
||
{system_supplements['output_requirement']}
|
||
重要配置要求:
|
||
- 请设置输出目录为output/
|
||
- 设置帧率为30fps
|
||
- 确保生成MP4格式视频
|
||
- 添加标题淡入淡出动画以确保生成视频而非静态图像
|
||
"""
|
||
else:
|
||
# 后续任务,检查前置文件是否存在
|
||
if os.path.exists(skeleton_path):
|
||
# 构建继续要求
|
||
full_prompt = f"""
|
||
{teacher_part}
|
||
{system_supplements['continue_requirement']}
|
||
|
||
{skeleton_basename} 内容:
|
||
{open(skeleton_path, 'r', encoding='utf-8').read()}
|
||
重要配置要求:
|
||
- 请保持输出目录为output/
|
||
- 保持帧率为30fps
|
||
- 确保生成MP4格式视频
|
||
"""
|
||
else:
|
||
logger.error(f"错误: 任务 {prompt_id} 依赖于前置任务生成的 {skeleton_basename} 文件,但该文件不存在。")
|
||
logger.info("请先执行任务 1 生成基础文件,然后再执行后续任务。")
|
||
exit(1)
|
||
|
||
# 格式化系统提示
|
||
formatted_prompt = SYS_PROMPT.format(XuQiu=full_prompt,Preview=True)
|
||
|
||
# 调用函数生成代码
|
||
generate_code_with_llm(prompt=formatted_prompt, save_path=skeleton_path)
|
||
|
||
logger.info(f"任务 {prompt_id} 处理完成!生成的文件为:{skeleton_basename}")
|
||
|
||
# 执行生成的Skeleton文件
|
||
logger.info(f"正在执行 {skeleton_basename} ...")
|
||
try:
|
||
# 定义清晰的输出目录
|
||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||
output_dir = os.path.join(current_dir, "output")
|
||
os.makedirs(output_dir, exist_ok=True)
|
||
|
||
# 构建执行命令,根据preview参数决定是否添加-p选项
|
||
command = ['python', skeleton_path, '-o', output_dir]
|
||
|
||
# 执行文件并通过命令行参数指定输出目录
|
||
subprocess.run(
|
||
command,
|
||
check=True
|
||
)
|
||
logger.info(f"视频已生成到: {output_dir}\\videos\\1080p30\\Skeleton_{timestamp}.mp4")
|
||
# TODO
|
||
# 这个生成的视频路径对于研发来讲很重要,需要返回给前端,前端展示给用户查看效果
|
||
|
||
except subprocess.CalledProcessError as e:
|
||
logger.error(f"执行文件时出错: {str(e)}")
|
||
except Exception as e:
|
||
logger.error(f"执行过程中发生未知错误: {str(e)}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"程序执行出错: {str(e)}")
|