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.
@@ -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,5 @@
1
+ """将文件或文件夹转换为 Markdown。"""
2
+
3
+ from .core import ConversionResult, convert
4
+
5
+ __all__ = ["ConversionResult", "convert"]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -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"))
@@ -0,0 +1,5 @@
1
+ """兼容原有进度显示导入方式。"""
2
+
3
+ from tomarkdown.ui import progress
4
+
5
+ __all__ = ["progress"]