multi-ocr-py 0.1.0__tar.gz → 0.2.2__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.
Files changed (40) hide show
  1. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/PKG-INFO +17 -17
  2. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/README.md +16 -16
  3. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-cli-args-design.md +2 -4
  4. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-concurrency-design.md +2 -2
  5. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-engine-refactor-design.md +1 -3
  6. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-liteparse-engine-design.md +1 -2
  7. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-model-shorthand-design.md +1 -9
  8. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-pdf-progress-design.md +2 -2
  9. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-16-src-package-restructure-design.md +0 -1
  10. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/pyproject.toml +4 -1
  11. multi_ocr_py-0.2.2/release.sh +36 -0
  12. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/__init__.py +2 -0
  13. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/batch.py +2 -2
  14. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/cli.py +0 -3
  15. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/engines/__init__.py +4 -5
  16. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/engines/base.py +5 -1
  17. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/tests/test_engines.py +0 -9
  18. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/uv.lock +2 -2
  19. multi_ocr_py-0.1.0/src/multi_ocr/engines/dashscope.py +0 -85
  20. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/.gitignore +0 -0
  21. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/.python-version +0 -0
  22. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/LICENSE +0 -0
  23. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/liteparse/Markdown-Output.md +0 -0
  24. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/liteparse/library-use.md +0 -0
  25. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/liteparse/ocr-config.md +0 -0
  26. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-batch-pdf-concurrency-design.md +0 -0
  27. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-batch-processing-design.md +0 -0
  28. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-multi-ocr-design.md +0 -0
  29. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/docs/superpowers/specs/2026-07-15-ollama-engine-design.md +0 -0
  30. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/engines/liteparse.py +0 -0
  31. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/engines/ollama.py +0 -0
  32. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/engines/siliconflow.py +0 -0
  33. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/pdf_utils.py +0 -0
  34. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/src/multi_ocr/single.py +0 -0
  35. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/tests/1.jpg +0 -0
  36. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/tests/2.pdf +0 -0
  37. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/tests/conftest.py +0 -0
  38. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/tests/test_batch.py +0 -0
  39. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/tests/test_pdf_utils.py +0 -0
  40. {multi_ocr_py-0.1.0 → multi_ocr_py-0.2.2}/tests/test_single.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multi-ocr-py
3
- Version: 0.1.0
3
+ Version: 0.2.2
4
4
  Summary: 多引擎 OCR 工具包:将 PDF 和图片转换为 Markdown,支持 CLI 全局安装和 SDK 依赖两种使用方式
5
5
  Project-URL: Repository, https://github.com/BlackBoxRecorder/multi-ocr
6
6
  Author-email: Yinnan <im.yinnan@outlook.com>
@@ -33,30 +33,32 @@ Description-Content-Type: text/markdown
33
33
 
34
34
  ## 支持的引擎
35
35
 
36
- | 引擎 | 说明 | 类型 |
37
- |---|---|---|
38
- | SiliconFlow + DeepSeek-OCR | 云端 OCR,精度高 | API |
39
- | SiliconFlow + PaddleOCR-VL-1.5 | 云端 OCR,轻量快速 | API |
40
- | DashScope + Qwen-VL-OCR | 阿里云百炼 | API |
41
- | LiteParse | 本地 PDF 解析,无需联网 | 本地 |
42
- | Ollama + DeepSeek-OCR | 本地部署 OCR | 本地 |
36
+ | 引擎 | 说明 | 类型 | 场景 | 费用 |
37
+ |---|---|---|---|---|
38
+ | SiliconFlow + DeepSeek-OCR | 云端 OCR,精度高 | API | 图片+PDF | 免费 |
39
+ | SiliconFlow + PaddleOCR-VL-1.5 | 云端 OCR,轻量快速 | API | 图片+PDF | 免费 |
40
+ | LiteParse | 本地 PDF 解析,无需联网 | 本地 | PDF | 免费 |
41
+ | Ollama + DeepSeek-OCR | 本地部署 OCR | 本地 | 图片+PDF | 免费 |
42
+
43
+
44
+ > 本地运行 ollama 参考:https://ollama.com/library/deepseek-ocr
43
45
 
