diff --git a/app/services/SDE/short_drama_explanation.py b/app/services/SDE/short_drama_explanation.py
index 5d85679..c77dc8c 100644
--- a/app/services/SDE/short_drama_explanation.py
+++ b/app/services/SDE/short_drama_explanation.py
@@ -409,6 +409,7 @@ class SubtitleAnalyzer:
temperature: float = 0.7,
narration_language: str = "简体中文(中国)",
drama_genre: str = "逆袭/复仇",
+ narration_word_count: int = 500,
) -> Dict[str, Any]:
"""生成供用户审核修改的解说正文。"""
try:
@@ -420,6 +421,7 @@ class SubtitleAnalyzer:
"plot_analysis": plot_analysis,
"subtitle_content": subtitle_content,
"narration_language": narration_language,
+ "narration_word_count": int(narration_word_count),
},
)
return self._generate_plain_text(prompt, system_prompt, temperature)
@@ -964,6 +966,7 @@ def generate_narration_copy(
narration_language: str = "简体中文(中国)",
drama_genre: str = "逆袭/复仇",
prompt_category: str = "short_drama_narration",
+ narration_word_count: int = 500,
) -> Dict[str, Any]:
"""生成可供用户审核修改的解说正文。"""
analyzer = SubtitleAnalyzer(
@@ -982,6 +985,7 @@ def generate_narration_copy(
temperature=temperature,
narration_language=narration_language,
drama_genre=drama_genre,
+ narration_word_count=narration_word_count,
)
diff --git a/app/services/llm/migration_adapter.py b/app/services/llm/migration_adapter.py
index aec7ab5..809725c 100644
--- a/app/services/llm/migration_adapter.py
+++ b/app/services/llm/migration_adapter.py
@@ -292,6 +292,7 @@ class SubtitleAnalyzerAdapter:
temperature: float = 0.7,
narration_language: str = "简体中文(中国)",
drama_genre: str = "逆袭/复仇",
+ narration_word_count: int = 500,
) -> Dict[str, Any]:
"""Generate editable narration copy before timeline matching."""
try:
@@ -303,6 +304,7 @@ class SubtitleAnalyzerAdapter:
"plot_analysis": plot_analysis,
"subtitle_content": subtitle_content,
"narration_language": narration_language,
+ "narration_word_count": int(narration_word_count),
},
)
narration_copy = self._generate_plain_text(prompt, system_prompt, temperature)
diff --git a/app/services/llm/test_subtitle_adapter_pipeline_unittest.py b/app/services/llm/test_subtitle_adapter_pipeline_unittest.py
index 77087e6..7d390b5 100644
--- a/app/services/llm/test_subtitle_adapter_pipeline_unittest.py
+++ b/app/services/llm/test_subtitle_adapter_pipeline_unittest.py
@@ -24,11 +24,14 @@ class SubtitleAnalyzerAdapterPipelineTests(unittest.TestCase):
temperature=0.7,
narration_language="简体中文(中国)",
drama_genre="家庭伦理",
+ narration_word_count=800,
)
self.assertEqual("success", result["status"])
self.assertIn("反击", result["narration_copy"])
self.assertIn("家庭伦理", call.call_args.kwargs["prompt"])
+ self.assertIn("800", call.call_args.kwargs["prompt"])
+ self.assertNotIn("300-650", call.call_args.kwargs["prompt"])
self.assertNotIn("response_format", call.call_args.kwargs)
def test_generate_narration_copy_can_use_film_tv_prompt_category(self):
@@ -49,11 +52,14 @@ class SubtitleAnalyzerAdapterPipelineTests(unittest.TestCase):
temperature=0.7,
narration_language="简体中文(中国)",
drama_genre="悬疑/犯罪",
+ narration_word_count=1200,
)
self.assertEqual("success", result["status"])
self.assertIn("影视解说正文创作任务", call.call_args.kwargs["prompt"])
self.assertIn("用户选择的影视类型", call.call_args.kwargs["prompt"])
+ self.assertIn("1200", call.call_args.kwargs["prompt"])
+ self.assertNotIn("350-750", call.call_args.kwargs["prompt"])
self.assertNotIn("短剧解说正文创作任务", call.call_args.kwargs["prompt"])
def test_film_tv_script_prompts_exclude_intro_outro_and_ads(self):
diff --git a/app/services/prompts/film_tv_narration/narration_copy.py b/app/services/prompts/film_tv_narration/narration_copy.py
index 7c6182a..1304a7e 100644
--- a/app/services/prompts/film_tv_narration/narration_copy.py
+++ b/app/services/prompts/film_tv_narration/narration_copy.py
@@ -22,7 +22,14 @@ class NarrationCopyPrompt(ParameterizedPrompt):
model_type=ModelType.TEXT,
output_format=OutputFormat.TEXT,
tags=["影视", "解说文案", "电影解说", "剧情承接", "用户审核"],
- parameters=["drama_name", "drama_genre", "plot_analysis", "subtitle_content", "narration_language"],
+ parameters=[
+ "drama_name",
+ "drama_genre",
+ "plot_analysis",
+ "subtitle_content",
+ "narration_language",
+ "narration_word_count",
+ ],
)
super().__init__(metadata, required_parameters=["drama_name", "plot_analysis", "subtitle_content"])
@@ -57,6 +64,11 @@ ${narration_language}
${drama_genre}
+## 用户要求的文案字数
+
+${narration_word_count}
+
+
## 类型写作规则
必须按用户选择的影视类型调整表达重点,不要自行改判类型:
- 剧情/情感:突出人物选择、关系裂痕、命运压力和情绪余波。
@@ -81,7 +93,7 @@ ${drama_genre}
4. 每句话只表达一个信息点,适合后续按句匹配画面。
5. 句子尽量短,单句优先 15-35 字;信息复杂时拆成多句。
6. 每 2-3 句要有明确承接,让观众知道为什么从上一幕来到下一幕。
-7. 总长度控制在 350-750 字;短素材取下限,长素材取上限。
+7. 总长度以 ${narration_word_count} 字为目标,允许上下浮动 10%;中日韩语言按非空白字符计数,其他语言按单词计数。不得再套用固定长度区间。
8. 不要使用编号、项目符号、章节标题或括号说明。
## 输出要求
diff --git a/app/services/prompts/short_drama_narration/narration_copy.py b/app/services/prompts/short_drama_narration/narration_copy.py
index 362e0fd..3678bc0 100644
--- a/app/services/prompts/short_drama_narration/narration_copy.py
+++ b/app/services/prompts/short_drama_narration/narration_copy.py
@@ -22,7 +22,14 @@ class NarrationCopyPrompt(ParameterizedPrompt):
model_type=ModelType.TEXT,
output_format=OutputFormat.TEXT,
tags=["短剧", "解说文案", "爆款开头", "叙事连续性", "用户审核"],
- parameters=["drama_name", "drama_genre", "plot_analysis", "subtitle_content", "narration_language"],
+ parameters=[
+ "drama_name",
+ "drama_genre",
+ "plot_analysis",
+ "subtitle_content",
+ "narration_language",
+ "narration_word_count",
+ ],
)
super().__init__(metadata, required_parameters=["drama_name", "plot_analysis", "subtitle_content"])
@@ -57,6 +64,11 @@ ${narration_language}
${drama_genre}
+## 用户要求的文案字数
+
+${narration_word_count}
+
+
## 类型写作规则
必须按用户选择的短剧类型调整表达重点,不要自行改判类型:
- 霸总/甜宠:突出误会、身份差、暧昧拉扯、守护感和情绪反差。
@@ -81,7 +93,7 @@ ${drama_genre}
4. 每句话只表达一个信息点,适合后续按句匹配画面。
5. 句子尽量短,单句优先 15-35 字;信息复杂时拆成多句。
6. 每 2-3 句要有明确因果承接,让观众知道为什么从上一幕来到下一幕。
-7. 总长度控制在 300-650 字;短素材取下限,长素材取上限。
+7. 总长度以 ${narration_word_count} 字为目标,允许上下浮动 10%;中日韩语言按非空白字符计数,其他语言按单词计数。不得再套用固定长度区间。
8. 不要使用编号、项目符号、章节标题或括号说明。
## 输出要求
diff --git a/project_version b/project_version
index 120f532..35864a9 100644
--- a/project_version
+++ b/project_version
@@ -1 +1 @@
-0.8.6
\ No newline at end of file
+0.8.7
\ No newline at end of file
diff --git a/webui/components/script_settings.py b/webui/components/script_settings.py
index 3f1b666..163d3cf 100644
--- a/webui/components/script_settings.py
+++ b/webui/components/script_settings.py
@@ -97,6 +97,9 @@ FILM_TV_TYPE_VALUES = {
"horror_thriller": "恐怖/惊悚",
}
SHORT_DRAMA_ORIGINAL_SOUND_RATIO_OPTIONS = list(range(0, 100, 10))
+DEFAULT_NARRATION_WORD_COUNT = 500
+MIN_NARRATION_WORD_COUNT = 100
+MAX_NARRATION_WORD_COUNT = 5000
SUMMARY_MODE_CONFIGS = {
MODE_FILM_SUMMARY: {
"mode_label_key": "Film TV Narration",
@@ -1574,6 +1577,7 @@ def render_script_buttons(tr, params):
type_option_key = _summary_state_key(summary_config, "type_option")
custom_type_key = _summary_state_key(summary_config, "custom_type")
original_sound_ratio_key = _summary_state_key(summary_config, "original_sound_ratio")
+ narration_word_count_key = _summary_state_key(summary_config, "narration_word_count")
language_option_key = _summary_state_key(summary_config, "narration_language_option")
custom_language_key = _summary_state_key(summary_config, "custom_narration_language")
narration_copy_key = _summary_state_key(summary_config, "narration_copy")
@@ -1592,7 +1596,7 @@ def render_script_buttons(tr, params):
config_col_widths = [1.15]
if show_custom_type:
config_col_widths.append(1.15)
- config_col_widths.extend([0.9, 1.15])
+ config_col_widths.extend([0.9, 0.9, 1.15])
if show_custom_language:
config_col_widths.append(1.15)
@@ -1623,6 +1627,16 @@ def render_script_buttons(tr, params):
key=original_sound_ratio_key,
)
config_col_index += 1
+ with config_cols[config_col_index]:
+ st.number_input(
+ tr("文案字数"),
+ min_value=MIN_NARRATION_WORD_COUNT,
+ max_value=MAX_NARRATION_WORD_COUNT,
+ value=DEFAULT_NARRATION_WORD_COUNT,
+ step=50,
+ key=narration_word_count_key,
+ )
+ config_col_index += 1
with config_cols[config_col_index]:
st.selectbox(
tr("解说语言"),
@@ -1663,6 +1677,7 @@ def render_script_buttons(tr, params):
type_option_key = _summary_state_key(summary_config, "type_option")
custom_type_key = _summary_state_key(summary_config, "custom_type")
original_sound_ratio_key = _summary_state_key(summary_config, "original_sound_ratio")
+ narration_word_count_key = _summary_state_key(summary_config, "narration_word_count")
language_option_key = _summary_state_key(summary_config, "narration_language_option")
custom_language_key = _summary_state_key(summary_config, "custom_narration_language")
narration_copy_key = _summary_state_key(summary_config, "narration_copy")
@@ -1674,6 +1689,9 @@ def render_script_buttons(tr, params):
narration_language = _resolve_summary_narration_language(summary_config)
drama_genre = _resolve_summary_type(summary_config)
original_sound_ratio = int(st.session_state.get(original_sound_ratio_key, 30))
+ narration_word_count = int(
+ st.session_state.get(narration_word_count_key, DEFAULT_NARRATION_WORD_COUNT)
+ )
if (
st.session_state.get(type_option_key) == "custom"
and not str(st.session_state.get(custom_type_key, '') or '').strip()
@@ -1720,6 +1738,7 @@ def render_script_buttons(tr, params):
video_paths=_selected_video_paths(),
narration_language=narration_language,
drama_genre=drama_genre,
+ narration_word_count=narration_word_count,
prompt_category=summary_config["prompt_category"],
search_keywords=summary_config["search_keywords"],
empty_title_message_key=summary_config["empty_title_message_key"],
diff --git a/webui/i18n/en.json b/webui/i18n/en.json
index b3b1a27..90f6e73 100644
--- a/webui/i18n/en.json
+++ b/webui/i18n/en.json
@@ -341,6 +341,7 @@
"影视类型": "Film/TV Type",
"自定义影视类型": "Custom Film/TV Type",
"原片占比": "Original Footage Ratio",
+ "文案字数": "Copy Length",
"例如:豪门虐恋": "For example: billionaire angst romance",
"例如:悬疑犯罪": "For example: suspense crime",
"请输入自定义短剧类型": "Please enter a custom short drama type",
diff --git a/webui/i18n/zh.json b/webui/i18n/zh.json
index bdf3fba..f6cf688 100644
--- a/webui/i18n/zh.json
+++ b/webui/i18n/zh.json
@@ -703,6 +703,7 @@
"影视类型": "影视类型",
"自定义影视类型": "自定义影视类型",
"原片占比": "原片占比",
+ "文案字数": "文案字数",
"例如:豪门虐恋": "例如:豪门虐恋",
"例如:悬疑犯罪": "例如:悬疑犯罪",
"请输入自定义短剧类型": "请输入自定义短剧类型",
diff --git a/webui/tools/generate_short_summary.py b/webui/tools/generate_short_summary.py
index df1bf54..86c240f 100644
--- a/webui/tools/generate_short_summary.py
+++ b/webui/tools/generate_short_summary.py
@@ -363,6 +363,7 @@ def generate_short_drama_narration_copy(
search_keywords: str = SHORT_DRAMA_SEARCH_KEYWORDS,
empty_title_message_key: str = "Please enter short drama name before web search",
web_search_context_description: str = "短剧名称、人物关系、剧情背景和公开剧情梗概",
+ narration_word_count: int = 500,
):
"""生成可由用户审核修改的短剧解说正文,不绑定时间戳。"""
subtitle_paths = _normalize_paths(subtitle_path)
@@ -422,6 +423,7 @@ def generate_short_drama_narration_copy(
temperature=temperature,
narration_language=narration_language,
drama_genre=drama_genre,
+ narration_word_count=narration_word_count,
)
except Exception as e:
logger.warning(f"使用新LLM服务生成文案失败,回退到旧实现: {str(e)}")
@@ -436,6 +438,7 @@ def generate_short_drama_narration_copy(
provider=text_provider,
narration_language=narration_language,
drama_genre=drama_genre,
+ narration_word_count=narration_word_count,
prompt_category=prompt_category,
)