分场景业务端完整实现流程
本文档详细介绍了分场景业务端的完整实现流程,主要内容包括以下几个部分:
- 整体流程概述:描述了系统的业务流程,从调用大模型生成场景代码到将生成的代码发送到远程服务器(java-linux)执行,再到 java-linux 自动处理视频文件与流式播放支持。
- 代码生成与错误修复提示模板:展示了用于大模型生成代码以及错误避让、错误修复的提示模板内容。
- 各个模块代码解析与实现:包括 VO 类、代码生成服务(CodeGenerateService)、代码修复服务(FixCodeService)、Linux 执行服务(LinuxService)、提示模板服务(ManimPromptService)以及视频生成与解释服务(ExplanationVideoService)的完整代码。
整个系统主要流程如下:
调用大模型生成代码
根据用户输入的主题与语言,使用预先定义的提示模板(如 user_topic_prompt.txt、generate_next_sence_prompt.txt 等),调用大模型生成场景代码。将生成的代码发送到远程服务器
生成的代码经过代码生成服务处理后,通过调用远程服务器接口(通过 LinuxService 中封装的调用方法),在 java-linux 上执行 Python 脚本。自动处理视频文件与流式播放支持
java-linux 端自动管理视频文件处理、HLS 分段生成、流式播放以及场景合并等任务,当代码执行过程中出现错误时,会调用代码修复服务自动调取大模型修复生成错误代码。此外,系统支持将错误原因以及修复建议记录下来,通过提示模板(code_avoid_prompt.txt)记录错误规则和改进措施。
接下来,将分部分展示各个文件的具体内容和详细说明。
1. 提示模板文件
1.1 user_topic_prompt.txt
该模板用于生成场景 1 的 Python 脚本。模板内容如下:
The Topic is:#(topic)
The generated subtitles and narration must use the #(language).
Please only output the runnable Python script code for scene 1, including the main method, speech synthesis, imports, and constant definitions.
1.2 generate_next_sence_prompt.txt
该模板用于生成下一个场景的 Python 脚本代码,要求生成的代码与前一场景接口风格、坐标系统和元素保持同步,不包含前一场景的代码。模板内容如下:
Please only output the runnable Python script code for next scene do not include the code from the previous scene but including the main method, speech synthesis, imports, and constant definitions.
Please ensure that the generated code for the next scene is synchronized with the previous scene's code in terms of interface style, coordinate system, and elements.
If there is no next scene, output done.
1.3 code_avoid_prompt.txt
当代码执行出错时,此提示模板用于分析错误原因并生成错误避免提示,以便后续生成代码时预防同样的错误。模板内容如下:
Please carefully analyze the cause of this error and summarize this error into a prompt.
I want to add it to the prompt template of the large model to prevent you from making the same mistake when generating code next time.
Do not output the python script code, output the error reason in English. Output according to the following format.:
### 【Manim Code Generation Rule: title】
1. Problem Description
2. Reason
3. Correct Practice (Must Follow)
4. Correct Code Example
1.4 code_fix_prompt.txt
当代码执行遇到错误时,此模板用于修复错误,模板要求返回修复后的代码。模板内容如下:
Code execution encountered an error, please fix it and output only the fixed code.
Eroor:
#(error)
2. 数据传输及 VO 类
2.1 ExplanationResult.java
该类封装了视频生成后的结果,包括生成的视频路径和视频时长信息。
package com.litongjava.manim.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExplanationResult {
private String video_path;
private int video_length;
}
2.2 ExplanationVo.java
该类用于封装用户生成视频请求中的解释信息,如提示文本、语音提供者、语音 ID、语言及用户标识。
package com.litongjava.manim.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
//"voice_provider": "openai",
//"voice_id": "default_voice",
//"language": "english",
public class ExplanationVo {
private String prompt;
private String voice_provider = "openai";
private String voice_id = "default_voice";
private String language = "english";
private String user_id;
public ExplanationVo(String text) {
this.prompt = text;
}
public ExplanationVo(String user_id, String text) {
this.user_id = user_id;
this.prompt = text;
}
}
3. 核心业务服务实现
下面依次展示各个关键服务的完整代码。所有代码均未省略,且附带详细注释说明各个方法的用途。
3.1 CodeGenerateService.java
该服务负责根据给定主题和其它参数调用大模型,生成相应的 Python 脚本代码,并处理返回结果。
同时,当生成的代码返回中存在附加格式(如 Markdown 中的代码块标识)时,也会进行相应提取和修正。
package com.litongjava.manim.services;
import java.io.File;
import java.io.IOException;
import com.litongjava.db.activerecord.Db;
import com.litongjava.gemini.GeminiChatRequestVo;
import com.litongjava.gemini.GeminiChatResponseVo;
import com.litongjava.gemini.GeminiClient;
import com.litongjava.gemini.GeminiGenerationConfigVo;
import com.litongjava.gemini.GoogleGeminiModels;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.json.JsonUtils;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;
import com.litongjava.vo.ToolVo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CodeGenerateService {
public final static boolean debug = false;
public String genAvoidPromptCode(GeminiChatRequestVo geminiChatRequestVo) {
GeminiChatResponseVo chatResponse = null;
GeminiGenerationConfigVo geminiGenerationConfigVo = new GeminiGenerationConfigVo();
//设置参数
geminiGenerationConfigVo.setTemperature(0d);
geminiChatRequestVo.setGenerationConfig(geminiGenerationConfigVo);
try {
chatResponse = GeminiClient.generate(GoogleGeminiModels.GEMINI_2_5_PRO_PREVIEW_03_25, geminiChatRequestVo);
} catch (Exception e) {
log.error("Faile to generate code", e);
return null;
}
String generatedText = chatResponse.getCandidates().get(0).getContent().getParts().get(0).getText();
return generatedText;
}
public String genManaimCode(String topic, String md5, GeminiChatRequestVo geminiChatRequestVo) {
String sql = "select python_code from ef_generate_code where md5=?";
String pythonCode = Db.queryStr(sql, md5);
if (pythonCode != null) {
return pythonCode;
}
GeminiChatResponseVo chatResponse = null;
GeminiGenerationConfigVo geminiGenerationConfigVo = new GeminiGenerationConfigVo();
geminiGenerationConfigVo.setTemperature(0d);
geminiChatRequestVo.setGenerationConfig(geminiGenerationConfigVo);
String prompt = Aop.get(ManimPromptService.class).getSystemPrompt();
geminiChatRequestVo.setSystemPrompt(prompt);
if (debug) {
log.info(JsonUtils.toSkipNullJson(geminiChatRequestVo));
}
try {
//chatResponse = GeminiClient.generate(GoogleGeminiModels.GEMINI_2_5_PRO_EXP_03_25, geminiChatRequestVo);
chatResponse = GeminiClient.generate(GoogleGeminiModels.GEMINI_2_5_PRO_PREVIEW_03_25, geminiChatRequestVo);
} catch (Exception e) {
log.error("Faile to generate code:{}", JsonUtils.toJson(geminiChatRequestVo), e);
return null;
}
String generatedText = chatResponse.getCandidates().get(0).getContent().getParts().get(0).getText();
String code = null;
int indexOf = generatedText.indexOf("```python");
if (indexOf == -1) {
if (generatedText.startsWith("{") && generatedText.endsWith("}")) {
code = generatedText;
ToolVo toolVo = null;
try {
toolVo = JsonUtils.parse(code.trim(), ToolVo.class);
} catch (Exception e) {
log.error("Failed to parse Json:{}", code);
return null;
}
return toolVo.getCode();
} else if ("done".contentEquals(generatedText)) {
return generatedText;
} else if (generatedText.startsWith("# -*- coding: utf-8 -*-")) {
return generatedText;
} else {
log.error("No code data found in the output:{}", generatedText);
return null;
}
} else {
int lastIndexOf = generatedText.lastIndexOf("```");
log.info("index:{},{}", indexOf, lastIndexOf);
if (lastIndexOf > 9) {
try {
code = generatedText.substring(indexOf + 9, lastIndexOf);
} catch (Exception e) {
log.error("generated text:{}", generatedText, e);
return null;
}
} else {
try {
code = generatedText.substring(indexOf + 9);
} catch (Exception e) {
log.error("generated text:{}", generatedText, e);
return null;
}
}
if (code != null) {
code = code.replace("from moviepy.editor import AudioFileClip", "from moviepy import AudioFileClip");
code.trim();
code = code.replaceAll("^(\\s*\\R)+", "").replaceAll("(\\R\\s*)+$", "");
new File("script").mkdirs();
try {
String path = "script/" + SnowflakeIdUtils.id() + ".py";
log.info("code file:{}", path);
FileUtil.writeString(code, path, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
}
return code;
}
}
}
3.2 FixCodeService.java
该服务负责捕获代码执行中的错误日志,并调用大模型修复代码。
同时,还会生成错误原因(通过 code_avoid_prompt.txt 模板),并将修复建议保存到数据库中。所有交互消息都通过 SSE 方式反馈给客户端。
package com.litongjava.manim.services;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.jfinal.kit.Kv;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Row;
import com.litongjava.gemini.GeminiChatRequestVo;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.openai.chat.ChatMessage;
import com.litongjava.template.PromptEngine;
import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.Tio;
import com.litongjava.tio.http.common.sse.SsePacket;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.hutool.ResourceUtil;
import com.litongjava.tio.utils.json.FastJson2Utils;
import com.litongjava.tio.utils.json.JsonUtils;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FixCodeService {
private static final boolean debug = false;
private CodeGenerateService codeGenerateService = Aop.get(CodeGenerateService.class);
public String fixCode(int i, String topic, String md5, List<ChatMessage> messages, String stdErr, GeminiChatRequestVo geminiChatRequestVo, ChannelContext channelContext) {
String code = null;
// 初始错误日志和 SSE 提示
String message = "Python code: execution failed:" + i;
log.error(message + ":{}", stdErr);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("progress", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
//log.info("fix request {}: {}", attempt, JsonUtils.toSkipNullJson(geminiChatRequestVo));
String info = "start fix code " + i;
log.info(info);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info", info));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
String codeFixPrompt = PromptEngine.renderToString("code_fix_prompt.txt", Kv.by("error", stdErr));
messages.add(new ChatMessage("user", codeFixPrompt));
geminiChatRequestVo.setChatMessages(messages);
if (debug) {
log.info("messages:{}", JsonUtils.toSkipNullJson(messages));
}
code = codeGenerateService.genManaimCode(topic, md5, geminiChatRequestVo);
messages.add(new ChatMessage("model", code));
return code;
}
public void generateErrorReasonAndSave(String topic, String code, String error, ChannelContext channelContext) {
String message;
message = "start generate avoid prompt";
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
List<ChatMessage> messages = new ArrayList<>();
messages.add(new ChatMessage("user", "The topic is:" + topic));
messages.add(new ChatMessage("model", "The code is:" + code));
messages.add(new ChatMessage("model", "The error is:" + error));
URL resource = ResourceUtil.getResource("prompts/code_avoid_prompt.txt");
StringBuilder codeAvoidPrompt = FileUtil.readURLAsString(resource);
// String codeAvoidPrompt = PromptEngine.renderToString("code_avoid_prompt.txt");
messages.add(new ChatMessage("user", codeAvoidPrompt.toString()));
if (debug) {
log.info("messages:{}", JsonUtils.toSkipNullJson(messages));
}
GeminiChatRequestVo geminiChatRequestVo = new GeminiChatRequestVo();
String systemPrompt = Aop.get(ManimPromptService.class).getSystemPrompt();
geminiChatRequestVo.setSystemPrompt(systemPrompt);
geminiChatRequestVo.setChatMessages(messages);
String avoidPrompt = codeGenerateService.genAvoidPromptCode(geminiChatRequestVo);
log.info("geneated avoidPrompt:{}", avoidPrompt);
messages.add(new ChatMessage("model", avoidPrompt));
message = "finish generate avoid prompt";
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
message = "start save to ef_generate_code_avoid_error_prompt";
log.info(message);
String final_request_json = JsonUtils.toJson(geminiChatRequestVo);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
Row row2 = Row.by("id", SnowflakeIdUtils.id()).set("final_request_json", final_request_json).set("prompt", avoidPrompt);
Db.save("ef_generate_code_avoid_error_prompt", row2);
message = "finish save to ef_generate_code_avoid_error_prompt";
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
}
}
3.3 LinuxService.java
该服务封装了与远程 Linux 执行服务器的交互方法,包括启动会话、结束会话、以及提交代码执行请求。
具体实现中,通过 LinuxClient 对外提供 API 调用,使用固定的 API 基地址和认证参数。
package com.litongjava.manim.services;
import com.jfinal.kit.Kv;
import com.litongjava.linux.LinuxClient;
import com.litongjava.linux.ProcessResult;
import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.Tio;
import com.litongjava.tio.http.common.sse.SsePacket;
import com.litongjava.tio.utils.json.FastJson2Utils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LinuxService {
// private static final String apiBase = "http://192.168.50.61";
private static final String apiBase = "http://13.216.69.13";
public final static boolean debug = false;
public ProcessResult startMainmSession() {
return LinuxClient.startMainmSession(apiBase, "123456");
}
public ProcessResult finish(long sessionIdPrt, String m3u8Path, String videos) {
return LinuxClient.finishMainmSession(apiBase, "123456", sessionIdPrt, m3u8Path, videos);
}
/**
* run code
* @param code
* @return
*/
public ProcessResult executeCode(String code) {
return LinuxClient.executeMainmCode(apiBase, "123456", code);
}
public ProcessResult executeCode(String code, long sessionPrt, String m3u8_path) {
return LinuxClient.executeMainmCode(apiBase, "123456", code, sessionPrt, m3u8_path);
}
public ProcessResult runCode(String code, long sessionIdPrt, String m3u8Path, ChannelContext channelContext) {
ProcessResult executeMainmCode = null;
try {
executeMainmCode = executeCode(code, sessionIdPrt, m3u8Path);
} catch (Exception e) {
String message = e.getMessage();
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
try {
executeMainmCode = executeCode(code, sessionIdPrt, m3u8Path);
} catch (Exception e2) {
message = e2.getMessage();
jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
try {
executeMainmCode = executeCode(code, sessionIdPrt, m3u8Path);
} catch (Exception e3) {
message = e2.getMessage();
jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("error", jsonBytes));
log.error(e.getMessage(), e);
return null;
}
}
}
return executeMainmCode;
}
}
3.4 ManimPromptService.java
该服务主要用于获取系统预设的提示信息(系统 prompt),包括生成代码的模板和完整的代码示例,通过加载资源文件内容并汇总数据库中保存的避免错误提示,构建最终返回给大模型的系统提示。
package com.litongjava.manim.services;
import java.net.URL;
import java.util.List;
import com.litongjava.db.activerecord.Db;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.hutool.ResourceUtil;
public class ManimPromptService {
public String getSystemPrompt() {
// 生成代码
URL resource = ResourceUtil.getResource("prompts/gen_video_code_en.txt");
StringBuilder stringBuffer = FileUtil.readURLAsString(resource);
String sql = "select prompt from ef_generate_code_avoid_error_prompt";
List<String> prompts = Db.queryListString(sql);
if (prompts != null && prompts.size() > 0) {
for (String string : prompts) {
stringBuffer.append(string).append("\r\n");
}
}
String prompt = stringBuffer.toString();
URL code_example_01_url = ResourceUtil.getResource("prompts/code_example_01.txt");
StringBuilder code_example_01 = FileUtil.readURLAsString(code_example_01_url);
URL code_example_02_url = ResourceUtil.getResource("prompts/code_example_02.txt");
StringBuilder code_example_02 = FileUtil.readURLAsString(code_example_02_url);
URL code_example_03_url = ResourceUtil.getResource("prompts/code_example_03.txt");
StringBuilder code_example_03 = FileUtil.readURLAsString(code_example_03_url);
URL code_example_04_url = ResourceUtil.getResource("prompts/code_example_04.txt");
StringBuilder code_example_04 = FileUtil.readURLAsString(code_example_04_url);
prompt = stringBuffer.toString() + "\r\n## Complete Python code example \r\n" + "\r\n### Example 1 \r\n" + code_example_01
//
+ "\r\n### Example 2 \r\n" + code_example_02
//
+ "\r\n### Example 3 \r\n" + code_example_03
//
+ "\r\n### Example 4 \r\n" + code_example_04;
return prompt;
}
}
3.5 ExplanationVideoService.java
该服务综合调用之前的各个服务,实现生成视频解释的整体流程。
主要流程为:
- 检查是否命中缓存(数据库中已存在生成的视频);
- 若未命中,则通过预设的 user_topic_prompt.txt 模板生成第一段 Python 代码,并调用 LinuxService 启动会话;
- 随后生成后续场景代码(通过 generate_next_sence_prompt.txt 模板),并逐步执行,每次执行失败时调用 FixCodeService 修复代码;
- 最终合并各个场景的视频,保存结果,并将视频地址返回给客户端,同时通过 SSE 实时反馈执行状态和进度消息。
package com.litongjava.manim.services;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import com.google.common.util.concurrent.Striped;
import com.jfinal.kit.Kv;
import com.jfinal.template.Template;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Row;
import com.litongjava.gemini.GeminiChatRequestVo;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.linux.ProcessResult;
import com.litongjava.manim.vo.ExplanationResult;
import com.litongjava.manim.vo.ExplanationVo;
import com.litongjava.openai.chat.ChatMessage;
import com.litongjava.template.PromptEngine;
import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.Tio;
import com.litongjava.tio.http.common.sse.SsePacket;
import com.litongjava.tio.http.server.util.SseEmitter;
import com.litongjava.tio.utils.SystemTimer;
import com.litongjava.tio.utils.crypto.Md5Utils;
import com.litongjava.tio.utils.hutool.StrUtil;
import com.litongjava.tio.utils.json.FastJson2Utils;
import com.litongjava.tio.utils.json.JsonUtils;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ExplanationVideoService {
private Striped<Lock> locks = Striped.lock(1024);
private LinuxService linuxService = Aop.get(LinuxService.class);
private CodeGenerateService codeGenerateService = Aop.get(CodeGenerateService.class);
FixCodeService fixCodeService = Aop.get(FixCodeService.class);
private String video_server_name = "https://manim.collegebot.ai";
private static final boolean debug = true;
public void index(ExplanationVo explanationVo, ChannelContext channelContext) {
String user_id = explanationVo.getUser_id();
String prompt = explanationVo.getPrompt();
String language = explanationVo.getLanguage();
long start = SystemTimer.currTime;
try {
ExplanationResult result = this.index(explanationVo, false, channelContext);
long end = SystemTimer.currTime;
if (result != null) {
String videoUrl = result.getVideo_path();
int video_length = result.getVideo_length();
if (videoUrl != null) {
long id = SnowflakeIdUtils.id();
Kv metadata = Kv.by("id", id);
Tio.send(channelContext, new SsePacket("metadata", JsonUtils.toJson(metadata)));
Row row = Row.by("id", id).set("video_url", videoUrl).set("video_length", video_length)
//
.set("title", prompt).set("language", language)
//
.set("voice_id", explanationVo.getVoice_id()).set("user_id", user_id).set("elapsed", (end - start));
Db.save("ef_ugvideo", row);
}
}
} finally {
if (channelContext != null) {
SseEmitter.closeSeeConnection(channelContext);
}
}
}
public ExplanationResult index(ExplanationVo xplanationVo, boolean isTelegram, ChannelContext channelContext) {
String topic = xplanationVo.getPrompt();
String language = xplanationVo.getLanguage();
String voice_provider = xplanationVo.getVoice_provider();
String voice_id = xplanationVo.getVoice_id();
String md5 = Md5Utils.getMD5(topic);
String sql = "select video_url,video_length from ef_generate_code where md5=? and language=? and voice_provider=? and voice_id=?";
Row row = Db.findFirst(sql, md5, language, voice_provider, voice_id);
String url = null;
if (row != null) {
log.info("hit cache ef_generate_code");
String output = row.getString("video_url");
url = video_server_name + output;
log.info("url:{}", url);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("url", url));
SsePacket ssePacket = new SsePacket("main", jsonBytes);
Tio.bSend(channelContext, ssePacket);
}
Integer video_length = row.getInt("video_length");
return new ExplanationResult(output, video_length);
}
// String generatedText = genSence(topic, md5);
// if (channelContext != null) {
// byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("sence", generatedText));
// SsePacket ssePacket = new SsePacket("sence", jsonBytes);
// Tio.bSend(channelContext, ssePacket);
// }
//user_topic_prompt.txt
String message = "Start generate python code";
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info", message));
SsePacket ssePacket = new SsePacket("progress", jsonBytes);
Tio.bSend(channelContext, ssePacket);
}
String sence = PromptEngine.renderToString("user_topic_prompt.txt", Kv.by("topic", topic).set("language", language));
log.info("sence:{}", sence);
List<ChatMessage> messages = new ArrayList<>();
messages.add(new ChatMessage("user", sence));
if (debug) {
log.info("messages:{}", JsonUtils.toSkipNullJson(messages));
}
//请求类
GeminiChatRequestVo geminiChatRequestVo = new GeminiChatRequestVo();
geminiChatRequestVo.setChatMessages(messages);
// log.info("request:{}", JsonUtils.toSkipNullJson(geminiChatRequestVo));
String code = codeGenerateService.genManaimCode(topic, md5, geminiChatRequestVo);
if (code != null && code.length() > 15) {
messages.add(new ChatMessage("model", code));
}
ProcessResult sessionResult = linuxService.startMainmSession();
long sessionIdPrt = sessionResult.getSessionIdPrt();
String m3u8Path = sessionResult.getOutput();
log.info("sessionIdPrt:{},m3u8Path:{}", sessionIdPrt, m3u8Path);
List<String> m3u8List = new ArrayList<>();
if (code != null) {
code = runFirstSenceCode(topic, md5, code, messages, geminiChatRequestVo, sessionIdPrt, m3u8Path, m3u8List, channelContext);
message = "finish first scene code";
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
message = "start generate second sence code";
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
String next_sence_prompt = PromptEngine.renderToString("generate_next_sence_prompt.txt");
messages.add(new ChatMessage("user", next_sence_prompt));
geminiChatRequestVo.setChatMessages(messages);
if (debug) {
log.info("messages:{}", JsonUtils.toSkipNullJson(messages));
}
code = codeGenerateService.genManaimCode(topic, md5, geminiChatRequestVo);
messages.add(new ChatMessage("model", code));
} else {
String info = "Failed to generate python code";
log.info(info);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("error", info));
SsePacket ssePacket = new SsePacket("error", jsonBytes);
Tio.bSend(channelContext, ssePacket);
}
return null;
}
url = video_server_name + m3u8Path;
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("url", url));
SsePacket ssePacket = new SsePacket("main", jsonBytes);
Tio.bSend(channelContext, ssePacket);
}
for (int i = 2; i < 100; i++) {
if (code == null) {
String info = "Failed to generate python code";
log.info(info);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("error", info));
SsePacket ssePacket = new SsePacket("error", jsonBytes);
Tio.bSend(channelContext, ssePacket);
}
return null;
} else if ("done".equals(code) && code.endsWith("done")) {
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info", "finish"));
SsePacket ssePacket = new SsePacket("progress", jsonBytes);
Tio.bSend(channelContext, ssePacket);
}
String videos = String.join(",", m3u8List);
ProcessResult finishResult = linuxService.finish(sessionIdPrt, m3u8Path, videos);
double video_length = finishResult.getViode_length();
Row saveRow = Row.by("id", SnowflakeIdUtils.id()).set("topic", topic).set("md5", md5)
//
.set("video_url", m3u8Path)
//
.set("language", language).set("voice_provider", voice_provider).set("voice_id", voice_id);
ExplanationResult explanationResult = new ExplanationResult();
if (video_length > 0) {
int video_length_int = (int) Math.round(video_length);
explanationResult.setVideo_length(video_length_int);
saveRow.set("video_length", video_length_int);
}
Db.save("ef_generate_code", saveRow);
log.info(video_server_name + m3u8Path.replace(".m3u8", ".mp4"));
explanationResult.setVideo_path(m3u8Path);
return explanationResult;
} else {
message = "Start run python code:" + i;
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info", message));
SsePacket ssePacket = new SsePacket("progress", jsonBytes);
Tio.bSend(channelContext, ssePacket);
}
ProcessResult executeMainmCode = linuxService.runCode(code, sessionIdPrt, m3u8Path, channelContext);
if (executeMainmCode != null && StrUtil.isBlank(executeMainmCode.getOutput())) {
String stdErr = executeMainmCode.getStdErr();
code = fixCodeService.fixCode(i, topic, md5, messages, stdErr, geminiChatRequestVo, channelContext);
fixCodeService.generateErrorReasonAndSave(topic, code, stdErr, channelContext);
} else {
String output = executeMainmCode.getOutput();
message = "finish code " + i + " " + output;
m3u8List.add(output);
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
message = "start generate next sence code:" + i;
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info ", message));
Tio.bSend(channelContext, new SsePacket("progress", jsonBytes));
}
String next_sence_prompt = PromptEngine.renderToString("generate_next_sence_prompt.txt");
messages.add(new ChatMessage("user", next_sence_prompt));
geminiChatRequestVo.setChatMessages(messages);
if (debug) {
log.info("messages:{}", JsonUtils.toSkipNullJson(messages));
}
code = codeGenerateService.genManaimCode(topic, md5, geminiChatRequestVo);
messages.add(new ChatMessage("model", code));
}
}
}
return null;
}
private String runFirstSenceCode(String topic, String md5, String code, List<ChatMessage> messages, GeminiChatRequestVo geminiChatRequestVo, long sessionIdPrt, String m3u8Path,
List<String> m3u8List, ChannelContext channelContext) {
String message;
for (int i = 1; i < 100; i++) {
message = "Start run first sence python code" + i;
log.info(message);
if (channelContext != null) {
byte[] jsonBytes = FastJson2Utils.toJSONBytes(Kv.by("info", message));
SsePacket ssePacket = new SsePacket("progress", jsonBytes);
Tio.bSend(channelContext, ssePacket);
}
ProcessResult executeMainmCode = linuxService.runCode(code, sessionIdPrt, m3u8Path, channelContext);
if (executeMainmCode != null && StrUtil.isBlank(executeMainmCode.getOutput())) {
String stdErr = executeMainmCode.getStdErr();
code = fixCodeService.fixCode(i, topic, md5, messages, stdErr, geminiChatRequestVo, channelContext);
fixCodeService.generateErrorReasonAndSave(topic, code, stdErr, channelContext);
} else {
String output = executeMainmCode.getOutput();
m3u8List.add(output);
return code;
}
}
return code;
}
public String genSence(String text, String md5) {
String sql = "select sence_prompt from ef_generate_sence where md5=?";
String generatedText = Db.queryStr(sql, md5);
if (generatedText != null) {
log.info("Cache Hit ef_generate_sence");
return generatedText;
}
Lock lock = locks.get(md5);
lock.lock();
try {
// 生成场景
Template template = PromptEngine.getTemplate("gen_video_sence_en.txt");
String prompt = template.renderToString();
String userMessageText = text + " \r\nplease reply use this message language.If English is involved, use English vocabulary instead of Pinyin.";
List<ChatMessage> messages = new ArrayList<>();
messages.add(new ChatMessage("user", userMessageText));
GeminiChatRequestVo geminiChatRequestVo = new GeminiChatRequestVo();
geminiChatRequestVo.setChatMessages(messages);
geminiChatRequestVo.setSystemPrompt(prompt);
log.info("request:{}", JsonUtils.toSkipNullJson(geminiChatRequestVo));
GeminiChatResponseVo chatResponse = GeminiClient.generate(GoogleGeminiModels.GEMINI_2_0_FLASH, geminiChatRequestVo);
generatedText = chatResponse.getCandidates().get(0).getContent().getParts().get(0).getText();
Row row = Row.by("id", SnowflakeIdUtils.id()).set("topic", text).set("md5", md5).set("sence_prompt", generatedText);
Db.save("ef_generate_sence", row);
return generatedText;
} finally {
lock.unlock();
}
}
public String genSence(String topic) {
return this.genSence(topic, Md5Utils.getMD5(topic));
}
}
4. 总结
本文档详细介绍了分场景业务端的完整实现流程和代码实现。主要内容包括:
- 提示模板:定义了生成场景代码、生成下一个场景代码、错误避免提示以及代码修复的标准格式,为大模型生成代码提供指导。
- 业务流程:从用户输入主题开始,生成可运行的 Python 脚本代码,通过 LinuxService 提交到远程服务器执行,后续处理包括视频 HLS 分段、场景合并以及通过 SSE 向客户端返回视频播放地址。
- 核心服务代码:包含了 VO 类、CodeGenerateService、FixCodeService、LinuxService、ManimPromptService 以及 ExplanationVideoService 等模块的完整实现,详细展示了各模块如何配合实现自动化视频生成与故障修复。