feat(subtitle_analysis): 更新解说文案生成逻辑,增强字幕内容支持

在多个文件中重构了解说文案生成的实现,新增对原始字幕内容的支持,以提供准确的时间戳信息。更新了相关参数和提示词模板,优化了生成逻辑,提升了内容的准确性和用户体验。同时,注释部分进行了清理,去除了调试信息的输出。
This commit is contained in:
linyq 2025-07-11 23:25:44 +08:00
parent c61462d706
commit e3a5e34c78
8 changed files with 428 additions and 33 deletions

View File

@ -49,16 +49,8 @@ class SubtitleAnalyzer:
self.temperature = temperature
self.provider = provider or self._detect_provider()
# 设置提示词模板
if custom_prompt:
self.prompt_template = custom_prompt
else:
# 使用新的提示词管理系统
self.prompt_template = PromptManager.get_prompt(
category="short_drama_narration",
name="plot_analysis",
parameters={}
)
# 设置自定义提示词(如果提供)
self.custom_prompt = custom_prompt
# 根据提供商类型确定是否为原生Gemini
self.is_native_gemini = self.provider.lower() == 'gemini'
@ -95,7 +87,16 @@ class SubtitleAnalyzer:
"""
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:
# 使用原生Gemini API格式
@ -127,7 +128,7 @@ class SubtitleAnalyzer:
"temperature": self.temperature,
"topK": 40,
"topP": 0.95,
"maxOutputTokens": 4000,
"maxOutputTokens": 64000,
"candidateCount": 1
},
"safetySettings": [
@ -356,13 +357,14 @@ class SubtitleAnalyzer:
logger.error(f"保存分析结果时发生错误: {str(e)}")
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:
short_name: 短剧名称
plot_analysis: 剧情分析内容
subtitle_content: 原始字幕内容用于提供准确的时间戳信息
temperature: 生成温度控制创造性默认0.7
Returns:
@ -375,7 +377,8 @@ class SubtitleAnalyzer:
name="script_generation",
parameters={
"drama_name": short_name,
"plot_analysis": plot_analysis
"plot_analysis": plot_analysis,
"subtitle_content": subtitle_content
}
)
@ -412,7 +415,7 @@ class SubtitleAnalyzer:
"temperature": temperature,
"topK": 40,
"topP": 0.95,
"maxOutputTokens": 4000,
"maxOutputTokens": 64000,
"candidateCount": 1,
"stopSequences": ["```", "注意", "说明"]
},
@ -675,6 +678,7 @@ def analyze_subtitle(
def generate_narration_script(
short_name: str = None,
plot_analysis: str = None,
subtitle_content: str = None,
api_key: Optional[str] = None,
model: Optional[str] = None,
base_url: Optional[str] = None,
@ -689,6 +693,7 @@ def generate_narration_script(
Args:
short_name: 短剧名称
plot_analysis: 剧情分析内容直接提供
subtitle_content: 原始字幕内容用于提供准确的时间戳信息
api_key: API密钥
model: 模型名称
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":
@ -740,10 +745,16 @@ if __name__ == '__main__':
print("字幕分析成功!")
print("分析结果:")
print(analysis_result["analysis"])
# 读取原始字幕内容用于解说脚本生成
with open(subtitle_path, 'r', encoding='utf-8') as f:
subtitle_content = f.read()
# 根据剧情生成解说文案
narration_result = generate_narration_script(
short_name="家里家外",
plot_analysis=analysis_result["analysis"],
subtitle_content=subtitle_content,
api_key=text_api_key,
model=text_model,
base_url=text_base_url,

View File

@ -251,7 +251,7 @@ def execute_ffmpeg_with_fallback(
bool: 是否成功
"""
try:
logger.debug(f"执行ffmpeg命令: {' '.join(cmd)}")
# logger.debug(f"执行ffmpeg命令: {' '.join(cmd)}")
# 在Windows系统上使用UTF-8编码处理输出
is_windows = os.name == 'nt'

View File

