Files
tps-dashboard/doco/convert_to_md.py
T

177 lines
5.0 KiB
Python
Raw Normal View History

"""
将 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()