"""PowerPoint (.pptx) → Markdown。每張投影片一節,含表格、圖片佔位與講者備註。 可獨立執行:python tools/convert/from_pptx.py [-o out.md] [--dry-run] """ import argparse import pathlib from pptx import Presentation from pptx.enum.shapes import MSO_SHAPE_TYPE def _table_md(tbl): rows = [ [c.text.strip().replace("|", "\\|").replace("\n", " ") for c in row.cells] for row in tbl.rows ] if not rows: return "" width = len(rows[0]) out = ["| " + " | ".join(rows[0]) + " |", "|" + " --- |" * width] out += ["| " + " | ".join(r) + " |" for r in rows[1:]] return "\n".join(out) def convert(src, assets_dir=None, md_dir=None): """回傳 (markdown, status)。status 恆為 'ok'。""" src = pathlib.Path(src) md_dir = pathlib.Path(md_dir) if md_dir else src.parent prs = Presentation(str(src)) counter = 0 sections = [] for i, slide in enumerate(prs.slides, 1): blocks = [f"## Slide {i}"] for shape in slide.shapes: if shape.has_text_frame and shape.text_frame.text.strip(): blocks.append(shape.text_frame.text.strip()) elif shape.has_table: blocks.append(_table_md(shape.table)) elif shape.shape_type == MSO_SHAPE_TYPE.PICTURE: counter += 1 name = f"img{counter}.{shape.image.ext}" if assets_dir is not None: assets_dir.mkdir(parents=True, exist_ok=True) (assets_dir / name).write_bytes(shape.image.blob) rel = (assets_dir / name).relative_to(md_dir).as_posix() else: rel = name # <> 包住:檔名含空格或不平衡括號時,未包住的連結會失效 blocks.append(f"![{name}](<{rel}>)") if slide.has_notes_slide: notes = slide.notes_slide.notes_text_frame.text.strip() if notes: blocks.append(f"### 講者備註\n\n{notes}") sections.append("\n\n".join(blocks)) return "\n\n".join(sections), "ok" def main(argv=None): ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("src") ap.add_argument("-o", "--output") ap.add_argument("--dry-run", action="store_true") a = ap.parse_args(argv) if a.dry_run: md, status = convert(a.src) print(f"[dry-run] {a.src}: status={status}, {len(md)} chars") return out = pathlib.Path(a.output) if a.output else pathlib.Path(a.src).with_suffix(".md") md, _ = convert(a.src, assets_dir=out.parent / f"{out.stem}-assets", md_dir=out.parent) out.write_text(md, encoding="utf-8") print(out) if __name__ == "__main__": main()