@ -282,15 +282,16 @@ class SubtitleAnalyzerAdapter:
"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:
short_name: 短剧名称
plot_analysis: 剧情分析内容
subtitle_content: 原始字幕内容用于提供准确的时间戳信息
temperature: 生成温度
Returns:
生成结果字典
"""
@ -301,7 +302,8 @@ class SubtitleAnalyzerAdapter:
name="script_generation",
parameters={
"drama_name": short_name,
"plot_analysis": plot_analysis
"plot_analysis": plot_analysis,
"subtitle_content": subtitle_content
}
)

View File

@ -207,7 +207,7 @@ class GeminiTextProvider(TextModelProvider):
prompt: str,
system_prompt: Optional[str] = None,
temperature: float = 1.0,
max_tokens: Optional[int] = None,
max_tokens: Optional[int] = 30000,
response_format: Optional[str] = None,
**kwargs) -> str:
"""
@ -231,7 +231,7 @@ class GeminiTextProvider(TextModelProvider):
"temperature": temperature,
"topK": 40,
"topP": 0.95,
"maxOutputTokens": max_tokens or 4000,
"maxOutputTokens": 60000,
"candidateCount": 1
},
"safetySettings": [
@ -269,7 +269,7 @@ class GeminiTextProvider(TextModelProvider):
# payload["generationConfig"]["stopSequences"] = ["```", "注意", "说明"]
# 记录请求信息
logger.debug(f"Gemini文本生成请求: {payload}")
# logger.debug(f"Gemini文本生成请求: {payload}")
# 发送API请求
response_data = await self._make_api_call(payload)

View File

@ -24,19 +24,25 @@ class ScriptGenerationPrompt(ParameterizedPrompt):
model_type=ModelType.TEXT,
output_format=OutputFormat.JSON,
tags=["短剧", "解说脚本", "文案生成", "原声片段"],
parameters=["drama_name", "plot_analysis"]
parameters=["drama_name", "plot_analysis", "subtitle_content"]
)
super().__init__(metadata, required_parameters=["drama_name", "plot_analysis"])
self._system_prompt = "你是一位专业的短视频解说脚本撰写专家。你必须严格按照JSON格式输出不能包含任何其他文字、说明或代码块标记。"
def get_template(self) -> str:
return """我是一个影视解说up主需要为我的粉丝讲解短剧《${drama_name}》的剧情,目前正在解说剧情,希望能让粉丝通过我的解说了解剧情,并且产生继续观看的兴趣,请生成一篇解说脚本,包含解说文案,以及穿插原声的片段,下面<plot>中的内容是短剧的剧情概述:
return """我是一个影视解说up主需要为我的粉丝讲解短剧《${drama_name}》的剧情,目前正在解说剧情,希望能让粉丝通过我的解说了解剧情,并且产生继续观看的兴趣,请生成一篇解说脚本,包含解说文案,以及穿插原声的片段
下面<plot>中的内容是短剧的剧情概述
<plot>
${plot_analysis}
</plot>
下面<subtitles>中的内容是短剧的原始字幕包含准确的时间戳信息
<subtitles>
${subtitle_content}
</subtitles>
请严格按照以下JSON格式输出不要添加任何其他文字说明或代码块标记
{
@ -57,7 +63,9 @@ ${plot_analysis}
3. 解说文案需包含角色微表情动作细节场景氛围的描写每段80-150
4. 通过细节关联普遍情感如遗憾和解成长避免直白抒情
5. 所有细节严格源自<plot>可对角色行为进行合理心理推导但不虚构剧情
6. 时间戳从<plot>摘取可根据解说内容拆分原时间片段如将10秒拆分为两个5秒
6. **时间戳必须严格基于<subtitles>中的原始时间戳**确保与视频画面精确匹配
7. 解说与原片穿插比例控制在7:3关键情绪点保留原片原声
8. 严禁跳脱剧情发展顺序所有描述必须符合先发生A再发生BA导致B的逻辑
9. 强化流程感让观众清晰感知剧情推进的先后顺序"""
9. 强化流程感让观众清晰感知剧情推进的先后顺序
10. 可根据解说内容需要拆分原时间片段如将10秒拆分为两个5秒但必须保持时间连续性
11. **确保每个解说片段的时间戳都能在原始字幕中找到对应的时间范围**"""

View 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` - 测试脚本

View 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的短剧解说功能生成基于准确时间戳的高质量解说文案。

View File

@ -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_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:
# 优先使用新的LLM服务架构
logger.info("使用新的LLM服务架构进行字幕分析")
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)
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(
short_name=video_theme,
plot_analysis=analysis_result["analysis"],
subtitle_content=subtitle_content, # 传递原始字幕内容
temperature=temperature
)
except Exception as e:
@ -217,6 +218,7 @@ def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperatu
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,