177 lines
5.0 KiB
Python
177 lines
5.0 KiB
Python
|
|
"""
|
|||
|
|
将 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()
|