ming-tomarkdown 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ming_tomarkdown-0.1.1/.gitignore +20 -0
- ming_tomarkdown-0.1.1/PKG-INFO +243 -0
- ming_tomarkdown-0.1.1/README.md +230 -0
- ming_tomarkdown-0.1.1/pyproject.toml +29 -0
- ming_tomarkdown-0.1.1/src/tomarkdown/__init__.py +5 -0
- ming_tomarkdown-0.1.1/src/tomarkdown/__main__.py +4 -0
- ming_tomarkdown-0.1.1/src/tomarkdown/cli.py +31 -0
- ming_tomarkdown-0.1.1/src/tomarkdown/core.py +103 -0
- ming_tomarkdown-0.1.1/src/tomarkdown/ui.py +42 -0
- ming_tomarkdown-0.1.1/to_md.py +11 -0
- ming_tomarkdown-0.1.1/ui.py +5 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Local credentials and generated output
|
|
13
|
+
.env
|
|
14
|
+
.env.*
|
|
15
|
+
!.env.example
|
|
16
|
+
results/
|
|
17
|
+
.DS_Store
|
|
18
|
+
|
|
19
|
+
# test
|
|
20
|
+
test
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ming-tomarkdown
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Convert documents and folders to Markdown with vision OCR
|
|
5
|
+
Project-URL: Repository, https://github.com/MingWangD/ToMarkdown
|
|
6
|
+
Project-URL: Issues, https://github.com/MingWangD/ToMarkdown/issues
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: markitdown-ocr>=0.1.0
|
|
9
|
+
Requires-Dist: markitdown[all]>=0.1.5
|
|
10
|
+
Requires-Dist: openai>=3.31.0
|
|
11
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Ming ToMarkdown
|
|
15
|
+
|
|
16
|
+
将 PDF、Word、Excel、PowerPoint、图片和文本文件转换为 Markdown 的 Python 包,支持终端命令和 Python 调用。
|
|
17
|
+
|
|
18
|
+
安装包名为 `ming-tomarkdown`,终端命令和 Python 导入名仍为 `tomarkdown`。GitHub 仓库名称保持 `ToMarkdown`。
|
|
19
|
+
|
|
20
|
+
默认开启图片 OCR:正文文字直接提取,图片交给视觉模型识别,再合并为 Markdown。支持单文件和混合格式文件夹,批量转换会保留输入文件夹名称。
|
|
21
|
+
|
|
22
|
+
## 安装
|
|
23
|
+
|
|
24
|
+
需要 Python 3.12 或更高版本。在已有虚拟环境中,可用 uv 直接安装 GitHub 发布版本,无需 Git:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv pip install "https://github.com/MingWangD/ToMarkdown/releases/download/v0.1.1/ming_tomarkdown-0.1.1-py3-none-any.whl"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
尚未创建虚拟环境时,先执行 `uv venv --python 3.12` 并激活环境。macOS / Linux 使用 `source .venv/bin/activate`,Windows PowerShell 使用 `.venv\Scripts\Activate.ps1`。
|
|
31
|
+
|
|
32
|
+
也可以通过 pip 从 GitHub 源码安装:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m pip install "ming-tomarkdown @ git+https://github.com/MingWangD/ToMarkdown.git@v0.1.1"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
这种安装方式需要本机已有 Git。也可以从仓库的 [Releases](https://github.com/MingWangD/ToMarkdown/releases) 下载 `.whl`,直接安装,无需 Git:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m pip install ./ming_tomarkdown-0.1.1-py3-none-any.whl
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
仅使用终端命令,也可以用 uv 安装:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv tool install "https://github.com/MingWangD/ToMarkdown/releases/download/v0.1.1/ming_tomarkdown-0.1.1-py3-none-any.whl"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
当前通过 GitHub 分发,尚未发布到 PyPI,请使用以上安装地址。
|
|
51
|
+
|
|
52
|
+
旧版 GitHub 包的安装名为 `tomarkdown`,升级前请在对应环境运行 `uv pip uninstall tomarkdown`(或 `python -m pip uninstall tomarkdown`),再安装新包。两者提供相同的导入模块和命令,不应同时安装。
|
|
53
|
+
|
|
54
|
+
## 快速开始
|
|
55
|
+
|
|
56
|
+
无需模型配置即可先验证普通转换:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
tomarkdown "/路径/文档.pdf" --no-ocr
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
配置好下一节的 OCR 服务后,直接运行:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 单个文件
|
|
66
|
+
tomarkdown "/路径/论文.pdf"
|
|
67
|
+
|
|
68
|
+
# 混合格式文件夹:PDF、Word、Excel 等
|
|
69
|
+
tomarkdown "/路径/资料"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
终端命令不需要先 `import`。如果终端找不到 `tomarkdown`,可以用安装该包的 Python 运行:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
python -m tomarkdown "/路径/资料"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 通用 OCR 配置
|
|
79
|
+
|
|
80
|
+
使用支持图片输入、兼容 OpenAI Chat Completions 接口的模型服务。在运行命令的目录创建 `.env`:
|
|
81
|
+
|
|
82
|
+
```dotenv
|
|
83
|
+
OPENAI_API_KEY=服务提供的_API_Key
|
|
84
|
+
OPENAI_MODEL=服务支持的视觉模型名称
|
|
85
|
+
OPENAI_BASE_URL=服务提供的_OpenAI_兼容接口基础地址
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`OPENAI_` 是变量名称,不限定模型提供商。密钥、模型和接口地址需要对应同一个服务;接口地址不要追加 `/chat/completions`。也可以直接设置系统环境变量,同名系统环境变量优先于 `.env`。
|
|
89
|
+
|
|
90
|
+
配置优先级:
|
|
91
|
+
|
|
92
|
+
- 模型:显式参数 → `MODEL` → `OPENAI_MODEL` → `GEMINI_MODEL` → `gemini-3.8-flash-high`。
|
|
93
|
+
- 密钥:Python 调用的 `api_key` 参数 → `GEMINI_API_KEY` → `OPENAI_API_KEY`。
|
|
94
|
+
- 接口:显式参数 → `OPENAI_BASE_URL` → `http://127.0.0.1:8045/v1`。
|
|
95
|
+
|
|
96
|
+
最后的模型名和接口地址沿用原项目默认值,不代表你的服务一定提供该模型。使用通用配置时,应填写实际服务信息,并清除不再使用的 `MODEL`、`GEMINI_API_KEY` 等旧值。
|
|
97
|
+
|
|
98
|
+
### 一个具体例子
|
|
99
|
+
|
|
100
|
+
假设本地服务已在 `http://127.0.0.1:8045/v1` 启动,提供名为 `vision-model` 的视觉模型。在当前目录的 `.env` 中填写:
|
|
101
|
+
|
|
102
|
+
```dotenv
|
|
103
|
+
OPENAI_API_KEY=你的本地服务密钥
|
|
104
|
+
OPENAI_MODEL=vision-model
|
|
105
|
+
OPENAI_BASE_URL=http://127.0.0.1:8045/v1
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`vision-model` 和密钥需要替换为实际值。然后运行:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
tomarkdown "/路径/论文.pdf"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
结果保存在当前目录下的 `results/论文.md`。
|
|
115
|
+
|
|
116
|
+
## 在 Python 中调用
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from tomarkdown import convert
|
|
120
|
+
|
|
121
|
+
# 默认开启 OCR,读取当前工作目录的 .env
|
|
122
|
+
result = convert("/路径/资料")
|
|
123
|
+
|
|
124
|
+
for path in result.files:
|
|
125
|
+
print("已保存:", path)
|
|
126
|
+
|
|
127
|
+
for source, error in result.errors.items():
|
|
128
|
+
print("转换失败:", source, error)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
也可以直接传入配置:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
import os
|
|
135
|
+
from tomarkdown import convert
|
|
136
|
+
|
|
137
|
+
result = convert(
|
|
138
|
+
"/路径/论文.pdf",
|
|
139
|
+
api_key=os.environ["OPENAI_API_KEY"],
|
|
140
|
+
model="你的视觉模型名称",
|
|
141
|
+
base_url="http://127.0.0.1:8045/v1",
|
|
142
|
+
show_progress=True,
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
关闭 OCR:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
result = convert("/路径/报告.docx", ocr=False)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`convert()` 返回 `ConversionResult`:`files` 是成功输出的 `Path` 列表,`errors` 是输入路径到错误信息的字典。单个文件转换失败后会继续处理其他文件;无效输入路径、缺少密钥等配置错误会直接抛出异常。
|
|
153
|
+
|
|
154
|
+
Python 调用默认不显示终端进度,可用 `show_progress=True` 开启;也可通过 `results_dir="/路径/results"` 指定结果根目录。
|
|
155
|
+
|
|
156
|
+
## 输出规则
|
|
157
|
+
|
|
158
|
+
安装包后,命令及 Python 调用默认将结果写入**当前工作目录**的 `results/`,不会写进包的安装目录。所需目录会自动创建。
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
单文件输入:/路径/论文.pdf
|
|
162
|
+
输出: results/论文.md
|
|
163
|
+
|
|
164
|
+
文件夹输入:/路径/资料/
|
|
165
|
+
├── 论文.pdf
|
|
166
|
+
├── 报告.docx
|
|
167
|
+
└── 数据.xlsx
|
|
168
|
+
输出: results/资料/
|
|
169
|
+
├── 论文.md
|
|
170
|
+
├── 报告.md
|
|
171
|
+
└── 数据.md
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
保留源文件名,只把扩展名改为 `.md`,例如 `报告.v2.pdf` → `报告.v2.md`。同一输出目录下的同名结果会覆盖,`报告.pdf` 与 `报告.docx` 也会写入同一个 `报告.md`。
|
|
175
|
+
|
|
176
|
+
文件夹只处理当前层,按文件名排序逐个转换,不进入子文件夹,跳过不支持的扩展名。终端显示文件序号、耗时及成功/失败数量,不显示页数或百分比;存在转换失败时命令以非零状态退出。
|
|
177
|
+
|
|
178
|
+
## 命令参数
|
|
179
|
+
|
|
180
|
+
| 参数 | 说明 |
|
|
181
|
+
| --- | --- |
|
|
182
|
+
| `file` | 文件或文件夹路径,必填 |
|
|
183
|
+
| `--model` | 指定视觉模型,覆盖环境变量 |
|
|
184
|
+
| `--base-url` | 指定 OpenAI 兼容接口基础地址,覆盖环境变量 |
|
|
185
|
+
| `--no-ocr` | 关闭 OCR,不调用模型;默认开启 OCR |
|
|
186
|
+
| `-h`、`--help` | 查看帮助 |
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
tomarkdown "/路径/文档.pdf" --model "你的视觉模型名称"
|
|
190
|
+
tomarkdown "/路径/资料" --no-ocr
|
|
191
|
+
tomarkdown --help
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## 支持的内容与处理流程
|
|
195
|
+
|
|
196
|
+
支持 PDF、Word(`.docx`)、Excel(`.xlsx`、`.xls`)、PowerPoint(`.pptx`)、图片、HTML,以及 TXT、CSV、JSON、XML 等文本格式。旧版 Word `.doc` 不在批量处理范围内。
|
|
197
|
+
|
|
198
|
+
```text
|
|
199
|
+
这一页 PDF
|
|
200
|
+
├── 文字层 → 直接提取
|
|
201
|
+
└── 图片 → LLM 识别
|
|
202
|
+
↓
|
|
203
|
+
合并成 Markdown
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
扫描件中的正文通常属于图片,需要 OCR。复杂排版、表格和公式的还原取决于文件内容、MarkItDown 插件及模型。JSON、XML 等文本主要保留原文。OCR 插件内部未抛出的识别错误不会计入失败数量。
|
|
207
|
+
|
|
208
|
+
## 从源码运行与构建
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
git clone https://github.com/MingWangD/ToMarkdown.git
|
|
212
|
+
cd ToMarkdown
|
|
213
|
+
uv sync
|
|
214
|
+
|
|
215
|
+
# 安装包后的命令入口
|
|
216
|
+
uv run tomarkdown "/路径/资料"
|
|
217
|
+
|
|
218
|
+
# 保留原有脚本入口:读取项目 .env,结果固定保存到项目 results/
|
|
219
|
+
uv run python to_md.py "/路径/资料"
|
|
220
|
+
|
|
221
|
+
# 生成 wheel 和源码包,输出到 dist/
|
|
222
|
+
uv build --no-sources
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
核心代码位于 `src/tomarkdown/`。根目录的 `to_md.py` 和 `ui.py` 保留为原有用法的兼容入口。发布包不包含 `.env`、转换结果或本地样例文件。
|
|
226
|
+
|
|
227
|
+
## 维护者:首次发布到 PyPI
|
|
228
|
+
|
|
229
|
+
仓库提供 `.github/workflows/publish.yml`,通过 GitHub Actions 可信发布,无需配置 PyPI API Token。
|
|
230
|
+
|
|
231
|
+
首次使用时,登录 PyPI 并打开[账号的 Publishing 页面](https://pypi.org/manage/account/publishing/),添加 GitHub 待发布项目,填写:
|
|
232
|
+
|
|
233
|
+
| 字段 | 值 |
|
|
234
|
+
| --- | --- |
|
|
235
|
+
| PyPI Project Name | `ming-tomarkdown` |
|
|
236
|
+
| Owner | `MingWangD` |
|
|
237
|
+
| Repository name | `ToMarkdown` |
|
|
238
|
+
| Workflow name | `publish.yml` |
|
|
239
|
+
| Environment name | `pypi` |
|
|
240
|
+
|
|
241
|
+
保存后,在 GitHub Actions 的 **Publish to PyPI** 流程中选择 **Run workflow**,勾选 **Publish to PyPI after building** 即可发布;不勾选时仅检查构建。此后发布新的 GitHub Release 也会自动触发 PyPI 发布。发布新版本前需先更新 `pyproject.toml` 中的版本号及依赖锁定文件。
|
|
242
|
+
|
|
243
|
+
首次发布成功后,其他人就可以用 `uv pip install ming-tomarkdown` 安装。
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Ming ToMarkdown
|
|
2
|
+
|
|
3
|
+
将 PDF、Word、Excel、PowerPoint、图片和文本文件转换为 Markdown 的 Python 包,支持终端命令和 Python 调用。
|
|
4
|
+
|
|
5
|
+
安装包名为 `ming-tomarkdown`,终端命令和 Python 导入名仍为 `tomarkdown`。GitHub 仓库名称保持 `ToMarkdown`。
|
|
6
|
+
|
|
7
|
+
默认开启图片 OCR:正文文字直接提取,图片交给视觉模型识别,再合并为 Markdown。支持单文件和混合格式文件夹,批量转换会保留输入文件夹名称。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
需要 Python 3.12 或更高版本。在已有虚拟环境中,可用 uv 直接安装 GitHub 发布版本,无需 Git:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv pip install "https://github.com/MingWangD/ToMarkdown/releases/download/v0.1.1/ming_tomarkdown-0.1.1-py3-none-any.whl"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
尚未创建虚拟环境时,先执行 `uv venv --python 3.12` 并激活环境。macOS / Linux 使用 `source .venv/bin/activate`,Windows PowerShell 使用 `.venv\Scripts\Activate.ps1`。
|
|
18
|
+
|
|
19
|
+
也可以通过 pip 从 GitHub 源码安装:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python -m pip install "ming-tomarkdown @ git+https://github.com/MingWangD/ToMarkdown.git@v0.1.1"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
这种安装方式需要本机已有 Git。也可以从仓库的 [Releases](https://github.com/MingWangD/ToMarkdown/releases) 下载 `.whl`,直接安装,无需 Git:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m pip install ./ming_tomarkdown-0.1.1-py3-none-any.whl
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
仅使用终端命令,也可以用 uv 安装:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv tool install "https://github.com/MingWangD/ToMarkdown/releases/download/v0.1.1/ming_tomarkdown-0.1.1-py3-none-any.whl"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
当前通过 GitHub 分发,尚未发布到 PyPI,请使用以上安装地址。
|
|
38
|
+
|
|
39
|
+
旧版 GitHub 包的安装名为 `tomarkdown`,升级前请在对应环境运行 `uv pip uninstall tomarkdown`(或 `python -m pip uninstall tomarkdown`),再安装新包。两者提供相同的导入模块和命令,不应同时安装。
|
|
40
|
+
|
|
41
|
+
## 快速开始
|
|
42
|
+
|
|
43
|
+
无需模型配置即可先验证普通转换:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
tomarkdown "/路径/文档.pdf" --no-ocr
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
配置好下一节的 OCR 服务后,直接运行:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# 单个文件
|
|
53
|
+
tomarkdown "/路径/论文.pdf"
|
|
54
|
+
|
|
55
|
+
# 混合格式文件夹:PDF、Word、Excel 等
|
|
56
|
+
tomarkdown "/路径/资料"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
终端命令不需要先 `import`。如果终端找不到 `tomarkdown`,可以用安装该包的 Python 运行:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python -m tomarkdown "/路径/资料"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 通用 OCR 配置
|
|
66
|
+
|
|
67
|
+
使用支持图片输入、兼容 OpenAI Chat Completions 接口的模型服务。在运行命令的目录创建 `.env`:
|
|
68
|
+
|
|
69
|
+
```dotenv
|
|
70
|
+
OPENAI_API_KEY=服务提供的_API_Key
|
|
71
|
+
OPENAI_MODEL=服务支持的视觉模型名称
|
|
72
|
+
OPENAI_BASE_URL=服务提供的_OpenAI_兼容接口基础地址
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`OPENAI_` 是变量名称,不限定模型提供商。密钥、模型和接口地址需要对应同一个服务;接口地址不要追加 `/chat/completions`。也可以直接设置系统环境变量,同名系统环境变量优先于 `.env`。
|
|
76
|
+
|
|
77
|
+
配置优先级:
|
|
78
|
+
|
|
79
|
+
- 模型:显式参数 → `MODEL` → `OPENAI_MODEL` → `GEMINI_MODEL` → `gemini-3.8-flash-high`。
|
|
80
|
+
- 密钥:Python 调用的 `api_key` 参数 → `GEMINI_API_KEY` → `OPENAI_API_KEY`。
|
|
81
|
+
- 接口:显式参数 → `OPENAI_BASE_URL` → `http://127.0.0.1:8045/v1`。
|
|
82
|
+
|
|
83
|
+
最后的模型名和接口地址沿用原项目默认值,不代表你的服务一定提供该模型。使用通用配置时,应填写实际服务信息,并清除不再使用的 `MODEL`、`GEMINI_API_KEY` 等旧值。
|
|
84
|
+
|
|
85
|
+
### 一个具体例子
|
|
86
|
+
|
|
87
|
+
假设本地服务已在 `http://127.0.0.1:8045/v1` 启动,提供名为 `vision-model` 的视觉模型。在当前目录的 `.env` 中填写:
|
|
88
|
+
|
|
89
|
+
```dotenv
|
|
90
|
+
OPENAI_API_KEY=你的本地服务密钥
|
|
91
|
+
OPENAI_MODEL=vision-model
|
|
92
|
+
OPENAI_BASE_URL=http://127.0.0.1:8045/v1
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`vision-model` 和密钥需要替换为实际值。然后运行:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
tomarkdown "/路径/论文.pdf"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
结果保存在当前目录下的 `results/论文.md`。
|
|
102
|
+
|
|
103
|
+
## 在 Python 中调用
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from tomarkdown import convert
|
|
107
|
+
|
|
108
|
+
# 默认开启 OCR,读取当前工作目录的 .env
|
|
109
|
+
result = convert("/路径/资料")
|
|
110
|
+
|
|
111
|
+
for path in result.files:
|
|
112
|
+
print("已保存:", path)
|
|
113
|
+
|
|
114
|
+
for source, error in result.errors.items():
|
|
115
|
+
print("转换失败:", source, error)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
也可以直接传入配置:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import os
|
|
122
|
+
from tomarkdown import convert
|
|
123
|
+
|
|
124
|
+
result = convert(
|
|
125
|
+
"/路径/论文.pdf",
|
|
126
|
+
api_key=os.environ["OPENAI_API_KEY"],
|
|
127
|
+
model="你的视觉模型名称",
|
|
128
|
+
base_url="http://127.0.0.1:8045/v1",
|
|
129
|
+
show_progress=True,
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
关闭 OCR:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
result = convert("/路径/报告.docx", ocr=False)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`convert()` 返回 `ConversionResult`:`files` 是成功输出的 `Path` 列表,`errors` 是输入路径到错误信息的字典。单个文件转换失败后会继续处理其他文件;无效输入路径、缺少密钥等配置错误会直接抛出异常。
|
|
140
|
+
|
|
141
|
+
Python 调用默认不显示终端进度,可用 `show_progress=True` 开启;也可通过 `results_dir="/路径/results"` 指定结果根目录。
|
|
142
|
+
|
|
143
|
+
## 输出规则
|
|
144
|
+
|
|
145
|
+
安装包后,命令及 Python 调用默认将结果写入**当前工作目录**的 `results/`,不会写进包的安装目录。所需目录会自动创建。
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
单文件输入:/路径/论文.pdf
|
|
149
|
+
输出: results/论文.md
|
|
150
|
+
|
|
151
|
+
文件夹输入:/路径/资料/
|
|
152
|
+
├── 论文.pdf
|
|
153
|
+
├── 报告.docx
|
|
154
|
+
└── 数据.xlsx
|
|
155
|
+
输出: results/资料/
|
|
156
|
+
├── 论文.md
|
|
157
|
+
├── 报告.md
|
|
158
|
+
└── 数据.md
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
保留源文件名,只把扩展名改为 `.md`,例如 `报告.v2.pdf` → `报告.v2.md`。同一输出目录下的同名结果会覆盖,`报告.pdf` 与 `报告.docx` 也会写入同一个 `报告.md`。
|
|
162
|
+
|
|
163
|
+
文件夹只处理当前层,按文件名排序逐个转换,不进入子文件夹,跳过不支持的扩展名。终端显示文件序号、耗时及成功/失败数量,不显示页数或百分比;存在转换失败时命令以非零状态退出。
|
|
164
|
+
|
|
165
|
+
## 命令参数
|
|
166
|
+
|
|
167
|
+
| 参数 | 说明 |
|
|
168
|
+
| --- | --- |
|
|
169
|
+
| `file` | 文件或文件夹路径,必填 |
|
|
170
|
+
| `--model` | 指定视觉模型,覆盖环境变量 |
|
|
171
|
+
| `--base-url` | 指定 OpenAI 兼容接口基础地址,覆盖环境变量 |
|
|
172
|
+
| `--no-ocr` | 关闭 OCR,不调用模型;默认开启 OCR |
|
|
173
|
+
| `-h`、`--help` | 查看帮助 |
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
tomarkdown "/路径/文档.pdf" --model "你的视觉模型名称"
|
|
177
|
+
tomarkdown "/路径/资料" --no-ocr
|
|
178
|
+
tomarkdown --help
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## 支持的内容与处理流程
|
|
182
|
+
|
|
183
|
+
支持 PDF、Word(`.docx`)、Excel(`.xlsx`、`.xls`)、PowerPoint(`.pptx`)、图片、HTML,以及 TXT、CSV、JSON、XML 等文本格式。旧版 Word `.doc` 不在批量处理范围内。
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
这一页 PDF
|
|
187
|
+
├── 文字层 → 直接提取
|
|
188
|
+
└── 图片 → LLM 识别
|
|
189
|
+
↓
|
|
190
|
+
合并成 Markdown
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
扫描件中的正文通常属于图片,需要 OCR。复杂排版、表格和公式的还原取决于文件内容、MarkItDown 插件及模型。JSON、XML 等文本主要保留原文。OCR 插件内部未抛出的识别错误不会计入失败数量。
|
|
194
|
+
|
|
195
|
+
## 从源码运行与构建
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
git clone https://github.com/MingWangD/ToMarkdown.git
|
|
199
|
+
cd ToMarkdown
|
|
200
|
+
uv sync
|
|
201
|
+
|
|
202
|
+
# 安装包后的命令入口
|
|
203
|
+
uv run tomarkdown "/路径/资料"
|
|
204
|
+
|
|
205
|
+
# 保留原有脚本入口:读取项目 .env,结果固定保存到项目 results/
|
|
206
|
+
uv run python to_md.py "/路径/资料"
|
|
207
|
+
|
|
208
|
+
# 生成 wheel 和源码包,输出到 dist/
|
|
209
|
+
uv build --no-sources
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
核心代码位于 `src/tomarkdown/`。根目录的 `to_md.py` 和 `ui.py` 保留为原有用法的兼容入口。发布包不包含 `.env`、转换结果或本地样例文件。
|
|
213
|
+
|
|
214
|
+
## 维护者:首次发布到 PyPI
|
|
215
|
+
|
|
216
|
+
仓库提供 `.github/workflows/publish.yml`,通过 GitHub Actions 可信发布,无需配置 PyPI API Token。
|
|
217
|
+
|
|
218
|
+
首次使用时,登录 PyPI 并打开[账号的 Publishing 页面](https://pypi.org/manage/account/publishing/),添加 GitHub 待发布项目,填写:
|
|
219
|
+
|
|
220
|
+
| 字段 | 值 |
|
|
221
|
+
| --- | --- |
|
|
222
|
+
| PyPI Project Name | `ming-tomarkdown` |
|
|
223
|
+
| Owner | `MingWangD` |
|
|
224
|
+
| Repository name | `ToMarkdown` |
|
|
225
|
+
| Workflow name | `publish.yml` |
|
|
226
|
+
| Environment name | `pypi` |
|
|
227
|
+
|
|
228
|
+
保存后,在 GitHub Actions 的 **Publish to PyPI** 流程中选择 **Run workflow**,勾选 **Publish to PyPI after building** 即可发布;不勾选时仅检查构建。此后发布新的 GitHub Release 也会自动触发 PyPI 发布。发布新版本前需先更新 `pyproject.toml` 中的版本号及依赖锁定文件。
|
|
229
|
+
|
|
230
|
+
首次发布成功后,其他人就可以用 `uv pip install ming-tomarkdown` 安装。
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ming-tomarkdown"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Convert documents and folders to Markdown with vision OCR"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"python-dotenv>=1.0.0",
|
|
9
|
+
"markitdown-ocr>=0.1.0",
|
|
10
|
+
"markitdown[all]>=0.1.5",
|
|
11
|
+
"openai>=3.31.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
tomarkdown = "tomarkdown.cli:main"
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Repository = "https://github.com/MingWangD/ToMarkdown"
|
|
19
|
+
Issues = "https://github.com/MingWangD/ToMarkdown/issues"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["hatchling>=1.27"]
|
|
23
|
+
build-backend = "hatchling.build"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/tomarkdown"]
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build.targets.sdist]
|
|
29
|
+
include = ["/src/tomarkdown", "/README.md", "/pyproject.toml", "/to_md.py", "/ui.py"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""tomarkdown 命令入口。"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .core import convert
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main(*, results_dir: Path | None = None) -> int:
|
|
10
|
+
parser = argparse.ArgumentParser(description="将文件或文件夹转换为 Markdown,默认开启图片 OCR。")
|
|
11
|
+
parser.add_argument("file", type=Path, help="文件或文件夹路径(文件夹只处理当前层)")
|
|
12
|
+
parser.add_argument("--no-ocr", action="store_true", help="仅进行常规提取,不调用模型")
|
|
13
|
+
parser.add_argument("--model", help="视觉模型名称;默认读取 MODEL、OPENAI_MODEL 或 GEMINI_MODEL")
|
|
14
|
+
parser.add_argument("--base-url", help="OpenAI 兼容接口地址,也可设置 OPENAI_BASE_URL")
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
try:
|
|
17
|
+
result = convert(
|
|
18
|
+
args.file,
|
|
19
|
+
ocr=not args.no_ocr,
|
|
20
|
+
model=args.model,
|
|
21
|
+
base_url=args.base_url,
|
|
22
|
+
results_dir=results_dir,
|
|
23
|
+
show_progress=True,
|
|
24
|
+
)
|
|
25
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
26
|
+
parser.error(str(exc))
|
|
27
|
+
if not result.files and not result.errors:
|
|
28
|
+
print("文件夹中没有可转换的文件。")
|
|
29
|
+
else:
|
|
30
|
+
print(f"处理结束:成功 {len(result.files)} 个,失败 {len(result.errors)} 个。")
|
|
31
|
+
return 1 if result.errors else 0
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""文件转换与批量转换,可独立于命令行使用。"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from contextlib import ExitStack, nullcontext
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
from markitdown import MarkItDown
|
|
11
|
+
from openai import OpenAI
|
|
12
|
+
|
|
13
|
+
from .ui import progress
|
|
14
|
+
|
|
15
|
+
SUPPORTED_EXTENSIONS = {
|
|
16
|
+
".pdf", ".docx", ".xlsx", ".xls", ".pptx",
|
|
17
|
+
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tif", ".tiff", ".webp",
|
|
18
|
+
".html", ".htm", ".txt", ".csv", ".json", ".xml",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ConversionResult:
|
|
24
|
+
"""files 为成功保存的路径,errors 为输入路径与错误信息的对应关系。"""
|
|
25
|
+
|
|
26
|
+
files: list[Path] = field(default_factory=list)
|
|
27
|
+
errors: dict[Path, str] = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def convert(
|
|
31
|
+
source: str | Path,
|
|
32
|
+
*,
|
|
33
|
+
ocr: bool = True,
|
|
34
|
+
model: str | None = None,
|
|
35
|
+
api_key: str | None = None,
|
|
36
|
+
base_url: str | None = None,
|
|
37
|
+
results_dir: str | Path | None = None,
|
|
38
|
+
show_progress: bool = False,
|
|
39
|
+
) -> ConversionResult:
|
|
40
|
+
"""转换文件或文件夹,默认写入当前目录的 results;批量时保留文件夹名。
|
|
41
|
+
|
|
42
|
+
默认读取当前目录的 .env。显式参数优先于环境变量。
|
|
43
|
+
配置和输入路径错误会抛出异常;单文件转换错误记录在返回值的 errors 中,
|
|
44
|
+
批量处理会继续。默认不显示终端进度。
|
|
45
|
+
"""
|
|
46
|
+
load_dotenv(Path.cwd() / ".env")
|
|
47
|
+
input_path = Path(source).expanduser().resolve()
|
|
48
|
+
output_dir = Path(results_dir).expanduser().resolve() if results_dir is not None else Path.cwd() / "results"
|
|
49
|
+
if input_path.is_dir():
|
|
50
|
+
output_dir /= input_path.name
|
|
51
|
+
sources = sorted(
|
|
52
|
+
path for path in input_path.iterdir()
|
|
53
|
+
if path.is_file() and path.suffix.lower() in SUPPORTED_EXTENSIONS
|
|
54
|
+
)
|
|
55
|
+
elif input_path.is_file():
|
|
56
|
+
sources = [input_path]
|
|
57
|
+
else:
|
|
58
|
+
raise FileNotFoundError(f"输入路径不存在或不是文件/文件夹:{input_path}")
|
|
59
|
+
|
|
60
|
+
result = ConversionResult()
|
|
61
|
+
if not sources:
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
model = model or os.getenv("MODEL") or os.getenv("OPENAI_MODEL") or os.getenv("GEMINI_MODEL") or "gemini-3.8-flash-high"
|
|
65
|
+
api_key = api_key or os.getenv("GEMINI_API_KEY") or os.getenv("OPENAI_API_KEY")
|
|
66
|
+
base_url = base_url or os.getenv("OPENAI_BASE_URL", "http://127.0.0.1:8045/v1")
|
|
67
|
+
if ocr and not api_key:
|
|
68
|
+
raise ValueError("OCR 需要 API Key,请配置 OPENAI_API_KEY 或传入 api_key。")
|
|
69
|
+
|
|
70
|
+
with ExitStack() as stack:
|
|
71
|
+
if ocr:
|
|
72
|
+
client = stack.enter_context(OpenAI(base_url=base_url, api_key=api_key))
|
|
73
|
+
converter = MarkItDown(
|
|
74
|
+
enable_plugins=True,
|
|
75
|
+
llm_client=client,
|
|
76
|
+
llm_model=model,
|
|
77
|
+
llm_prompt=(
|
|
78
|
+
"提取图片中所有文字,保留原文语言、阅读顺序、标题、表格和公式。"
|
|
79
|
+
"以 Markdown 输出,公式使用 LaTeX。"
|
|
80
|
+
"只返回提取的内容,不添加解释,不用代码块包裹全文。"
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
converter = MarkItDown()
|
|
85
|
+
|
|
86
|
+
for index, path in enumerate(sources, start=1):
|
|
87
|
+
output = output_dir / f"{path.stem}.md"
|
|
88
|
+
try:
|
|
89
|
+
if path == output:
|
|
90
|
+
raise ValueError("输入文件不能与输出文件相同。")
|
|
91
|
+
status = progress(f"[{index}/{len(sources)}] 正在转换 {path.name}") if show_progress else nullcontext()
|
|
92
|
+
with status:
|
|
93
|
+
converted = converter.convert(str(path))
|
|
94
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
output.write_text(converted.text_content + "\n", encoding="utf-8")
|
|
96
|
+
result.files.append(output)
|
|
97
|
+
if show_progress:
|
|
98
|
+
print(f"已保存:{output}")
|
|
99
|
+
except Exception as exc:
|
|
100
|
+
result.errors[path] = str(exc)
|
|
101
|
+
if show_progress:
|
|
102
|
+
print(f"转换失败:{path.name}:{exc}", file=sys.stderr)
|
|
103
|
+
return result
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""终端进度显示:转换期间显示动画和耗时,不估算完成百分比。"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from itertools import cycle
|
|
7
|
+
from threading import Event, Thread
|
|
8
|
+
from typing import Iterator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@contextmanager
|
|
12
|
+
def progress(message: str) -> Iterator[None]:
|
|
13
|
+
"""在终端显示等待动画;重定向输出时只打印开始和结束状态。"""
|
|
14
|
+
started = time.monotonic()
|
|
15
|
+
stopped = Event()
|
|
16
|
+
terminal = sys.stderr.isatty()
|
|
17
|
+
|
|
18
|
+
def animate() -> None:
|
|
19
|
+
for frame in cycle("|/-\\"):
|
|
20
|
+
elapsed = time.monotonic() - started
|
|
21
|
+
print(f"\r{frame} {message}({elapsed:.1f}s)", end="", file=sys.stderr, flush=True)
|
|
22
|
+
if stopped.wait(0.1):
|
|
23
|
+
break
|
|
24
|
+
|
|
25
|
+
worker = Thread(target=animate, daemon=True) if terminal else None
|
|
26
|
+
if worker:
|
|
27
|
+
worker.start()
|
|
28
|
+
else:
|
|
29
|
+
print(message, file=sys.stderr)
|
|
30
|
+
|
|
31
|
+
completed = False
|
|
32
|
+
try:
|
|
33
|
+
yield
|
|
34
|
+
completed = True
|
|
35
|
+
finally:
|
|
36
|
+
stopped.set()
|
|
37
|
+
if worker:
|
|
38
|
+
worker.join()
|
|
39
|
+
status = "处理完成" if completed else "处理未完成"
|
|
40
|
+
prefix = "\r\033[2K" if terminal else ""
|
|
41
|
+
elapsed = time.monotonic() - started
|
|
42
|
+
print(f"{prefix}{status},耗时 {elapsed:.1f}s", file=sys.stderr, flush=True)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""兼容原有脚本用法,结果仍保存到本项目的 results 文件夹。"""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
from tomarkdown.cli import main
|
|
7
|
+
|
|
8
|
+
if __name__ == "__main__":
|
|
9
|
+
project_dir = Path(__file__).resolve().parent
|
|
10
|
+
load_dotenv(project_dir / ".env")
|
|
11
|
+
raise SystemExit(main(results_dir=project_dir / "results"))
|