44
46
  ## 安装
45
47
 
46
48
  ### CLI 全局安装
47
49
 
48
50
  ```bash
49
- uv tool install multi-ocr
51
+ uv tool install multi-ocr-py
50
52
  # 或
51
- pip install multi-ocr
53
+ pip install multi-ocr-py
52
54
  ```
53
55
 
54
56
  ### SDK 依赖安装
55
57
 
56
58
  ```bash
57
- pip install multi-ocr
59
+ pip install multi-ocr-py
58
60
  # 或
59
- uv add multi-ocr
61
+ uv add multi-ocr-py
60
62
  ```
61
63
 
62
64
  ## CLI 使用
@@ -66,13 +68,13 @@ uv add multi-ocr
66
68
  multi-ocr document.pdf
67
69
 
68
70
  # 指定引擎和页码范围
69
- multi-ocr document.pdf --model dashscope-qwen-vl-ocr --pages 1-5
71
+ multi-ocr document.pdf --model liteparse --pages 1-5
70
72
 
71
- # 批量处理目录
73
+ # 批量处理目录,4个并发
72
74
  multi-ocr ./scans/ --model silicon-deepseek-ocr -j 4
73
75
 
74
76
  # 查看帮助
75
- multi-ocr --help
77
+ multi-ocr -h
76
78
  ```
77
79
 
78
80
  ### 环境变量
@@ -80,10 +82,8 @@ multi-ocr --help
80
82
  | 引擎 | 环境变量 |
81
83
  |---|---|
82
84
  | SiliconFlow | `SILICONFLOW_API_KEY` |
83
- | DashScope | `DASHSCOPE_API_KEY` |
84
85
  | Ollama | `OLLAMA_BASE_URL`(可选,默认 http://127.0.0.1:11434)|
85
86
 
86
- 也可通过 `--api-key` 参数直接传入。
87
87
 
88
88
  ## SDK 使用
89
89
 
@@ -7,30 +7,32 @@
7
7
 
8
8
  ## 支持的引擎
9
9
 
10
- | 引擎 | 说明 | 类型 |
11
- |---|---|---|
12
- | SiliconFlow + DeepSeek-OCR | 云端 OCR,精度高 | API |
13
- | SiliconFlow + PaddleOCR-VL-1.5 | 云端 OCR,轻量快速 | API |
14
- | DashScope + Qwen-VL-OCR | 阿里云百炼 | API |
15
- | LiteParse | 本地 PDF 解析,无需联网 | 本地 |
16
- | Ollama + DeepSeek-OCR | 本地部署 OCR | 本地 |
10
+ | 引擎 | 说明 | 类型 | 场景 | 费用 |
11
+ |---|---|---|---|---|
12
+ | SiliconFlow + DeepSeek-OCR | 云端 OCR,精度高 | API | 图片+PDF | 免费 |
13
+ | SiliconFlow + PaddleOCR-VL-1.5 | 云端 OCR,轻量快速 | API | 图片+PDF | 免费 |
14
+ | LiteParse | 本地 PDF 解析,无需联网 | 本地 | PDF | 免费 |
15
+ | Ollama + DeepSeek-OCR | 本地部署 OCR | 本地 | 图片+PDF | 免费 |
16
+
17
+
18
+ > 本地运行 ollama 参考:https://ollama.com/library/deepseek-ocr
17
19
 
18
20
  ## 安装
19
21
 
20
22
  ### CLI 全局安装
21
23
 
22
24
  ```bash
23
- uv tool install multi-ocr
25
+ uv tool install multi-ocr-py
24
26
  # 或
25
- pip install multi-ocr
27
+ pip install multi-ocr-py
26
28
  ```
27
29
 
28
30
  ### SDK 依赖安装
29
31
 
30
32
  ```bash
31
- pip install multi-ocr
33
+ pip install multi-ocr-py
32
34
  # 或
