feat: CCA 唱词助手子项目 v3 — 脚本版流水线完成

新增 cca/ 子项目:编导A稿+人声音频 → ASR+AI校对+AI折行 → 5段SRT字幕。
- 讯飞录音文件转写标准版(热词注入)
- DeepSeek AI校对(严格纪律:只改错别字/术语/填充词,不润色)
- DeepSeek AI折行(语义断句,≤14字/行)
- 节目结构自动切分(导视/正片×3/预告)
- 绝对时间戳SRT输出(大洋系统兼容)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
simonkoson
2026-07-04 15:25:08 +08:00
parent 1ccb37f0c7
commit ede30d3043
21 changed files with 5530 additions and 1 deletions
+152
View File
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
"""
CCA 唱词助手 — 脚本版流水线入口
用法:
# 完整流程: A稿热词 + ASR + AI校对 + AI折行 → 5个SRT
python cca_pipeline.py --audio data/xxx.mp3 --script data/xxx.docx
# 手动指定热词
python cca_pipeline.py --audio data/xxx.mp3 --hotwords "热词1|热词2"
# 跳过ASR,从缓存处理(调试折行/校对用)
python cca_pipeline.py --asr-cache output/asr_raw.json --script data/xxx.docx
# 省token模式(不用AI折行和校对)
python cca_pipeline.py --asr-cache output/asr_raw.json --no-ai
流水线:
A稿 → 热词提取 → 音频+热词 → 讯飞ASR → AI校对 → 节目结构切分 → AI折行 → 5个SRT
"""
import argparse
import json
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "src"))
from asr_client import transcribe, parse_result
from line_breaker import process_sentences
from ai_line_breaker import process_sentences_with_ai
from srt_writer import write_srt, ms_to_srt_time
from segment_splitter import split_into_segments
from hotword_extractor import extract_hotwords
from ai_proofreader import proofread_batch
def main():
parser = argparse.ArgumentParser(description="CCA 唱词助手 - 自动生成拍词 SRT")
parser.add_argument("--audio", type=str, help="音频文件路径 (mp3/wav)")
parser.add_argument("--script", type=str, help="A稿路径 (.docx/.txt),用于热词提取和AI校对")
parser.add_argument("--hotwords", type=str, default="", help="手动指定热词,用|分隔(与--script可叠加)")
parser.add_argument("--asr-cache", type=str, help="ASR 缓存 JSON 路径(跳过ASR调用)")
parser.add_argument("--output-dir", type=str, default="output", help="输出目录 (默认: output/)")
parser.add_argument("--no-ai", action="store_true", help="不使用AI折行和校对(省token")
parser.add_argument("--no-proofread", action="store_true", help="跳过AI校对(只省校对的token")
args = parser.parse_args()
if not args.audio and not args.asr_cache:
parser.error("必须提供 --audio 或 --asr-cache")
output_dir = Path(args.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
use_ai = not args.no_ai
# ====== Step 0: 热词提取(从A稿)======
hot_words = []
script_text = ""
if args.hotwords:
hot_words = [w.strip() for w in args.hotwords.split("|") if w.strip()]
if args.script:
print(f"[流水线] 从A稿提取热词: {args.script}")
script_hot = extract_hotwords(args.script, use_ai=use_ai)
# 合并手动热词和A稿热词
seen = set(hot_words)
for w in script_hot:
if w not in seen:
hot_words.append(w)
seen.add(w)
# 读取A稿全文(校对用)
ext = os.path.splitext(args.script)[1].lower()
if ext == ".docx":
from hotword_extractor import read_docx_text
script_text = read_docx_text(args.script)
else:
from hotword_extractor import read_text_file
script_text = read_text_file(args.script)
if hot_words:
print(f"[流水线] 热词共 {len(hot_words)} 个: {', '.join(hot_words[:10])}...")
# 保存热词列表
hotwords_path = output_dir / "hotwords.txt"
with open(hotwords_path, "w", encoding="utf-8") as f:
f.write("|".join(hot_words))
# ====== Step 1: ASR 转写 ======
if args.asr_cache:
print(f"[流水线] 从缓存加载 ASR 结果: {args.asr_cache}")
with open(args.asr_cache, "r", encoding="utf-8") as f:
raw_json = f.read()
sentences = parse_result(raw_json)
else:
print(f"[流水线] 开始 ASR 转写: {args.audio}")
sentences, raw_json = transcribe(args.audio, hot_words=hot_words if hot_words else None)
cache_path = output_dir / "asr_raw.json"
with open(cache_path, "w", encoding="utf-8") as f:
f.write(raw_json)
print(f"[流水线] ASR 原始结果已缓存: {cache_path}")
print(f"[流水线] ASR 共 {len(sentences)}")
# ====== Step 2: AI 校对 ======
if use_ai and not args.no_proofread and script_text:
print("[流水线] AI 校对中 (DeepSeek)...")
sentences = proofread_batch(sentences, script_text)
elif not script_text and not args.no_proofread and use_ai:
print("[流水线] 未提供A稿(--script),跳过AI校对")
# ====== Step 3: 节目结构切分 ======
print("[流水线] 切分节目结构...")
segments = split_into_segments(sentences)
print(f"[流水线] 切分结果: {[name for name, _ in segments]}")
# ====== Step 4: 折行 + 生成 SRT ======
if use_ai:
print("[流水线] 使用 AI 折行 (DeepSeek)...")
else:
print("[流水线] 使用机械折行规则...")
for seg_name, seg_sentences in segments:
if not seg_sentences:
print(f" [{seg_name}] 空段,跳过")
continue
if use_ai:
subtitle_lines = process_sentences_with_ai(seg_sentences)
else:
subtitle_lines = process_sentences(seg_sentences)
seg_offset = subtitle_lines[0][0] if subtitle_lines else 0
seg_end = subtitle_lines[-1][1] if subtitle_lines else 0
seg_duration = seg_end - seg_offset
srt_filename = f"{seg_name}.srt"
srt_path = output_dir / srt_filename
write_srt(subtitle_lines, str(srt_path)) # 绝对时间戳,方便在时间线上对位
print(f" [{seg_name}] 时长 {ms_to_srt_time(seg_duration)}, 在音频中的位置: {ms_to_srt_time(seg_offset)} ~ {ms_to_srt_time(seg_end)}")
# ====== 完成 ======
print(f"\n[流水线] 完成! 输出目录: {output_dir}/")
print("[流水线] 生成的文件:")
for f in sorted(output_dir.glob("*.srt")):
print(f" {f.name}")
if __name__ == "__main__":
main()