""" 将 20 期融合A稿 (.docx) 转为带 YAML frontmatter 的 .md 文件, 供 TPS 主项目知识库 /api/knowledge/upload 批量导入。 用法: cd E:\tps-dashboard\doco python convert_to_md.py 产物落在 doco/deliverables/ 目录下,每期一个 .md 文件。 """ import glob import os import re import sys from datetime import datetime from pathlib import Path from docx import Document PROGRAMS_DIR = Path(__file__).parent / "programs" OUTPUT_DIR = Path(__file__).parent / "deliverables" # ep001 文件名没有编导和标题信息,需要手工补 EP001_OVERRIDE = { "title": "现代防空反导大对决", "author": "", # ← 通哥填 "date": "2026-06-12", } def find_fusion_docx(episode_dir: Path) -> Path | None: """在 episode 目录里找融合A稿 docx(排除批改稿)。""" candidates = [] for f in episode_dir.iterdir(): if f.suffix == ".docx" and "融合A稿" in f.name and "批改" not in f.name: candidates.append(f) if not candidates: return None if len(candidates) == 1: return candidates[0] # 优先选带日期前缀的(ep002+ 格式) for c in candidates: if re.match(r"\d{8}", c.stem): return c return candidates[0] def parse_metadata_from_filename(docx_path: Path, episode_dir_name: str) -> dict: """ 从文件名提取元数据。 ep002+ 文件名格式:{YYYYMMDD}{标题}_{编导}_融合A稿.docx ep001 特殊处理。 """ if episode_dir_name.startswith("ep001"): return EP001_OVERRIDE.copy() stem = docx_path.stem # e.g. "20260127潜艇的仿生之路_穆佩弦_融合A稿" # 去掉 "_融合A稿" 后缀 stem = re.sub(r"_融合A稿$", "", stem) # 提取日期 (前8位) date_str = stem[:8] try: dt = datetime.strptime(date_str, "%Y%m%d") date_iso = dt.strftime("%Y-%m-%d") except ValueError: date_iso = "" rest = stem[8:] # e.g. "潜艇的仿生之路_穆佩弦" # 最后一个 _ 分隔编导 parts = rest.rsplit("_", 1) if len(parts) == 2: title, author = parts else: title = rest author = "" return {"title": title, "date": date_iso, "author": author} def docx_to_markdown(docx_path: Path) -> str: """将融合A稿 docx 转为 markdown 正文。""" doc = Document(str(docx_path)) lines = [] for para in doc.paragraphs: text = para.text.strip() if not text: continue # 大标题(第一段,通常带书名号) if not lines and (text.startswith("《") or text.startswith("【") is False): lines.append(f"# {text}") lines.append("") continue # 段头标签:【主持人1】【解说3】【专家2】【导视】隔断:【...】 if re.match(r"^【.+?】$", text) or re.match(r"^隔断\d*[::]【.+?】", text): lines.append("") lines.append(f"## {text}") lines.append("") continue # 普通正文段落 lines.append(text) lines.append("") return "\n".join(lines).strip() + "\n" def build_frontmatter(meta: dict, episode_id: str) -> str: """生成 YAML frontmatter。""" fm_lines = ["---"] fm_lines.append(f"标题: {meta['title']}") if meta["author"]: fm_lines.append(f"编导: {meta['author']}") if meta["date"]: fm_lines.append(f"播出日期: {meta['date']}") fm_lines.append("类型: 节目文稿") fm_lines.append(f"期号: {episode_id}") fm_lines.append("---") return "\n".join(fm_lines) def main(): OUTPUT_DIR.mkdir(exist_ok=True) # 找所有 ep??? 目录(排除 ep002_004 这种非标准的) episode_dirs = sorted( d for d in PROGRAMS_DIR.iterdir() if d.is_dir() and re.match(r"^ep\d{3}_\d{8}_", d.name) ) print(f"找到 {len(episode_dirs)} 个期目录") success = 0 errors = [] for ep_dir in episode_dirs: episode_id = ep_dir.name.split("_")[0] # ep001, ep002, ... docx_path = find_fusion_docx(ep_dir) if docx_path is None: errors.append(f"{ep_dir.name}: 未找到融合A稿") continue print(f" {episode_id}: {docx_path.name}") meta = parse_metadata_from_filename(docx_path, ep_dir.name) frontmatter = build_frontmatter(meta, episode_id) body = docx_to_markdown(docx_path) # 输出文件名:ep001_现代防空反导大对决.md safe_title = meta["title"].replace(":", ":").replace("/", "_") out_name = f"{episode_id}_{safe_title}.md" out_path = OUTPUT_DIR / out_name with open(out_path, "w", encoding="utf-8") as f: f.write(frontmatter + "\n\n" + body) success += 1 print(f"\n完成:{success}/{len(episode_dirs)} 期转换成功") if errors: print("失败:") for e in errors: print(f" {e}") print(f"\n产物目录:{OUTPUT_DIR}") if __name__ == "__main__": main()