diff --git a/core/segment_tts.py b/core/segment_tts.py index 7a78555..01e0c13 100644 --- a/core/segment_tts.py +++ b/core/segment_tts.py @@ -119,6 +119,12 @@ def analyze(self, text: str, index: int, total: int) -> Tuple[str, float]: if re.search(rule["pattern"], text): candidates.append((rule["type"], rule["confidence"])) + # 位置规则兜底:最后一个 segment 如果没有被关键词/正则覆盖,默认 cta + if index == total - 1 and not any(t == "cta" for t, _ in candidates): + pos_rules = self.rules.get("position_rules", {}) + if "last" in pos_rules: + candidates.append((pos_rules["last"]["type"], pos_rules["last"]["confidence"])) + if not candidates: # 默认:中间段为 narrative if 0 < index < total - 1: @@ -127,6 +133,13 @@ def analyze(self, text: str, index: int, total: int) -> Tuple[str, float]: # 选择置信度最高的类型 best_type, best_conf = max(candidates, key=lambda x: x[1]) + + # Guard:非最后一个 segment 不允许被推断为 cta + # CTA 只能出现在视频结尾,避免中间段被误标为行动号召 + if best_type == "cta" and index < total - 1: + # 降级为 narrative,置信度中等 + return "narrative", 0.5 + return best_type, best_conf diff --git a/extensions/storyboard/storyboard_ai.py b/extensions/storyboard/storyboard_ai.py index b27d0c3..fe85f6c 100644 --- a/extensions/storyboard/storyboard_ai.py +++ b/extensions/storyboard/storyboard_ai.py @@ -102,6 +102,20 @@ def _infer_segment_type(self, text: str, index: int, total: int) -> str: if candidates: return max(candidates, key=lambda x: x[1])[0] + # 位置规则兜底 + pos_rules = self.rules.get("position_rules", {}) + if index == 0 and "first" in pos_rules: + candidates.append((pos_rules["first"]["type"], pos_rules["first"]["confidence"])) + if index == total - 1 and "last" in pos_rules: + candidates.append((pos_rules["last"]["type"], pos_rules["last"]["confidence"])) + + if candidates: + best_type, _ = max(candidates, key=lambda x: x[1]) + # Guard:非最后一个 segment 不允许被推断为 cta + if best_type == "cta" and index < total - 1: + return "narrative" + return best_type + # 简单回退 if index == 0: return "hook" diff --git a/rules/segment_types.json b/rules/segment_types.json index 9630de4..9808705 100644 --- a/rules/segment_types.json +++ b/rules/segment_types.json @@ -9,8 +9,8 @@ }, "last": { "type": "cta", - "confidence": 0.6, - "description": "最后一段默认为 cta,但如果有 CTA 关键词则 confidence 更高" + "confidence": 0.9, + "description": "最后一段默认为 cta,高 confidence 确保结尾引导" } }, "keyword_rules": [ diff --git a/scripts/fix_cta_overuse.py b/scripts/fix_cta_overuse.py new file mode 100755 index 0000000..77031a5 --- /dev/null +++ b/scripts/fix_cta_overuse.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +""" +修复 segments.json 中 CTA 过多的问题 + +用法: + python scripts/fix_cta_overuse.py output/rsi_cn/segments.json + +逻辑: + - 只有最后一个 segment 允许类型为 "cta" + - 其他被误标为 "cta" 的 segment 降级为 "narrative" +""" + +import json +import sys +from pathlib import Path + + +def fix_cta_overuse(segments_path: str): + path = Path(segments_path) + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + + segments = data.get("segments", []) + if not segments: + print("No segments found") + return + + total = len(segments) + fixed = 0 + + for i, seg in enumerate(segments): + if seg.get("segment_type") == "cta" and i < total - 1: + seg["segment_type"] = "narrative" + seg["segment_type_confidence"] = 0.5 + fixed += 1 + print(f" Fixed {seg['id']}: cta -> narrative") + + if fixed > 0: + # 备份原文件 + backup_path = path.with_suffix(".json.bak") + path.rename(backup_path) + with open(path, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + print(f"\nFixed {fixed} segment(s). Original backed up to {backup_path.name}") + else: + print("No overuse CTA segments found. All good!") + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print(f"Usage: python {sys.argv[0]} ") + sys.exit(1) + fix_cta_overuse(sys.argv[1])