92 lines
3.0 KiB
Python
92 lines
3.0 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
将 DOCX 转为 Markdown,并把内嵌图片导出到同级 media 目录,Markdown 内使用相对路径引用。
|
|||
|
|
|
|||
|
|
说明:markitdown / MCP 用的默认 mammoth 路径在「表格+多图」等版式下经常出现图片被省略;
|
|||
|
|
Office Viewer 是编辑器原生渲染 Word,与命令行转换不是同一套逻辑。
|
|||
|
|
本脚本通过 mammoth 的 convert_image 将图片写入磁盘,便于 AI 与文档工具使用。
|
|||
|
|
|
|||
|
|
依赖:pip install mammoth
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
python docx_to_markdown_with_images.py input.docx -o out.md
|
|||
|
|
python docx_to_markdown_with_images.py input.docx -o out.md --media-dir assets/req-images
|
|||
|
|
|
|||
|
|
全局安装(推荐,任意仓库可直接调用):
|
|||
|
|
Windows: 复制本文件到 %USERPROFILE%\\.claude\\scripts\\
|
|||
|
|
或在 jjb-ai 仓库 .claude\\scripts 下执行: install-docx-converter-global.ps1
|
|||
|
|
然后: python "%USERPROFILE%\\.claude\\scripts\\docx_to_markdown_with_images.py" ...
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> int:
|
|||
|
|
try:
|
|||
|
|
import mammoth
|
|||
|
|
except ImportError:
|
|||
|
|
print("请先安装: pip install mammoth", file=sys.stderr)
|
|||
|
|
return 1
|
|||
|
|
|
|||
|
|
parser = argparse.ArgumentParser(description="DOCX → Markdown,图片落盘")
|
|||
|
|
parser.add_argument("docx", help="输入 .docx 路径")
|
|||
|
|
parser.add_argument("-o", "--output", required=True, help="输出 .md 路径")
|
|||
|
|
parser.add_argument(
|
|||
|
|
"--media-dir",
|
|||
|
|
default="media",
|
|||
|
|
help="媒体目录(相对 output 所在目录),默认 media",
|
|||
|
|
)
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
|
|||
|
|
out_md = os.path.abspath(args.output)
|
|||
|
|
base_dir = os.path.dirname(out_md)
|
|||
|
|
media_rel = args.media_dir.strip("/\\").replace("\\", "/")
|
|||
|
|
media_abs = os.path.join(base_dir, media_rel.replace("/", os.sep))
|
|||
|
|
os.makedirs(media_abs, exist_ok=True)
|
|||
|
|
|
|||
|
|
counter = {"n": 0}
|
|||
|
|
|
|||
|
|
def save_image(image):
|
|||
|
|
content_type = image.content_type or "image/png"
|
|||
|
|
ext = content_type.split("/")[-1].lower()
|
|||
|
|
if ext in ("jpeg", "pjpeg"):
|
|||
|
|
ext = "jpg"
|
|||
|
|
counter["n"] += 1
|
|||
|
|
filename = f"image_{counter['n']:03d}.{ext}"
|
|||
|
|
path_abs = os.path.join(media_abs, filename)
|
|||
|
|
with image.open() as stream:
|
|||
|
|
data = stream.read()
|
|||
|
|
with open(path_abs, "wb") as f:
|
|||
|
|
f.write(data)
|
|||
|
|
rel_src = f"{media_rel}/{filename}".replace("\\", "/")
|
|||
|
|
return {"src": rel_src}
|
|||
|
|
|
|||
|
|
docx_path = os.path.abspath(args.docx)
|
|||
|
|
if not os.path.isfile(docx_path):
|
|||
|
|
print(f"文件不存在: {docx_path}", file=sys.stderr)
|
|||
|
|
return 1
|
|||
|
|
|
|||
|
|
with open(docx_path, "rb") as docx_file:
|
|||
|
|
result = mammoth.convert_to_markdown(
|
|||
|
|
docx_file,
|
|||
|
|
convert_image=mammoth.images.img_element(save_image),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
for msg in result.messages:
|
|||
|
|
print(msg, file=sys.stderr)
|
|||
|
|
|
|||
|
|
with open(out_md, "w", encoding="utf-8") as f:
|
|||
|
|
f.write(result.value)
|
|||
|
|
|
|||
|
|
print(out_md, file=sys.stderr)
|
|||
|
|
print(f"images: {counter['n']}", file=sys.stderr)
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
raise SystemExit(main())
|