33
- uv add multi-ocr
35
+ uv add multi-ocr-py
34
36
  ```
35
37
 
36
38
  ## CLI 使用
@@ -40,13 +42,13 @@ uv add multi-ocr
40
42
  multi-ocr document.pdf
41
43
 
42
44
  # 指定引擎和页码范围
43
- multi-ocr document.pdf --model dashscope-qwen-vl-ocr --pages 1-5
45
+ multi-ocr document.pdf --model liteparse --pages 1-5
44
46
 
45
- # 批量处理目录
47
+ # 批量处理目录,4个并发
46
48
  multi-ocr ./scans/ --model silicon-deepseek-ocr -j 4
47
49
 
48
50
  # 查看帮助
49
- multi-ocr --help
51
+ multi-ocr -h
50
52
  ```
51
53
 
52
54
  ### 环境变量
@@ -54,10 +56,8 @@ multi-ocr --help
54
56
  | 引擎 | 环境变量 |
55
57
  |---|---|
56
58
  | SiliconFlow | `SILICONFLOW_API_KEY` |
57
- | DashScope | `DASHSCOPE_API_KEY` |
58
59
  | Ollama | `OLLAMA_BASE_URL`(可选,默认 http://127.0.0.1:11434)|
59
60
 
60
- 也可通过 `--api-key` 参数直接传入。
61
61
 
62
62
  ## SDK 使用
63
63
 
@@ -49,19 +49,18 @@ python main.py <path> [--model MODEL] [--pages PAGES] [--api-key KEY] [--ollama-
49
49
  |------|---------------------------|---------------------------|
50
50
  | liteparse | ❌ 不支持 | ✅ 支持 |
51
51
  | siliconflow | ✅ 支持 | ❌ 不支持 |
52
- | dashscope | ✅ 支持 | ❌ 不支持 |
53
52
  | ollama | ✅ 支持 | ❌ 不支持 |
54
53
 
55
54
  ### 单文件模式下的自动转换
56
55
 
57
- | 输入类型 | liteparse | 其他引擎 (siliconflow/dashscope/ollama) |
56
+ | 输入类型 | liteparse | 其他引擎 (siliconflow/ollama) |
58
57
  |---------|----------|---------------------------------------|
59
58
  | 图片 | 转为单页 PDF → `parse_pdf()` | 直接 `parse_image()` |
60
59
  | PDF | 直接 `parse_pdf()` | 拆为图片 → `parse_image()` 逐页识别 |
61
60
 
62
61
  ### 批量模式下的自动转换
63
62
 
64
- | 输入类型 | liteparse | 其他引擎 (siliconflow/dashscope/ollama) |
63
+ | 输入类型 | liteparse | 其他引擎 (siliconflow/ollama) |
65
64
  |---------|----------|---------------------------------------|
66
65
  | 图片目录 | 合并为 PDF → `parse_pdf()` | 逐张 `parse_image()` → 合并输出 |
67
66
  | PDF 目录 | 逐文件 `parse_pdf()` | 逐文件拆为图片 → `parse_image()` 逐页识别 |
@@ -128,7 +127,6 @@ python main.py <path> [--model MODEL] [--pages PAGES] [--api-key KEY] [--ollama-
128
127
  | `ocr.py` | `recognize()` → `parse_image()` 调用更新 |
129
128
  | `engines/base.py` | `recognize()` 重命名为 `parse_image()` |
130
129
  | `engines/siliconflow.py` | `recognize()` 重命名为 `parse_image()` |
131
- | `engines/dashscope.py` | `recognize()` 重命名为 `parse_image()` |
132
130
  | `engines/ollama.py` | `recognize()` 重命名为 `parse_image()` |
133
131
  | `engines/liteparse.py` | `recognize()` → `parse_image()` 或抛出 NotImplementedError(如有实现) |
134
132
  | `tests/test_batch.py` | 新增混合目录报错测试;更新对应测试用例;`recognize()` → `parse_image()` |
@@ -67,7 +67,7 @@ with ThreadPoolExecutor(max_workers=concurrency) as executor:
67
67
 
68
68
  ### 引擎线程安全
69
69
 
70
- - API 引擎(SiliconFlow、DashScope、Ollama):HTTP 客户端 `OpenAI` 实例本身是线程安全的,多个线程共享同一引擎实例并发调用 `parse_image()` 没有问题
70
+ - API 引擎(SiliconFlow、Ollama):HTTP 客户端 `OpenAI` 实例本身是线程安全的,多个线程共享同一引擎实例并发调用 `parse_image()` 没有问题
71
71
  - LiteParse:该引擎覆盖了 `parse_pdf()`,不走默认逐页实现,因此不受并发改动影响
72
72
 
73
73
  ### 特殊情况处理
@@ -117,7 +117,7 @@ python main.py ./images
117
117
  | `engines/base.py` | `OCREngine.parse_pdf()` | `concurrency: int = 1` |
118
118
  | `main.py` | `main()` | 解析 `-j` 参数并向下传递 |
119
119
 
120
- 引擎子类(SiliconFlow、DashScope、Ollama)**无需修改**,仅有 LiteParse 覆盖了 `parse_pdf()` 签名,需补上 `concurrency` 参数以保持兼容(内部忽略该参数)。
120
+ 引擎子类(SiliconFlow、Ollama)**无需修改**,仅有 LiteParse 覆盖了 `parse_pdf()` 签名,需补上 `concurrency` 参数以保持兼容(内部忽略该参数)。
121
121
 
122
122
  ## Testing
123
123
 
@@ -38,7 +38,6 @@ multi-ocr/
38
38
  │ ├── __init__.py # 注册表 + get_engine()
39
39
  │ ├── base.py # OCREngine(parse_image + parse_pdf 均为 abstract)
40
40
  │ ├── siliconflow.py # + parse_pdf
41
- │ ├── dashscope.py # + parse_pdf
42
41
  │ ├── ollama.py # + parse_pdf
43
42
  │ └── liteparse.py # 不变
44
43
  └── tests/
@@ -66,7 +65,7 @@ class OCREngine(ABC):
66
65
 
67
66
  ## 各引擎实现
68
67
 
69
- ### siliconflow / dashscope / ollama
68
+ ### siliconflow / ollama
70
69
 
71
70
  `parse_image` 不变。`parse_pdf` 统一模式:
72
71
 
@@ -197,7 +196,6 @@ def ocr_file(
197
196
  |------|----------|
198
197
  | `engines/base.py` | 修改 — `parse_pdf` 改为 abstractmethod |
199
198
  | `engines/siliconflow.py` | 修改 — 新增 `parse_pdf` |
200
- | `engines/dashscope.py` | 修改 — 新增 `parse_pdf` |
201
199
  | `engines/ollama.py` | 修改 — 新增 `parse_pdf` |
202
200
  | `engines/liteparse.py` | 不变 |
203
201
  | `pdf_utils.py` | 修改 — 新增 `split_pdf` |
@@ -4,7 +4,7 @@
4
4
 
5
5
  新增 LiteParse 作为本地 PDF 解析引擎。LiteParse 是一个基于 Rust 的开源文档解析库,Python 包 `liteparse` 可直接调用,无需 API Key、无需联网,能直接解析 PDF 并输出 markdown。
6
6
 
7
- 与现有云端引擎(SiliconFlow、DashScope)不同,LiteParse **仅处理 PDF**,不处理图片。同时,本次统一所有引擎的输出格式为 markdown,保存扩展名改为 `.md`。
7
+ 与现有云端引擎(如 SiliconFlow)不同,LiteParse **仅处理 PDF**,不处理图片。同时,本次统一所有引擎的输出格式为 markdown,保存扩展名改为 `.md`。
8
8
 
9
9
  ## 引擎接口扩展
10
10
 
@@ -58,7 +58,6 @@ class LiteParseEngine(OCREngine):
58
58
 
59
59
  ```python
60
60
  _registry: dict[str, type[OCREngine]] = {
61
- "dashscope": DashScopeEngine,
62
61
  "siliconflow": SiliconFlowEngine,
63
62
  "liteparse": LiteParseEngine,
64
63
  }
@@ -10,14 +10,12 @@
10
10
 
11
11
  ```bash
12
12
  python main.py input.pdf --provider siliconflow --model deepseek-ai/DeepSeek-OCR
13
- python main.py input.pdf --provider dashscope --model qwen3.5-ocr
14
13
  ```
15
14
 
16
15
  改为缩写后:
17
16
 
18
17
  ```bash
19
18
  python main.py input.pdf --model silicon-deepseek-ocr
20
- python main.py input.pdf --model dashscope-qwen-ocr
21
19
  ```
22
20
 
23
21
  ## MODEL_MAP 定义
@@ -28,8 +26,6 @@ python main.py input.pdf --model dashscope-qwen-ocr
28
26
  MODEL_MAP: dict[str, tuple[str, str, str]] = {
29
27
  "silicon-deepseek-ocr": ("siliconflow", "deepseek-ai/DeepSeek-OCR", "SiliconFlow + DeepSeek-OCR"),
30
28
  "silicon-paddle-ocr": ("siliconflow", "PaddlePaddle/PaddleOCR-VL-1.5", "SiliconFlow + PaddleOCR-VL-1.5"),
31
- "dashscope-qwen-ocr": ("dashscope", "qwen3.5-ocr", "DashScope + Qwen-OCR"),
32
- "dashscope-qwen-vl-ocr": ("dashscope", "qwen-vl-ocr", "DashScope + Qwen-VL-OCR"),
33
29
  }
34
30
  ```
35
31
 
@@ -64,16 +60,13 @@ DEFAULT_MODEL = "silicon-deepseek-ocr"
64
60
  可用模型:
65
61
  silicon-deepseek-ocr → SiliconFlow + DeepSeek-OCR
66
62
  silicon-paddle-ocr → SiliconFlow + PaddleOCR-VL-1.5
67
- dashscope-qwen-ocr → DashScope + Qwen-OCR
68
- dashscope-qwen-vl-ocr → DashScope + Qwen-VL-OCR
69
63
  ```
70
64
 
71
65
  `--api-key` 的 help 提示用户根据模型配置对应环境变量:
72
66
 
73
67
  ```
74
68
  --api-key API_KEY API Key(根据 --model 自动读取对应环境变量:
75
- siliconflow 模型 → SILICONFLOW_API_KEY
76
- dashscope 模型 → DASHSCOPE_API_KEY)
69
+ siliconflow 模型 → SILICONFLOW_API_KEY
77
70
  ```
78
71
 
79
72
  ## 主流程变更(`main.py`)
@@ -106,7 +99,6 @@ ocr_file(...) → 执行 OCR
106
99
  | 场景 | 预期 |
107
100
  |------|------|
108
101
  | `--model silicon-deepseek-ocr` | 正确推导 provider=SiliconFlow,使用 DeepSeek-OCR |
109
- | `--model dashscope-qwen-ocr` | 正确推导 provider=DashScope,使用 qwen3.5-ocr |
110
102
  | `--model invalid-model` | 报错退出,列出所有可用缩写 |
111
103
  | 不传 `--model` | 使用默认值 `silicon-deepseek-ocr` |
112
104
  | 未设置对应环境变量 | 提示设置对应环境变量(如 SILICONFLOW_API_KEY) |
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## 背景
8
8
 
9
- 当前 `ocr_file()` 对 PDF 逐页调用 OCR API 时没有任何进度提示。批量模式仅有文件级 tqdm,不显示页级进度。三个引擎(SiliconFlow / DashScope / Ollama)各自实现了完全相同的 `parse_pdf()` 逐页迭代逻辑。
9
+ 当前 `ocr_file()` 对 PDF 逐页调用 OCR API 时没有任何进度提示。批量模式仅有文件级 tqdm,不显示页级进度。三个引擎(SiliconFlow / Ollama)各自实现了完全相同的 `parse_pdf()` 逐页迭代逻辑。
10
10
 
11
11
  ## 各模式进度行为
12
12
 
@@ -28,7 +28,7 @@
28
28
  - 新增 `from typing import Callable`、`import shutil`、`from pdf_utils import split_pdf`
29
29
  - 方法体:`split_pdf()` → 逐页 `parse_image()` → 每页调用 `progress_callback()` → 合并结果
30
30
 
31
- ### 2. `engines/siliconflow.py` / `dashscope.py` / `ollama.py` — 删除重复代码
31
+ ### 2. `engines/siliconflow.py` / `ollama.py` — 删除重复代码
32
32
 
33
33
  - 删除各自的 `parse_pdf()` 方法(每个约 12 行)
34
34
  - 删除不再需要的 `import shutil` 和 `from pdf_utils import split_pdf`
@@ -20,7 +20,6 @@ multi-ocr/
20
20
  │ └── engines/
21
21
  │ ├── __init__.py # 引擎注册表 + get_engine() 工厂
22
22
  │ ├── base.py # OCREngine 抽象基类
23
- │ ├── dashscope.py # DashScope 引擎
24
23
  │ ├── liteparse.py # LiteParse 引擎(可选依赖)
25
24
  │ ├── ollama.py # Ollama 引擎
26
25
  │ └── siliconflow.py # SiliconFlow 引擎
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "multi-ocr-py"
3
- version = "0.1.0"
3
+ dynamic = ["version"]
4
4
  description = "多引擎 OCR 工具包:将 PDF 和图片转换为 Markdown,支持 CLI 全局安装和 SDK 依赖两种使用方式"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -42,6 +42,9 @@ build-backend = "hatchling.build"
42
42
  [tool.hatch.build.targets.wheel]
43
43
  packages = ["src/multi_ocr"]
44
44
 
45
+ [tool.hatch.version]
46
+ path = "src/multi_ocr/__init__.py"
47
+
45
48
  [tool.pytest.ini_options]
46
49
  pythonpath = ["src"]
47
50
 
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PART="${1:-minor}"
5
+
6
+ echo "==> multi-ocr 发布流程开始"
7
+
8
+ # 1. 检查 git 状态
9
+ if [ -n "$(git status --porcelain)" ]; then
10
+ echo "==> 错误: 工作区有未提交的变更"
11
+ exit 1
12
+ fi
13
+
14
+ # 2. 升级版本号
15
+ echo "==> 升级版本 ($PART)"
16
+ hatch version "$PART"
17
+ NEW_VERSION=$(hatch version)
18
+ echo " 新版本: $NEW_VERSION"
19
+
20
+ # 3. 构建
21
+ echo "==> 构建包"
22
+ hatch build
23
+
24
+ # 4. 发布到 PyPI(使用 UV_PUBLISH_TOKEN / UV_PUBLISH_PASSWORD)
25
+ echo "==> 发布到 PyPI"
26
+ uv publish
27
+
28
+ # 5. Git 提交 + Tag
29
+ echo "==> Git 提交 & 打标签"
30
+ git add -A
31
+ git commit -m "chore: release v${NEW_VERSION}"
32
+ git tag "v${NEW_VERSION}"
33
+ git push
34
+ git push --tags
35
+
36
+ echo "==> v${NEW_VERSION} 发布完成!"
@@ -23,3 +23,5 @@ __all__ = [
23
23
  "ocr_file",
24
24
  "ocr_directory",
25
25
  ]
26
+
27
+ __version__ = "0.2.2"
@@ -65,7 +65,7 @@ def _batch_images(
65
65
  ):
66
66
  idx = future_to_idx[future]
67
67
  results[idx] = future.result()
68
- merged = "\n\n".join(r for r in results if r is not None)
68
+ merged = engine.merge_results([r for r in results if r is not None])
69
69
  else:
70
70
  results = []
71
71
  for img_path in tqdm(
@@ -73,7 +73,7 @@ def _batch_images(
73
73
  ):
74
74
  text = engine.parse_image(img_path)
75
75
  results.append(text)
76
- merged = "\n\n".join(results)
76
+ merged = engine.merge_results(results)
77
77
 
78
78
  output_path = input_dir.with_suffix(".md")
79
79
  output_path.write_text(merged, encoding="utf-8")
@@ -2,7 +2,6 @@
2
2
 
3
3
  支持多种 OCR 引擎:
4
4
  - SiliconFlow(DeepSeek-OCR / PaddleOCR-VL-1.5)
5
- - DashScope(Qwen-VL-OCR)
6
5
  - LiteParse(本地 PDF 解析)
7
6
  - Ollama(本地 DeepSeek-OCR)
8
7
 
@@ -37,7 +36,6 @@ MODEL_MAP: dict[str, tuple[str, str, str]] = {
37
36
  "PaddlePaddle/PaddleOCR-VL-1.5",
38
37
  "SiliconFlow + PaddleOCR-VL-1.5",
39
38
  ),
40
- "dashscope-qwen-vl-ocr": ("dashscope", "qwen-vl-ocr", "DashScope + Qwen-VL-OCR"),
41
39
  "liteparse": ("liteparse", "", "LiteParse (本地 PDF 解析)"),
42
40
  "ollama-deepseek-ocr": (
43
41
  "ollama",
@@ -49,7 +47,6 @@ MODEL_MAP: dict[str, tuple[str, str, str]] = {
49
47
  # provider -> 环境变量名
50
48
  _PROVIDER_ENV: dict[str, str] = {
51
49
  "siliconflow": "SILICONFLOW_API_KEY",
52
- "dashscope": "DASHSCOPE_API_KEY",
53
50
  # ollama / liteparse 为本地引擎,不需要 API Key
54
51
  }
55
52
 
@@ -1,5 +1,4 @@
1
1
  from multi_ocr.engines.base import OCREngine
2
- from multi_ocr.engines.dashscope import DashScopeEngine
3
2
  from multi_ocr.engines.ollama import OllamaEngine
4
3
  from multi_ocr.engines.siliconflow import SiliconFlowEngine
5
4
 
@@ -14,7 +13,6 @@ except ImportError:
14
13
  # 引擎注册表:provider 名 -> Engine 类
15
14
  # 添加新提供商时只需在此注册
16
15
  _registry: dict[str, type[OCREngine]] = {
17
- "dashscope": DashScopeEngine,
18
16
  "ollama": OllamaEngine,
19
17
  "siliconflow": SiliconFlowEngine,
20
18
  }
@@ -24,7 +22,7 @@ if _HAS_LITEPARSE:
24
22
 
25
23
 
26
24
  def get_engine(
27
- provider: str, model: str, api_key: str, base_url: str | None = None
25
+ provider: str, model: str, api_key: str, base_url: str | None = None, **extra_kwargs
28
26
  ) -> OCREngine:
29
27
  """工厂函数:按 provider 名查找并实例化 OCR 引擎。
30
28
 
@@ -44,6 +42,7 @@ def get_engine(
44
42
  if engine_cls is None:
45
43
  available = ", ".join(_registry.keys())
46
44
  raise ValueError(f"未知的 OCR 提供商: {provider},当前可用: {available}")
45
+ kwargs: dict = dict(extra_kwargs)
47
46
  if base_url is not None:
48
- return engine_cls(model=model, api_key=api_key, base_url=base_url)
49
- return engine_cls(model=model, api_key=api_key)
47
+ kwargs["base_url"] = base_url
48
+ return engine_cls(model=model, api_key=api_key, **kwargs)
@@ -10,6 +10,10 @@ from multi_ocr.pdf_utils import split_pdf
10
10
  class OCREngine(ABC):
11
11
  """OCR 引擎抽象基类。所有 OCR 引擎必须实现此接口。"""
12
12
 
13
+ def merge_results(self, results: list[str]) -> str:
14
+ """合并多条返回结果。默认用双换行分隔,JSON 引擎可覆写为数组格式。"""
15
+ return "\n\n".join(results)
16
+
13
17
  @abstractmethod
14
18
  def parse_image(self, image_path: Path) -> str:
15
19
  """对单张图片进行 OCR,返回 markdown 文本。
@@ -56,7 +60,7 @@ class OCREngine(ABC):
56
60
  results.append(f"--- 第 {label} 页 ---\n{text}")
57
61
  if progress_callback:
58
62
  progress_callback()
59
- return "\n\n".join(results)
63
+ return self.merge_results(results)
60
64
  finally:
61
65
  if image_paths:
62
66
  shutil.rmtree(image_paths[0].parent, ignore_errors=True)
@@ -19,15 +19,6 @@ class TestGetEngine:
19
19
  assert isinstance(engine, SiliconFlowEngine)
20
20
  assert engine._model == "test-model"
21
21
 
22
- def test_known_provider_dashscope(self) -> None:
23
- engine = get_engine(
24
- provider="dashscope", model="qwen-vl-ocr", api_key="test-key"
25
- )
26
- from multi_ocr.engines.dashscope import DashScopeEngine
27
-
28
- assert isinstance(engine, DashScopeEngine)
29
- assert engine._model == "qwen-vl-ocr"
30
-
31
22
  def test_unknown_provider(self) -> None:
32
23
  with pytest.raises(ValueError, match="未知的 OCR 提供商"):
33
24
  get_engine(provider="nonexistent", model="m", api_key="k")
@@ -233,9 +233,9 @@ wheels = [
233
233
  ]
234
234
 
235
235
  [[package]]
236
- name = "multi-ocr"
236
+ name = "multi-ocr-py"
237
237
  version = "0.1.0"
238
- source = { virtual = "." }
238
+ source = { editable = "." }
239
239
  dependencies = [
240
240
  { name = "liteparse" },
241
241
  { name = "ollama" },
@@ -1,85 +0,0 @@
1
- import base64
2
- import re
3
- from pathlib import Path
4
-
5
- from openai import OpenAI
6
-
7
- from multi_ocr.engines.base import OCREngine
8
-
9
-
10
- class DashScopeEngine(OCREngine):
11
- """DashScope (阿里云百炼) OCR 引擎,兼容 OpenAI SDK。
12
-
13
- 支持 Qwen-VL-OCR 模型,通过 OpenAI 兼容接口调用。
14
- 参考文档:https://help.aliyun.com/zh/model-studio/qwen-ocr
15
-
16
- 默认为通用 OCR 场景,如需使用内置任务(高精识别、表格解析、信息抽取等),
17
- 可参照文档在 prompt 中传入对应指令。
18
- """
19
-
20
- BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
21
-
22
- # 模型名称 -> 提示词映射(按前缀匹配)
23
- _PROMPT_MAP: dict[str, str] = {
24
- "qwen-vl-ocr": "OCR this image and output in markdown format.",
25
- }
26
-
27
- _FALLBACK_PROMPT = "OCR this image and output in markdown format."
28
-
29
- def __init__(self, model: str, api_key: str, base_url: str | None = None) -> None:
30
- self._model = model
31
- self._client = OpenAI(
32
- api_key=api_key,
33
- base_url=base_url or self.BASE_URL,
34
- timeout=120.0,
35
- )
36
-
37
- def _get_prompt(self) -> str:
38
- """根据模型名称匹配对应的提示词,未匹配则使用默认提示词。"""
39
- for prefix, prompt in self._PROMPT_MAP.items():
40
- if prefix in self._model:
41
- return prompt
42
- return self._FALLBACK_PROMPT
43
-
44
- def parse_image(self, image_path: Path) -> str:
45
- with open(image_path, "rb") as f:
46
- image_data = base64.b64encode(f.read()).decode("utf-8")
47
-
48
- # 根据扩展名确定 MIME 类型
49
- suffix = image_path.suffix.lower()
50
- mime_map = {".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg"}
51
- mime_type = mime_map.get(suffix, "image/png")
52
-
53
- data_url = f"data:{mime_type};base64,{image_data}"
54
-
55
- prompt = self._get_prompt()
56
-
57
- response = self._client.chat.completions.create(
58
- model=self._model,
59
- messages=[
60
- {
61
- "role": "user",
62
- "content": [
63
- {
64
- "type": "image_url",
65
- "image_url": {"url": data_url},
66
- },
67
- {
68
- "type": "text",
69
- "text": prompt,
70
- },
71
- ],
72
- }
73
- ],
74
- )
75
-
76
- content = response.choices[0].message.content
77
- if not content:
78
- return ""
79
- # 清理模型可能输出的特殊标记
80
- content = re.sub(r"<\|/?ref\|>", "", content)
81
- content = re.sub(r"<\|/?det\|>", "", content)
82
- # 剥离 markdown 代码块包裹(如 ```markdown ... ```)
83
- content = re.sub(r"^```(?:markdown)?\s*\n", "", content, count=1)
84
- content = re.sub(r"\n```\s*$", "", content)
85
- return content.strip()
File without changes
File without changes
File without changes
File without changes