mirror of
https://github.com/linyqh/NarratoAI.git
synced 2025-12-11 18:42:49 +00:00
feat(subtitle_analysis): 更新解说文案生成逻辑,增强字幕内容支持
在多个文件中重构了解说文案生成的实现,新增对原始字幕内容的支持,以提供准确的时间戳信息。更新了相关参数和提示词模板,优化了生成逻辑,提升了内容的准确性和用户体验。同时,注释部分进行了清理,去除了调试信息的输出。
This commit is contained in:
parent
c61462d706
commit
e3a5e34c78
@ -49,16 +49,8 @@ class SubtitleAnalyzer:
|
|||||||
self.temperature = temperature
|
self.temperature = temperature
|
||||||
self.provider = provider or self._detect_provider()
|
self.provider = provider or self._detect_provider()
|
||||||
|
|
||||||
# 设置提示词模板
|
# 设置自定义提示词(如果提供)
|
||||||
if custom_prompt:
|
self.custom_prompt = custom_prompt
|
||||||
self.prompt_template = custom_prompt
|
|
||||||
else:
|
|
||||||
# 使用新的提示词管理系统
|
|
||||||
self.prompt_template = PromptManager.get_prompt(
|
|
||||||
category="short_drama_narration",
|
|
||||||
name="plot_analysis",
|
|
||||||
parameters={}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 根据提供商类型确定是否为原生Gemini
|
# 根据提供商类型确定是否为原生Gemini
|
||||||
self.is_native_gemini = self.provider.lower() == 'gemini'
|
self.is_native_gemini = self.provider.lower() == 'gemini'
|
||||||
@ -95,7 +87,16 @@ class SubtitleAnalyzer:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 构建完整提示词
|
# 构建完整提示词
|
||||||
prompt = f"{self.prompt_template}\n\n{subtitle_content}"
|
if self.custom_prompt:
|
||||||
|
# 使用自定义提示词
|
||||||
|
prompt = f"{self.custom_prompt}\n\n{subtitle_content}"
|
||||||
|
else:
|
||||||
|
# 使用新的提示词管理系统,正确传入参数
|
||||||
|
prompt = PromptManager.get_prompt(
|
||||||
|
category="short_drama_narration",
|
||||||
|
name="plot_analysis",
|
||||||
|
parameters={"subtitle_content": subtitle_content}
|
||||||
|
)
|
||||||
|
|
||||||
if self.is_native_gemini:
|
if self.is_native_gemini:
|
||||||
# 使用原生Gemini API格式
|
# 使用原生Gemini API格式
|
||||||
@ -127,7 +128,7 @@ class SubtitleAnalyzer:
|
|||||||
"temperature": self.temperature,
|
"temperature": self.temperature,
|
||||||
"topK": 40,
|
"topK": 40,
|
||||||
"topP": 0.95,
|
"topP": 0.95,
|
||||||
"maxOutputTokens": 4000,
|
"maxOutputTokens": 64000,
|
||||||
"candidateCount": 1
|
"candidateCount": 1
|
||||||
},
|
},
|
||||||
"safetySettings": [
|
"safetySettings": [
|
||||||
@ -356,13 +357,14 @@ class SubtitleAnalyzer:
|
|||||||
logger.error(f"保存分析结果时发生错误: {str(e)}")
|
logger.error(f"保存分析结果时发生错误: {str(e)}")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def generate_narration_script(self, short_name:str, plot_analysis: str, temperature: float = 0.7) -> Dict[str, Any]:
|
def generate_narration_script(self, short_name: str, plot_analysis: str, subtitle_content: str = "", temperature: float = 0.7) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
根据剧情分析生成解说文案
|
根据剧情分析生成解说文案
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
short_name: 短剧名称
|
short_name: 短剧名称
|
||||||
plot_analysis: 剧情分析内容
|
plot_analysis: 剧情分析内容
|
||||||
|
subtitle_content: 原始字幕内容,用于提供准确的时间戳信息
|
||||||
temperature: 生成温度,控制创造性,默认0.7
|
temperature: 生成温度,控制创造性,默认0.7
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -375,7 +377,8 @@ class SubtitleAnalyzer:
|
|||||||
name="script_generation",
|
name="script_generation",
|
||||||
parameters={
|
parameters={
|
||||||
"drama_name": short_name,
|
"drama_name": short_name,
|
||||||
"plot_analysis": plot_analysis
|
"plot_analysis": plot_analysis,
|
||||||
|
"subtitle_content": subtitle_content
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -412,7 +415,7 @@ class SubtitleAnalyzer:
|
|||||||
"temperature": temperature,
|
"temperature": temperature,
|
||||||
"topK": 40,
|
"topK": 40,
|
||||||
"topP": 0.95,
|
"topP": 0.95,
|
||||||
"maxOutputTokens": 4000,
|
"maxOutputTokens": 64000,
|
||||||
"candidateCount": 1,
|
"candidateCount": 1,
|
||||||
"stopSequences": ["```", "注意", "说明"]
|
"stopSequences": ["```", "注意", "说明"]
|
||||||
},
|
},
|
||||||
@ -675,6 +678,7 @@ def analyze_subtitle(
|
|||||||
def generate_narration_script(
|
def generate_narration_script(
|
||||||
short_name: str = None,
|
short_name: str = None,
|
||||||
plot_analysis: str = None,
|
plot_analysis: str = None,
|
||||||
|
subtitle_content: str = None,
|
||||||
api_key: Optional[str] = None,
|
api_key: Optional[str] = None,
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
base_url: Optional[str] = None,
|
base_url: Optional[str] = None,
|
||||||
@ -689,6 +693,7 @@ def generate_narration_script(
|
|||||||
Args:
|
Args:
|
||||||
short_name: 短剧名称
|
short_name: 短剧名称
|
||||||
plot_analysis: 剧情分析内容,直接提供
|
plot_analysis: 剧情分析内容,直接提供
|
||||||
|
subtitle_content: 原始字幕内容,用于提供准确的时间戳信息
|
||||||
api_key: API密钥
|
api_key: API密钥
|
||||||
model: 模型名称
|
model: 模型名称
|
||||||
base_url: API基础URL
|
base_url: API基础URL
|
||||||
@ -710,7 +715,7 @@ def generate_narration_script(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 生成解说文案
|
# 生成解说文案
|
||||||
result = analyzer.generate_narration_script(short_name, plot_analysis, temperature)
|
result = analyzer.generate_narration_script(short_name, plot_analysis, subtitle_content or "", temperature)
|
||||||
|
|
||||||
# 保存结果
|
# 保存结果
|
||||||
if save_result and result["status"] == "success":
|
if save_result and result["status"] == "success":
|
||||||
@ -740,10 +745,16 @@ if __name__ == '__main__':
|
|||||||
print("字幕分析成功!")
|
print("字幕分析成功!")
|
||||||
print("分析结果:")
|
print("分析结果:")
|
||||||
print(analysis_result["analysis"])
|
print(analysis_result["analysis"])
|
||||||
|
|
||||||
|
# 读取原始字幕内容用于解说脚本生成
|
||||||
|
with open(subtitle_path, 'r', encoding='utf-8') as f:
|
||||||
|
subtitle_content = f.read()
|
||||||
|
|
||||||
# 根据剧情生成解说文案
|
# 根据剧情生成解说文案
|
||||||
narration_result = generate_narration_script(
|
narration_result = generate_narration_script(
|
||||||
|
short_name="家里家外",
|
||||||
plot_analysis=analysis_result["analysis"],
|
plot_analysis=analysis_result["analysis"],
|
||||||
|
subtitle_content=subtitle_content,
|
||||||
api_key=text_api_key,
|
api_key=text_api_key,
|
||||||
model=text_model,
|
model=text_model,
|
||||||
base_url=text_base_url,
|
base_url=text_base_url,
|
||||||
|
|||||||
@ -251,7 +251,7 @@ def execute_ffmpeg_with_fallback(
|
|||||||
bool: 是否成功
|
bool: 是否成功
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
logger.debug(f"执行ffmpeg命令: {' '.join(cmd)}")
|
# logger.debug(f"执行ffmpeg命令: {' '.join(cmd)}")
|
||||||
|
|
||||||
# 在Windows系统上使用UTF-8编码处理输出
|
# 在Windows系统上使用UTF-8编码处理输出
|
||||||
is_windows = os.name == 'nt'
|
is_windows = os.name == 'nt'
|
||||||
|
|||||||
@ -282,15 +282,16 @@ class SubtitleAnalyzerAdapter:
|
|||||||
"temperature": 1.0
|
"temperature": 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
def generate_narration_script(self, short_name: str, plot_analysis: str, temperature: float = 0.7) -> Dict[str, Any]:
|
def generate_narration_script(self, short_name: str, plot_analysis: str, subtitle_content: str = "", temperature: float = 0.7) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
生成解说文案 - 兼容原有接口
|
生成解说文案 - 兼容原有接口
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
short_name: 短剧名称
|
short_name: 短剧名称
|
||||||
plot_analysis: 剧情分析内容
|
plot_analysis: 剧情分析内容
|
||||||
|
subtitle_content: 原始字幕内容,用于提供准确的时间戳信息
|
||||||
temperature: 生成温度
|
temperature: 生成温度
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
生成结果字典
|
生成结果字典
|
||||||
"""
|
"""
|
||||||
@ -301,7 +302,8 @@ class SubtitleAnalyzerAdapter:
|
|||||||
name="script_generation",
|
name="script_generation",
|
||||||
parameters={
|
parameters={
|
||||||
"drama_name": short_name,
|
"drama_name": short_name,
|
||||||
"plot_analysis": plot_analysis
|
"plot_analysis": plot_analysis,
|
||||||
|
"subtitle_content": subtitle_content
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -207,7 +207,7 @@ class GeminiTextProvider(TextModelProvider):
|
|||||||
prompt: str,
|
prompt: str,
|
||||||
system_prompt: Optional[str] = None,
|
system_prompt: Optional[str] = None,
|
||||||
temperature: float = 1.0,
|
temperature: float = 1.0,
|
||||||
max_tokens: Optional[int] = None,
|
max_tokens: Optional[int] = 30000,
|
||||||
response_format: Optional[str] = None,
|
response_format: Optional[str] = None,
|
||||||
**kwargs) -> str:
|
**kwargs) -> str:
|
||||||
"""
|
"""
|
||||||
@ -231,7 +231,7 @@ class GeminiTextProvider(TextModelProvider):
|
|||||||
"temperature": temperature,
|
"temperature": temperature,
|
||||||
"topK": 40,
|
"topK": 40,
|
||||||
"topP": 0.95,
|
"topP": 0.95,
|
||||||
"maxOutputTokens": max_tokens or 4000,
|
"maxOutputTokens": 60000,
|
||||||
"candidateCount": 1
|
"candidateCount": 1
|
||||||
},
|
},
|
||||||
"safetySettings": [
|
"safetySettings": [
|
||||||
@ -269,7 +269,7 @@ class GeminiTextProvider(TextModelProvider):
|
|||||||
# payload["generationConfig"]["stopSequences"] = ["```", "注意", "说明"]
|
# payload["generationConfig"]["stopSequences"] = ["```", "注意", "说明"]
|
||||||
|
|
||||||
# 记录请求信息
|
# 记录请求信息
|
||||||
logger.debug(f"Gemini文本生成请求: {payload}")
|
# logger.debug(f"Gemini文本生成请求: {payload}")
|
||||||
|
|
||||||
# 发送API请求
|
# 发送API请求
|
||||||
response_data = await self._make_api_call(payload)
|
response_data = await self._make_api_call(payload)
|
||||||
|
|||||||
@ -24,19 +24,25 @@ class ScriptGenerationPrompt(ParameterizedPrompt):
|
|||||||
model_type=ModelType.TEXT,
|
model_type=ModelType.TEXT,
|
||||||
output_format=OutputFormat.JSON,
|
output_format=OutputFormat.JSON,
|
||||||
tags=["短剧", "解说脚本", "文案生成", "原声片段"],
|
tags=["短剧", "解说脚本", "文案生成", "原声片段"],
|
||||||
parameters=["drama_name", "plot_analysis"]
|
parameters=["drama_name", "plot_analysis", "subtitle_content"]
|
||||||
)
|
)
|
||||||
super().__init__(metadata, required_parameters=["drama_name", "plot_analysis"])
|
super().__init__(metadata, required_parameters=["drama_name", "plot_analysis"])
|
||||||
|
|
||||||
self._system_prompt = "你是一位专业的短视频解说脚本撰写专家。你必须严格按照JSON格式输出,不能包含任何其他文字、说明或代码块标记。"
|
self._system_prompt = "你是一位专业的短视频解说脚本撰写专家。你必须严格按照JSON格式输出,不能包含任何其他文字、说明或代码块标记。"
|
||||||
|
|
||||||
def get_template(self) -> str:
|
def get_template(self) -> str:
|
||||||
return """我是一个影视解说up主,需要为我的粉丝讲解短剧《${drama_name}》的剧情,目前正在解说剧情,希望能让粉丝通过我的解说了解剧情,并且产生继续观看的兴趣,请生成一篇解说脚本,包含解说文案,以及穿插原声的片段,下面<plot>中的内容是短剧的剧情概述:
|
return """我是一个影视解说up主,需要为我的粉丝讲解短剧《${drama_name}》的剧情,目前正在解说剧情,希望能让粉丝通过我的解说了解剧情,并且产生继续观看的兴趣,请生成一篇解说脚本,包含解说文案,以及穿插原声的片段。
|
||||||
|
|
||||||
|
下面<plot>中的内容是短剧的剧情概述:
|
||||||
<plot>
|
<plot>
|
||||||
${plot_analysis}
|
${plot_analysis}
|
||||||
</plot>
|
</plot>
|
||||||
|
|
||||||
|
下面<subtitles>中的内容是短剧的原始字幕(包含准确的时间戳信息):
|
||||||
|
<subtitles>
|
||||||
|
${subtitle_content}
|
||||||
|
</subtitles>
|
||||||
|
|
||||||
请严格按照以下JSON格式输出,不要添加任何其他文字、说明或代码块标记:
|
请严格按照以下JSON格式输出,不要添加任何其他文字、说明或代码块标记:
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -57,7 +63,9 @@ ${plot_analysis}
|
|||||||
3. 解说文案需包含角色微表情、动作细节、场景氛围的描写,每段80-150字
|
3. 解说文案需包含角色微表情、动作细节、场景氛围的描写,每段80-150字
|
||||||
4. 通过细节关联普遍情感(如遗憾、和解、成长),避免直白抒情
|
4. 通过细节关联普遍情感(如遗憾、和解、成长),避免直白抒情
|
||||||
5. 所有细节严格源自<plot>,可对角色行为进行合理心理推导但不虚构剧情
|
5. 所有细节严格源自<plot>,可对角色行为进行合理心理推导但不虚构剧情
|
||||||
6. 时间戳从<plot>摘取,可根据解说内容拆分原时间片段(如将10秒拆分为两个5秒)
|
6. **时间戳必须严格基于<subtitles>中的原始时间戳**,确保与视频画面精确匹配
|
||||||
7. 解说与原片穿插比例控制在7:3,关键情绪点保留原片原声
|
7. 解说与原片穿插比例控制在7:3,关键情绪点保留原片原声
|
||||||
8. 严禁跳脱剧情发展顺序,所有描述必须符合“先发生A,再发生B,A导致B”的逻辑
|
8. 严禁跳脱剧情发展顺序,所有描述必须符合“先发生A,再发生B,A导致B”的逻辑
|
||||||
9. 强化流程感,让观众清晰感知剧情推进的先后顺序"""
|
9. 强化流程感,让观众清晰感知剧情推进的先后顺序
|
||||||
|
10. 可根据解说内容需要拆分原时间片段(如将10秒拆分为两个5秒),但必须保持时间连续性
|
||||||
|
11. **确保每个解说片段的时间戳都能在原始字幕中找到对应的时间范围**"""
|
||||||
|
|||||||
202
docs/short_drama_narration_optimization.md
Normal file
202
docs/short_drama_narration_optimization.md
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# 短剧解说功能优化说明
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本次优化解决了短剧解说功能中原始字幕信息缺失的问题,确保生成的解说文案与视频时间戳正确匹配。
|
||||||
|
|
||||||
|
## 问题分析
|
||||||
|
|
||||||
|
### 原始问题
|
||||||
|
1. **参数化调用错误**:`SubtitleAnalyzer` 在获取 `PlotAnalysisPrompt` 时传入空参数字典,导致模板中的占位符无法被正确替换
|
||||||
|
2. **数据传递链断裂**:解说脚本生成阶段无法直接访问原始字幕的时间戳信息
|
||||||
|
3. **时间戳信息丢失**:生成的解说文案与视频画面时间戳不匹配
|
||||||
|
|
||||||
|
### 根本原因
|
||||||
|
- 提示词模板期望参数化方式接收字幕内容,但实际使用了简单的字符串拼接
|
||||||
|
- 解说脚本生成时只能访问剧情分析结果,无法获取原始字幕的准确时间戳
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
### 1. 修复参数化调用问题
|
||||||
|
|
||||||
|
**修改文件**: `app/services/SDE/short_drama_explanation.py`
|
||||||
|
|
||||||
|
**修改内容**:
|
||||||
|
```python
|
||||||
|
# 修改前
|
||||||
|
self.prompt_template = PromptManager.get_prompt(
|
||||||
|
category="short_drama_narration",
|
||||||
|
name="plot_analysis",
|
||||||
|
parameters={} # 空参数字典
|
||||||
|
)
|
||||||
|
prompt = f"{self.prompt_template}\n\n{subtitle_content}" # 字符串拼接
|
||||||
|
|
||||||
|
# 修改后
|
||||||
|
if self.custom_prompt:
|
||||||
|
prompt = f"{self.custom_prompt}\n\n{subtitle_content}"
|
||||||
|
else:
|
||||||
|
prompt = PromptManager.get_prompt(
|
||||||
|
category="short_drama_narration",
|
||||||
|
name="plot_analysis",
|
||||||
|
parameters={"subtitle_content": subtitle_content} # 正确传入参数
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 增强解说脚本生成的数据访问
|
||||||
|
|
||||||
|
**修改文件**: `app/services/prompts/short_drama_narration/script_generation.py`
|
||||||
|
|
||||||
|
**修改内容**:
|
||||||
|
```python
|
||||||
|
# 添加 subtitle_content 参数支持
|
||||||
|
parameters=["drama_name", "plot_analysis", "subtitle_content"]
|
||||||
|
|
||||||
|
# 优化提示词模板,添加原始字幕信息
|
||||||
|
template = """
|
||||||
|
下面<plot>中的内容是短剧的剧情概述:
|
||||||
|
<plot>
|
||||||
|
${plot_analysis}
|
||||||
|
</plot>
|
||||||
|
|
||||||
|
下面<subtitles>中的内容是短剧的原始字幕(包含准确的时间戳信息):
|
||||||
|
<subtitles>
|
||||||
|
${subtitle_content}
|
||||||
|
</subtitles>
|
||||||
|
|
||||||
|
重要要求:
|
||||||
|
6. **时间戳必须严格基于<subtitles>中的原始时间戳**,确保与视频画面精确匹配
|
||||||
|
11. **确保每个解说片段的时间戳都能在原始字幕中找到对应的时间范围**
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 更新方法签名和调用
|
||||||
|
|
||||||
|
**修改内容**:
|
||||||
|
```python
|
||||||
|
# 方法签名更新
|
||||||
|
def generate_narration_script(
|
||||||
|
self,
|
||||||
|
short_name: str,
|
||||||
|
plot_analysis: str,
|
||||||
|
subtitle_content: str = "", # 新增参数
|
||||||
|
temperature: float = 0.7
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
|
||||||
|
# 调用时传入原始字幕内容
|
||||||
|
prompt = PromptManager.get_prompt(
|
||||||
|
category="short_drama_narration",
|
||||||
|
name="script_generation",
|
||||||
|
parameters={
|
||||||
|
"drama_name": short_name,
|
||||||
|
"plot_analysis": plot_analysis,
|
||||||
|
"subtitle_content": subtitle_content # 传入原始字幕
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.services.SDE.short_drama_explanation import analyze_subtitle, generate_narration_script
|
||||||
|
|
||||||
|
# 1. 分析字幕
|
||||||
|
analysis_result = analyze_subtitle(
|
||||||
|
subtitle_file_path="path/to/subtitle.srt",
|
||||||
|
api_key="your_api_key",
|
||||||
|
model="your_model",
|
||||||
|
base_url="your_base_url"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. 读取原始字幕内容
|
||||||
|
with open("path/to/subtitle.srt", 'r', encoding='utf-8') as f:
|
||||||
|
subtitle_content = f.read()
|
||||||
|
|
||||||
|
# 3. 生成解说脚本(现在包含原始字幕信息)
|
||||||
|
narration_result = generate_narration_script(
|
||||||
|
short_name="短剧名称",
|
||||||
|
plot_analysis=analysis_result["analysis"],
|
||||||
|
subtitle_content=subtitle_content, # 传入原始字幕内容
|
||||||
|
api_key="your_api_key",
|
||||||
|
model="your_model",
|
||||||
|
base_url="your_base_url"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整示例
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 完整的短剧解说生成流程
|
||||||
|
subtitle_path = "path/to/your/subtitle.srt"
|
||||||
|
|
||||||
|
# 步骤1:分析字幕
|
||||||
|
analysis_result = analyze_subtitle(
|
||||||
|
subtitle_file_path=subtitle_path,
|
||||||
|
api_key="your_api_key",
|
||||||
|
model="gemini-2.0-flash",
|
||||||
|
base_url="https://api.narratoai.cn/v1/chat/completions",
|
||||||
|
save_result=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if analysis_result["status"] == "success":
|
||||||
|
# 步骤2:读取原始字幕内容
|
||||||
|
with open(subtitle_path, 'r', encoding='utf-8') as f:
|
||||||
|
subtitle_content = f.read()
|
||||||
|
|
||||||
|
# 步骤3:生成解说脚本
|
||||||
|
narration_result = generate_narration_script(
|
||||||
|
short_name="我的短剧",
|
||||||
|
plot_analysis=analysis_result["analysis"],
|
||||||
|
subtitle_content=subtitle_content, # 关键:传入原始字幕
|
||||||
|
api_key="your_api_key",
|
||||||
|
model="gemini-2.0-flash",
|
||||||
|
base_url="https://api.narratoai.cn/v1/chat/completions",
|
||||||
|
save_result=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if narration_result["status"] == "success":
|
||||||
|
print("解说脚本生成成功!")
|
||||||
|
print(narration_result["narration_script"])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 优化效果
|
||||||
|
|
||||||
|
### 修改前
|
||||||
|
- ❌ 字幕内容无法正确嵌入提示词
|
||||||
|
- ❌ 解说脚本生成时缺少原始时间戳信息
|
||||||
|
- ❌ 生成的时间戳可能不准确或缺失
|
||||||
|
|
||||||
|
### 修改后
|
||||||
|
- ✅ 字幕内容正确嵌入到剧情分析提示词中
|
||||||
|
- ✅ 解说脚本生成时可访问完整的原始字幕信息
|
||||||
|
- ✅ 生成的解说文案时间戳与视频画面精确匹配
|
||||||
|
- ✅ 保持时间连续性和逻辑顺序
|
||||||
|
- ✅ 支持时间片段的合理拆分
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
运行测试脚本验证修改效果:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 test_short_drama_narration.py
|
||||||
|
```
|
||||||
|
|
||||||
|
测试覆盖:
|
||||||
|
1. ✅ 剧情分析提示词参数化功能
|
||||||
|
2. ✅ 解说脚本生成提示词参数化功能
|
||||||
|
3. ✅ SubtitleAnalyzer集成功能
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **向后兼容性**:修改保持了原有API的向后兼容性
|
||||||
|
2. **参数传递**:确保在调用 `generate_narration_script` 时传入 `subtitle_content` 参数
|
||||||
|
3. **时间戳准确性**:生成的解说文案时间戳现在严格基于原始字幕
|
||||||
|
4. **模块化设计**:保持了提示词管理系统的模块化架构
|
||||||
|
|
||||||
|
## 相关文件
|
||||||
|
|
||||||
|
- `app/services/SDE/short_drama_explanation.py` - 主要功能实现
|
||||||
|
- `app/services/prompts/short_drama_narration/plot_analysis.py` - 剧情分析提示词
|
||||||
|
- `app/services/prompts/short_drama_narration/script_generation.py` - 解说脚本生成提示词
|
||||||
|
- `test_short_drama_narration.py` - 测试脚本
|
||||||
170
docs/webui_bug_fix_summary.md
Normal file
170
docs/webui_bug_fix_summary.md
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
# WebUI短剧解说功能Bug修复总结
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
在运行WebUI的短剧解说功能时,出现以下错误:
|
||||||
|
|
||||||
|
```
|
||||||
|
2025-07-11 22:15:29 | ERROR | "./app/services/prompts/manager.py:59": get_prompt - 提示词渲染失败: short_drama_narration.script_generation - 模板渲染失败 'script_generation': 缺少必需参数 (缺少参数: subtitle_content)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 根本原因
|
||||||
|
|
||||||
|
在之前的优化中,我们修改了 `ScriptGenerationPrompt` 类,添加了 `subtitle_content` 作为必需参数,但是在 `app/services/llm/migration_adapter.py` 中的 `SubtitleAnalyzerAdapter.generate_narration_script` 方法没有相应更新,导致调用提示词时缺少必需的参数。
|
||||||
|
|
||||||
|
## 修复内容
|
||||||
|
|
||||||
|
### 1. 修复 migration_adapter.py
|
||||||
|
|
||||||
|
**文件**: `app/services/llm/migration_adapter.py`
|
||||||
|
|
||||||
|
**修改内容**:
|
||||||
|
```python
|
||||||
|
# 修改前
|
||||||
|
def generate_narration_script(self, short_name: str, plot_analysis: str, temperature: float = 0.7) -> Dict[str, Any]:
|
||||||
|
|
||||||
|
# 修改后
|
||||||
|
def generate_narration_script(self, short_name: str, plot_analysis: str, subtitle_content: str = "", temperature: float = 0.7) -> Dict[str, Any]:
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数传递修复**:
|
||||||
|
```python
|
||||||
|
# 修改前
|
||||||
|
prompt = PromptManager.get_prompt(
|
||||||
|
category="short_drama_narration",
|
||||||
|
name="script_generation",
|
||||||
|
parameters={
|
||||||
|
"drama_name": short_name,
|
||||||
|
"plot_analysis": plot_analysis
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 修改后
|
||||||
|
prompt = PromptManager.get_prompt(
|
||||||
|
category="short_drama_narration",
|
||||||
|
name="script_generation",
|
||||||
|
parameters={
|
||||||
|
"drama_name": short_name,
|
||||||
|
"plot_analysis": plot_analysis,
|
||||||
|
"subtitle_content": subtitle_content # 添加缺失的参数
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 修复 WebUI 调用代码
|
||||||
|
|
||||||
|
**文件**: `webui/tools/generate_short_summary.py`
|
||||||
|
|
||||||
|
**修改内容**:
|
||||||
|
|
||||||
|
1. **确保字幕内容在所有情况下都可用**:
|
||||||
|
```python
|
||||||
|
# 修改前:字幕内容只在新LLM服务架构中读取
|
||||||
|
try:
|
||||||
|
analyzer = SubtitleAnalyzerAdapter(...)
|
||||||
|
with open(subtitle_path, 'r', encoding='utf-8') as f:
|
||||||
|
subtitle_content = f.read()
|
||||||
|
analysis_result = analyzer.analyze_subtitle(subtitle_content)
|
||||||
|
except Exception as e:
|
||||||
|
# 回退时没有subtitle_content变量
|
||||||
|
|
||||||
|
# 修改后:无论使用哪种实现都先读取字幕内容
|
||||||
|
with open(subtitle_path, 'r', encoding='utf-8') as f:
|
||||||
|
subtitle_content = f.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
analyzer = SubtitleAnalyzerAdapter(...)
|
||||||
|
analysis_result = analyzer.analyze_subtitle(subtitle_content)
|
||||||
|
except Exception as e:
|
||||||
|
# 回退时subtitle_content变量仍然可用
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **修复新LLM服务架构的调用**:
|
||||||
|
```python
|
||||||
|
# 修改前
|
||||||
|
narration_result = analyzer.generate_narration_script(
|
||||||
|
short_name=video_theme,
|
||||||
|
plot_analysis=analysis_result["analysis"],
|
||||||
|
temperature=temperature
|
||||||
|
)
|
||||||
|
|
||||||
|
# 修改后
|
||||||
|
narration_result = analyzer.generate_narration_script(
|
||||||
|
short_name=video_theme,
|
||||||
|
plot_analysis=analysis_result["analysis"],
|
||||||
|
subtitle_content=subtitle_content, # 添加字幕内容参数
|
||||||
|
temperature=temperature
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **修复回退到旧实现的调用**:
|
||||||
|
```python
|
||||||
|
# 修改前
|
||||||
|
narration_result = generate_narration_script(
|
||||||
|
short_name=video_theme,
|
||||||
|
plot_analysis=analysis_result["analysis"],
|
||||||
|
api_key=text_api_key,
|
||||||
|
model=text_model,
|
||||||
|
base_url=text_base_url,
|
||||||
|
save_result=True,
|
||||||
|
temperature=temperature,
|
||||||
|
provider=text_provider
|
||||||
|
)
|
||||||
|
|
||||||
|
# 修改后
|
||||||
|
narration_result = generate_narration_script(
|
||||||
|
short_name=video_theme,
|
||||||
|
plot_analysis=analysis_result["analysis"],
|
||||||
|
subtitle_content=subtitle_content, # 添加字幕内容参数
|
||||||
|
api_key=text_api_key,
|
||||||
|
model=text_model,
|
||||||
|
base_url=text_base_url,
|
||||||
|
save_result=True,
|
||||||
|
temperature=temperature,
|
||||||
|
provider=text_provider
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
创建并运行了测试脚本,验证了以下内容:
|
||||||
|
|
||||||
|
1. ✅ 提示词参数化功能正常
|
||||||
|
2. ✅ 所有必需参数都正确传递
|
||||||
|
3. ✅ 方法签名包含所有必需参数
|
||||||
|
4. ✅ 字幕内容正确嵌入到提示词中
|
||||||
|
|
||||||
|
## 修复效果
|
||||||
|
|
||||||
|
**修复前**:
|
||||||
|
- ❌ WebUI运行时出现"缺少必需参数"错误
|
||||||
|
- ❌ 无法生成解说脚本
|
||||||
|
- ❌ 用户体验中断
|
||||||
|
|
||||||
|
**修复后**:
|
||||||
|
- ✅ WebUI正常运行,无参数错误
|
||||||
|
- ✅ 解说脚本生成功能正常
|
||||||
|
- ✅ 原始字幕内容正确传递到提示词
|
||||||
|
- ✅ 生成的解说文案基于准确的时间戳信息
|
||||||
|
|
||||||
|
## 相关文件
|
||||||
|
|
||||||
|
- `app/services/llm/migration_adapter.py` - 修复适配器方法签名和参数传递
|
||||||
|
- `webui/tools/generate_short_summary.py` - 修复WebUI调用代码
|
||||||
|
- `app/services/prompts/short_drama_narration/script_generation.py` - 提示词模板(之前已优化)
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **向后兼容性**: 修改保持了API的向后兼容性,`subtitle_content` 参数有默认值
|
||||||
|
2. **错误处理**: 确保在所有代码路径中都能获取到字幕内容
|
||||||
|
3. **一致性**: 新旧实现都使用相同的参数传递方式
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
这次修复解决了WebUI中短剧解说功能的关键bug,确保了:
|
||||||
|
- 提示词系统的参数完整性
|
||||||
|
- WebUI功能的正常运行
|
||||||
|
- 用户体验的连续性
|
||||||
|
- 代码的健壮性和一致性
|
||||||
|
|
||||||
|
现在用户可以正常使用WebUI的短剧解说功能,生成基于准确时间戳的高质量解说文案。
|
||||||
@ -172,15 +172,15 @@ def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperatu
|
|||||||
text_model = config.app.get(f'text_{text_provider}_model_name')
|
text_model = config.app.get(f'text_{text_provider}_model_name')
|
||||||
text_base_url = config.app.get(f'text_{text_provider}_base_url')
|
text_base_url = config.app.get(f'text_{text_provider}_base_url')
|
||||||
|
|
||||||
|
# 读取字幕文件内容(无论使用哪种实现都需要)
|
||||||
|
with open(subtitle_path, 'r', encoding='utf-8') as f:
|
||||||
|
subtitle_content = f.read()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 优先使用新的LLM服务架构
|
# 优先使用新的LLM服务架构
|
||||||
logger.info("使用新的LLM服务架构进行字幕分析")
|
logger.info("使用新的LLM服务架构进行字幕分析")
|
||||||
analyzer = SubtitleAnalyzerAdapter(text_api_key, text_model, text_base_url, text_provider)
|
analyzer = SubtitleAnalyzerAdapter(text_api_key, text_model, text_base_url, text_provider)
|
||||||
|
|
||||||
# 读取字幕文件
|
|
||||||
with open(subtitle_path, 'r', encoding='utf-8') as f:
|
|
||||||
subtitle_content = f.read()
|
|
||||||
|
|
||||||
analysis_result = analyzer.analyze_subtitle(subtitle_content)
|
analysis_result = analyzer.analyze_subtitle(subtitle_content)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -209,6 +209,7 @@ def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperatu
|
|||||||
narration_result = analyzer.generate_narration_script(
|
narration_result = analyzer.generate_narration_script(
|
||||||
short_name=video_theme,
|
short_name=video_theme,
|
||||||
plot_analysis=analysis_result["analysis"],
|
plot_analysis=analysis_result["analysis"],
|
||||||
|
subtitle_content=subtitle_content, # 传递原始字幕内容
|
||||||
temperature=temperature
|
temperature=temperature
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -217,6 +218,7 @@ def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperatu
|
|||||||
narration_result = generate_narration_script(
|
narration_result = generate_narration_script(
|
||||||
short_name=video_theme,
|
short_name=video_theme,
|
||||||
plot_analysis=analysis_result["analysis"],
|
plot_analysis=analysis_result["analysis"],
|
||||||
|
subtitle_content=subtitle_content, # 传递原始字幕内容
|
||||||
api_key=text_api_key,
|
api_key=text_api_key,
|
||||||
model=text_model,
|
model=text_model,
|
||||||
base_url=text_base_url,
|
base_url=text_base_url,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user