pdf2md-tool 0.8.0__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.
- pdf2md_tool-0.8.0/LICENSE +21 -0
- pdf2md_tool-0.8.0/PKG-INFO +254 -0
- pdf2md_tool-0.8.0/README.md +214 -0
- pdf2md_tool-0.8.0/pyproject.toml +76 -0
- pdf2md_tool-0.8.0/setup.cfg +4 -0
- pdf2md_tool-0.8.0/src/pdf2md/__init__.py +8 -0
- pdf2md_tool-0.8.0/src/pdf2md/_pix2tex_runner.py +45 -0
- pdf2md_tool-0.8.0/src/pdf2md/_profiles.py +130 -0
- pdf2md_tool-0.8.0/src/pdf2md/aggregate.py +407 -0
- pdf2md_tool-0.8.0/src/pdf2md/cli.py +930 -0
- pdf2md_tool-0.8.0/src/pdf2md/discovery.py +84 -0
- pdf2md_tool-0.8.0/src/pdf2md/executor.py +317 -0
- pdf2md_tool-0.8.0/src/pdf2md/extractors.py +241 -0
- pdf2md_tool-0.8.0/src/pdf2md/formula_cropper.py +337 -0
- pdf2md_tool-0.8.0/src/pdf2md/multi_roundtrip.py +272 -0
- pdf2md_tool-0.8.0/src/pdf2md/normalize.py +63 -0
- pdf2md_tool-0.8.0/src/pdf2md/optimize.py +392 -0
- pdf2md_tool-0.8.0/src/pdf2md/pdfs.py +184 -0
- pdf2md_tool-0.8.0/src/pdf2md/pixel_roundtrip.py +406 -0
- pdf2md_tool-0.8.0/src/pdf2md/provenance.py +174 -0
- pdf2md_tool-0.8.0/src/pdf2md/restructure.py +354 -0
- pdf2md_tool-0.8.0/src/pdf2md/roundtrip.py +197 -0
- pdf2md_tool-0.8.0/src/pdf2md/routing.py +467 -0
- pdf2md_tool-0.8.0/src/pdf2md/stats.py +780 -0
- pdf2md_tool-0.8.0/src/pdf2md/telemetry.py +314 -0
- pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/PKG-INFO +254 -0
- pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/SOURCES.txt +45 -0
- pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/dependency_links.txt +1 -0
- pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/entry_points.txt +2 -0
- pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/requires.txt +15 -0
- pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/top_level.txt +1 -0
- pdf2md_tool-0.8.0/tests/test_aggregate.py +95 -0
- pdf2md_tool-0.8.0/tests/test_cli_smoke.py +88 -0
- pdf2md_tool-0.8.0/tests/test_executor.py +290 -0
- pdf2md_tool-0.8.0/tests/test_extractors.py +133 -0
- pdf2md_tool-0.8.0/tests/test_formula_cropper.py +141 -0
- pdf2md_tool-0.8.0/tests/test_multi_roundtrip.py +97 -0
- pdf2md_tool-0.8.0/tests/test_normalize.py +50 -0
- pdf2md_tool-0.8.0/tests/test_optimize.py +216 -0
- pdf2md_tool-0.8.0/tests/test_pdfs.py +68 -0
- pdf2md_tool-0.8.0/tests/test_pixel_roundtrip.py +223 -0
- pdf2md_tool-0.8.0/tests/test_provenance.py +67 -0
- pdf2md_tool-0.8.0/tests/test_restructure.py +113 -0
- pdf2md_tool-0.8.0/tests/test_roundtrip.py +74 -0
- pdf2md_tool-0.8.0/tests/test_routing.py +303 -0
- pdf2md_tool-0.8.0/tests/test_stats.py +183 -0
- pdf2md_tool-0.8.0/tests/test_telemetry.py +122 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leonardo Marques de Souza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pdf2md-tool
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Conversor PDF→MD CPU-first: roteamento por intent (rápido/qualidade), núcleo offline e round-trip mensurável
|
|
5
|
+
Author-email: Leonardo Marques de Souza <leonardo.marques.souza@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/LeoPR/pdf2md
|
|
8
|
+
Project-URL: Repository, https://github.com/LeoPR/pdf2md
|
|
9
|
+
Project-URL: Issues, https://github.com/LeoPR/pdf2md/issues
|
|
10
|
+
Keywords: pdf,markdown,pdf-to-markdown,ocr,latex,document-conversion,cli,round-trip
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: typer>=0.12
|
|
28
|
+
Requires-Dist: pymupdf>=1.24
|
|
29
|
+
Requires-Dist: pillow>=10
|
|
30
|
+
Requires-Dist: psutil>=5.9
|
|
31
|
+
Provides-Extra: rtpixel
|
|
32
|
+
Requires-Dist: numpy>=1.26; extra == "rtpixel"
|
|
33
|
+
Requires-Dist: scipy>=1.14; extra == "rtpixel"
|
|
34
|
+
Requires-Dist: scikit-image>=0.22; extra == "rtpixel"
|
|
35
|
+
Provides-Extra: ocr
|
|
36
|
+
Requires-Dist: pytesseract>=0.3; extra == "ocr"
|
|
37
|
+
Provides-Extra: all
|
|
38
|
+
Requires-Dist: pdf2md-tool[ocr,rtpixel]; extra == "all"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# pdf2md
|
|
42
|
+
|
|
43
|
+
Conversor PDF → Markdown que **mede cada extrator em vértices primitivos**
|
|
44
|
+
(velocidade, memória, VRAM, latência, qualidade por elemento, footprint de
|
|
45
|
+
instalação) e deixa um **roteador escolher o caminho mais barato que ainda
|
|
46
|
+
satisfaz o seu intent**. Em vez de "o melhor extrator universal", entrega a
|
|
47
|
+
decisão: `--rapido` usa um caminho CPU puro (offline, determinístico, ~0.02 s/pg);
|
|
48
|
+
`--qualidade` gasta GPU/marker só quando você pede e o host comporta.
|
|
49
|
+
|
|
50
|
+
O **núcleo roda em qualquer máquina** — só `pip`, sem GPU, sem modelos, sem rede.
|
|
51
|
+
As capacidades pesadas (marker/GPU, pix2tex, OCR, VLM) são **opcionais** e
|
|
52
|
+
detectadas em runtime por `pdf2md doctor`.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install pdf2md-tool # núcleo CPU — nada externo
|
|
56
|
+
pdf2md convert paper.pdf --intent rapido
|
|
57
|
+
```
|
|
58
|
+
> No PyPI o pacote chama-se **`pdf2md-tool`** (o slug `pdf2md` estava reservado por
|
|
59
|
+
> outra conta). O **comando** e o **import** continuam `pdf2md`. Do código-fonte:
|
|
60
|
+
> `git clone … && pip install -e .`.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Quickstart sem GPU (o caminho portável)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install pdf2md-tool
|
|
68
|
+
pdf2md doctor # o que você tem (core sempre OK; resto opcional)
|
|
69
|
+
pdf2md convert paper.pdf --intent rapido --out out/
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`--rapido` roteia para **pdftotext (PyMuPDF)**: prosa fiel, math como Unicode cru,
|
|
73
|
+
0 VRAM, ~63 MB RAM, ~0.1 s de cold-start, determinístico. Não precisa instalar
|
|
74
|
+
mais nada. Para recuperar math em LaTeX, layout e tabelas, use `--qualidade` (que
|
|
75
|
+
quer marker/GPU) — `pdf2md doctor --intent qualidade` diz exatamente o que falta.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Por que vale a pena — vértices medidos (RTX 3060, N pequeno)
|
|
80
|
+
|
|
81
|
+
| Vértice | Como o roteador fecha | Âncora medida |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| **Velocidade** | `--rapido`/`--low-resource`/`--indexacao` → pdftotext | **0.02 s/pg** vs marker 12.9 (~**630×**) |
|
|
84
|
+
| **RAM** | teto duro 160 MB em `--low-resource`; degrada, não estoura | pdftotext **63 MB** vs marker ~1500 (est.) |
|
|
85
|
+
| **CPU-only** | pipeline 100% CPU (pdftotext + cropper built-in + pix2tex) | math display **0.80** sem GPU |
|
|
86
|
+
| **Latência** | default sem warm-up; refiners caros só quando o intent paga | pdftotext **0.1 s** vs marker ~30 s (est.) |
|
|
87
|
+
| **Qualidade** | roteada por **sub-elemento** (prosa/math/matriz/logo) | prosa 0.95 · scan impresso WER 0.052 · matriz 0.50 ⚠️ |
|
|
88
|
+
| **Footprint** | core offline; pesado é opt-in (`doctor`) | core = só wheels do pyproject |
|
|
89
|
+
|
|
90
|
+
> **Escopo honesto:** perfis medidos em **1 host** (RTX 3060) e N pequeno; RAM/cold-start
|
|
91
|
+
> de marker/VLM são **estimados**. É "roteamento medido para este corpus", não benchmark
|
|
92
|
+
> universal. Ver [`docs/profiles/`](docs/profiles/) (os 7 perfis) e
|
|
93
|
+
> [`tickets/closed/T090…`](tickets/closed/T090_macro_intent_routing.md).
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Status
|
|
98
|
+
|
|
99
|
+
| Camada / capacidade | Estado | Notas |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| **Roteador macro-intent** `--rapido/--qualidade/--balanceado/--auto/--indexacao/--low-resource` | Estável (T090) | `route()` puro profile-driven; degradação honesta |
|
|
102
|
+
| **Extração CPU** pdftotext (PyMuPDF) + Tesseract (scan) | Estável | offline, determinístico |
|
|
103
|
+
| **Cropper de fórmula CPU + pix2tex** (math display → LaTeX) | Estável | cropper built-in; runtime pix2tex externo (torch) |
|
|
104
|
+
| **Extração GPU** marker-pdf (Surya+Texify) | Estável | venv próprio + GPU; math/layout nativo |
|
|
105
|
+
| **MD → PDF** pandoc + Chrome + KaTeX | Estável | |
|
|
106
|
+
| **Round-trip textual + multi-iteração** | Estável | validador sem ground-truth |
|
|
107
|
+
| **Pixel-roundtrip visual L0.5** (SSIM + align) | Estável | extra `[rtpixel]` |
|
|
108
|
+
| **Otimização adaptativa de imagens** | Estável | −38.6% em N&C, sem perda visual |
|
|
109
|
+
| **Telemetria por step + agregada** | Estável | `psutil` + `nvidia-smi` |
|
|
110
|
+
| Reconstrução vetorial de logos (VLM small-image) | Pesquisa | T180 (não promovido) |
|
|
111
|
+
| Vetorização SVG (potrace) · tabelas (TEDS) · cross-hardware | Pesquisa | T132 · BURACO #2 · T091 |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Instalação
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/LeoPR/pdf2md && cd pdf2md
|
|
119
|
+
pip install -e . # núcleo CPU (typer, pymupdf, pillow, psutil)
|
|
120
|
+
pip install -e '.[rtpixel]' # + validador visual (numpy/scipy/scikit-image)
|
|
121
|
+
pip install -e '.[ocr]' # + wrapper pytesseract (engine é externo)
|
|
122
|
+
pip install -e '.[all]' # tudo que é pip-puro seguro
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Capacidades externas (NÃO instaláveis por pip deste pacote — `pdf2md doctor` valida):**
|
|
126
|
+
|
|
127
|
+
| Capacidade | Como obter | Por que externo |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| **marker/GPU** (math+layout nativo) | venv próprio + `PDF2MD_MARKER` | conflito `pillow<11` + torch/CUDA |
|
|
130
|
+
| **pix2tex** (math→LaTeX CPU) | venv com torch + `PDF2MD_PIX2TEX_PYTHON` | torch é pesado/OS-específico |
|
|
131
|
+
| **tesseract** (OCR scan) | engine UB-Mannheim no PATH + extra `[ocr]` | binário de sistema |
|
|
132
|
+
| **pandoc + Chrome** (MD→PDF) | no PATH | binários de sistema |
|
|
133
|
+
| **ollama + gemma3/qwen** (logos) | daemon `:11434` + `ollama pull` | server + modelos fora do pip |
|
|
134
|
+
|
|
135
|
+
> `pip install pdf2md-tool[gpu]` **não existe de propósito**: marker fixa `Pillow<11` e é
|
|
136
|
+
> impossível co-instalar no mesmo ambiente. A interface honesta para a stack pesada é o
|
|
137
|
+
> `doctor`, não um extra pip. Dev roda a suíte com `uv sync --all-extras`.
|
|
138
|
+
>
|
|
139
|
+
> *Setup do autor (não é requisito):* venv junction em `Z:\venvs\pdf2md`; ver
|
|
140
|
+
> [`docs/reference/conventions.md`](docs/reference/conventions.md).
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Uso
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
MACRO por intent (roteador T090 — escolhe a stack por host+doc)
|
|
148
|
+
pdf2md convert FILE.pdf --intent rapido # CPU puro, velocidade máxima
|
|
149
|
+
pdf2md convert FILE.pdf --intent qualidade # marker/GPU se houver; senão degrada
|
|
150
|
+
pdf2md convert FILE.pdf --intent auto # melhor stack que CABE no host
|
|
151
|
+
pdf2md convert FILE.pdf --intent indexacao # pass1 indexa tudo; pass2 enfileira math-heavy
|
|
152
|
+
pdf2md route FILE.pdf --intent qualidade # dry-run: mostra o pipeline (--execute roda)
|
|
153
|
+
|
|
154
|
+
LEGADO (mantido; --intent substitui)
|
|
155
|
+
pdf2md convert FILE.pdf --quick / --best
|
|
156
|
+
|
|
157
|
+
SUBCOMANDOS FINOS (controle granular / retomar pipeline parcial)
|
|
158
|
+
pdf2md extract · restruct · optimize · stats · rt · rt-multi · aggr · prov · norm · pdfs
|
|
159
|
+
|
|
160
|
+
META
|
|
161
|
+
pdf2md doctor [--intent N] # capabilities do host (+ o que um intent usaria aqui)
|
|
162
|
+
pdf2md version · help <cmd>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Os 6 intents (resumo; detalhes em [how-to/escolher_intent](docs/how-to/escolher_intent.md))
|
|
166
|
+
|
|
167
|
+
| Intent | PRIMARY | Para quê |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `--rapido` | pdftotext (mesmo com GPU) | indexar/pré-processar em massa; wall-time mínimo |
|
|
170
|
+
| `--low-resource` | pdftotext (teto RAM 160 MB) | máquinas magras; optimize desliga se estourar |
|
|
171
|
+
| `--indexacao` | pdftotext (pass1) + marker (pass2 enfileirável) | milhares de docs; pass2 só nos de perda recuperável |
|
|
172
|
+
| `--balanceado` (default) | marker se houver, senão pdftotext | uso geral |
|
|
173
|
+
| `--qualidade` | marker (degrada p/ pdftotext+pix2tex sem GPU) | máxima fidelidade; math LaTeX, layout |
|
|
174
|
+
| `--auto` | melhor que o host comporta | "faça a melhor coisa possível aqui" |
|
|
175
|
+
|
|
176
|
+
Sem GPU, `--qualidade` **degrada com aviso honesto** (`.rationale` vai pra proveniência),
|
|
177
|
+
nunca finge qualidade nem quebra em silêncio.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Variáveis de ambiente
|
|
182
|
+
|
|
183
|
+
| Variável | Função |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `PDF2MD_MARKER` | path do `marker_single` (sem fallback de máquina — **necessária** p/ usar marker) |
|
|
186
|
+
| `PDF2MD_PIX2TEX_PYTHON` | python de um venv com `pix2tex` (runtime math→LaTeX CPU) |
|
|
187
|
+
| `PDF2MD_TESSERACT` | path do `tesseract` (senão PATH → local padrão do SO) |
|
|
188
|
+
| `PDF2MD_PANDOC` / `PDF2MD_CHROME` | paths de pandoc / Chrome (senão PATH → local padrão do SO) |
|
|
189
|
+
| `PDF2MD_ZCACHE` / `PDF2MD_AULAQUANTUM` | raízes de corpus zcache / source privado (default = drives do autor) |
|
|
190
|
+
|
|
191
|
+
Descoberta (em `pdf2md/discovery.py`): `env → PATH (multi-nome, multi-SO) → local padrão
|
|
192
|
+
do SO → nome do comando`. Sem paths absolutos presos a uma máquina.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Estrutura do repo
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
pdf2md/
|
|
200
|
+
├── src/pdf2md/ (lógica do pacote)
|
|
201
|
+
│ ├── cli.py CLI (macro --intent, route, doctor, subcomandos)
|
|
202
|
+
│ ├── routing.py ROTEADOR T090: route() puro + HostInfo/DocInfo + pass2_warranted
|
|
203
|
+
│ ├── _profiles.py MAPA: perfis medidos (route-relevant), dep-free
|
|
204
|
+
│ ├── executor.py executa o Pipeline que route() decide
|
|
205
|
+
│ ├── extractors.py PRIMARYs CPU: pdftotext + tesseract
|
|
206
|
+
│ ├── formula_cropper.py cropper de fórmula display CPU (built-in)
|
|
207
|
+
│ ├── discovery.py descoberta portável de ferramentas externas
|
|
208
|
+
│ ├── optimize.py otimização adaptativa de imagens
|
|
209
|
+
│ ├── telemetry.py INSTRUMENTO: wall/cpu/mem/gpu/io por step
|
|
210
|
+
│ ├── pixel_roundtrip.py validador visual L0.5 (extra [rtpixel])
|
|
211
|
+
│ ├── roundtrip.py · multi_roundtrip.py round-trip textual + estabilidade
|
|
212
|
+
│ ├── pdfs.py · restructure.py · normalize.py · provenance.py · stats.py · aggregate.py
|
|
213
|
+
│ └── _pix2tex_runner.py script no venv externo do pix2tex (subprocess)
|
|
214
|
+
├── corpus/ dataset em 3 tiers (ver corpus/RIGHTS.md)
|
|
215
|
+
│ ├── examples/ excertos LIVRES commitados (prova pronta, sem baixar nada)
|
|
216
|
+
│ └── registry.py resolve(doc_id) → in-repo | zcache | private
|
|
217
|
+
├── docs/ Diátaxis (tutorials/how-to/reference/explanation/profiles)
|
|
218
|
+
├── lab/ bancada experimental (eNN por experimento)
|
|
219
|
+
└── tickets/ work items (open/closed/research) + INDEX.md
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
PDFs pesados ficam **fora do repo** (zcache `Z:/caches/...` via `PDF2MD_ZCACHE`, ou sources
|
|
223
|
+
privados). O tier in-repo (`corpus/examples/`) tem só excertos livres pequenos.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Resultados (medidos)
|
|
228
|
+
|
|
229
|
+
Round-trip e telemetria sobre **Nielsen & Chuang QCQI** (704 pág, RTX 3060):
|
|
230
|
+
|
|
231
|
+
| Métrica | Valor |
|
|
232
|
+
|---|---:|
|
|
233
|
+
| Round-trip textual (cap. 4, marker) | **95.09%** |
|
|
234
|
+
| Multi-iteração (paper) | drift 0.86% em 5 iter (**estável**) |
|
|
235
|
+
| Otimização de imagens (cap. 4) | 4.5 → 2.7 MB (**−38.6%**) |
|
|
236
|
+
| Extração full-doc (marker) | ~4145 s (~70 min) |
|
|
237
|
+
|
|
238
|
+
> Os números do N&C são **resultados derivados** (métricas, não reprodução da obra),
|
|
239
|
+
> usados sob licença legítima do detentor — ver [`corpus/RIGHTS.md`](corpus/RIGHTS.md). O
|
|
240
|
+
> PDF original e reproduções completas ficam fora do repositório.
|
|
241
|
+
|
|
242
|
+
Caminho CPU validado em corpus livre in-repo: pdftotext prosa WER 0.016 (pg estruturada),
|
|
243
|
+
Tesseract scan impresso WER 0.052, pix2tex math display 0.80 (linha única; matriz ~0.50).
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Documentação
|
|
248
|
+
|
|
249
|
+
- Arquitetura: [`docs/explanation/arquitetura.md`](docs/explanation/arquitetura.md) · Filosofia: [`philosophy.md`](docs/explanation/philosophy.md)
|
|
250
|
+
- Perfis medidos: [`docs/profiles/`](docs/profiles/) · Tecnologias: [`docs/reference/tecnologias.md`](docs/reference/tecnologias.md)
|
|
251
|
+
- Referência de CLI: [`docs/reference/cli.md`](docs/reference/cli.md) · Escolher intent: [`docs/how-to/escolher_intent.md`](docs/how-to/escolher_intent.md)
|
|
252
|
+
- Direitos do corpus: [`corpus/RIGHTS.md`](corpus/RIGHTS.md) · Timeline: [`docs/explanation/diario.md`](docs/explanation/diario.md)
|
|
253
|
+
|
|
254
|
+
Licença: MIT (ver [`LICENSE`](LICENSE)).
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# pdf2md
|
|
2
|
+
|
|
3
|
+
Conversor PDF → Markdown que **mede cada extrator em vértices primitivos**
|
|
4
|
+
(velocidade, memória, VRAM, latência, qualidade por elemento, footprint de
|
|
5
|
+
instalação) e deixa um **roteador escolher o caminho mais barato que ainda
|
|
6
|
+
satisfaz o seu intent**. Em vez de "o melhor extrator universal", entrega a
|
|
7
|
+
decisão: `--rapido` usa um caminho CPU puro (offline, determinístico, ~0.02 s/pg);
|
|
8
|
+
`--qualidade` gasta GPU/marker só quando você pede e o host comporta.
|
|
9
|
+
|
|
10
|
+
O **núcleo roda em qualquer máquina** — só `pip`, sem GPU, sem modelos, sem rede.
|
|
11
|
+
As capacidades pesadas (marker/GPU, pix2tex, OCR, VLM) são **opcionais** e
|
|
12
|
+
detectadas em runtime por `pdf2md doctor`.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install pdf2md-tool # núcleo CPU — nada externo
|
|
16
|
+
pdf2md convert paper.pdf --intent rapido
|
|
17
|
+
```
|
|
18
|
+
> No PyPI o pacote chama-se **`pdf2md-tool`** (o slug `pdf2md` estava reservado por
|
|
19
|
+
> outra conta). O **comando** e o **import** continuam `pdf2md`. Do código-fonte:
|
|
20
|
+
> `git clone … && pip install -e .`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Quickstart sem GPU (o caminho portável)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install pdf2md-tool
|
|
28
|
+
pdf2md doctor # o que você tem (core sempre OK; resto opcional)
|
|
29
|
+
pdf2md convert paper.pdf --intent rapido --out out/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`--rapido` roteia para **pdftotext (PyMuPDF)**: prosa fiel, math como Unicode cru,
|
|
33
|
+
0 VRAM, ~63 MB RAM, ~0.1 s de cold-start, determinístico. Não precisa instalar
|
|
34
|
+
mais nada. Para recuperar math em LaTeX, layout e tabelas, use `--qualidade` (que
|
|
35
|
+
quer marker/GPU) — `pdf2md doctor --intent qualidade` diz exatamente o que falta.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Por que vale a pena — vértices medidos (RTX 3060, N pequeno)
|
|
40
|
+
|
|
41
|
+
| Vértice | Como o roteador fecha | Âncora medida |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| **Velocidade** | `--rapido`/`--low-resource`/`--indexacao` → pdftotext | **0.02 s/pg** vs marker 12.9 (~**630×**) |
|
|
44
|
+
| **RAM** | teto duro 160 MB em `--low-resource`; degrada, não estoura | pdftotext **63 MB** vs marker ~1500 (est.) |
|
|
45
|
+
| **CPU-only** | pipeline 100% CPU (pdftotext + cropper built-in + pix2tex) | math display **0.80** sem GPU |
|
|
46
|
+
| **Latência** | default sem warm-up; refiners caros só quando o intent paga | pdftotext **0.1 s** vs marker ~30 s (est.) |
|
|
47
|
+
| **Qualidade** | roteada por **sub-elemento** (prosa/math/matriz/logo) | prosa 0.95 · scan impresso WER 0.052 · matriz 0.50 ⚠️ |
|
|
48
|
+
| **Footprint** | core offline; pesado é opt-in (`doctor`) | core = só wheels do pyproject |
|
|
49
|
+
|
|
50
|
+
> **Escopo honesto:** perfis medidos em **1 host** (RTX 3060) e N pequeno; RAM/cold-start
|
|
51
|
+
> de marker/VLM são **estimados**. É "roteamento medido para este corpus", não benchmark
|
|
52
|
+
> universal. Ver [`docs/profiles/`](docs/profiles/) (os 7 perfis) e
|
|
53
|
+
> [`tickets/closed/T090…`](tickets/closed/T090_macro_intent_routing.md).
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Status
|
|
58
|
+
|
|
59
|
+
| Camada / capacidade | Estado | Notas |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| **Roteador macro-intent** `--rapido/--qualidade/--balanceado/--auto/--indexacao/--low-resource` | Estável (T090) | `route()` puro profile-driven; degradação honesta |
|
|
62
|
+
| **Extração CPU** pdftotext (PyMuPDF) + Tesseract (scan) | Estável | offline, determinístico |
|
|
63
|
+
| **Cropper de fórmula CPU + pix2tex** (math display → LaTeX) | Estável | cropper built-in; runtime pix2tex externo (torch) |
|
|
64
|
+
| **Extração GPU** marker-pdf (Surya+Texify) | Estável | venv próprio + GPU; math/layout nativo |
|
|
65
|
+
| **MD → PDF** pandoc + Chrome + KaTeX | Estável | |
|
|
66
|
+
| **Round-trip textual + multi-iteração** | Estável | validador sem ground-truth |
|
|
67
|
+
| **Pixel-roundtrip visual L0.5** (SSIM + align) | Estável | extra `[rtpixel]` |
|
|
68
|
+
| **Otimização adaptativa de imagens** | Estável | −38.6% em N&C, sem perda visual |
|
|
69
|
+
| **Telemetria por step + agregada** | Estável | `psutil` + `nvidia-smi` |
|
|
70
|
+
| Reconstrução vetorial de logos (VLM small-image) | Pesquisa | T180 (não promovido) |
|
|
71
|
+
| Vetorização SVG (potrace) · tabelas (TEDS) · cross-hardware | Pesquisa | T132 · BURACO #2 · T091 |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Instalação
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/LeoPR/pdf2md && cd pdf2md
|
|
79
|
+
pip install -e . # núcleo CPU (typer, pymupdf, pillow, psutil)
|
|
80
|
+
pip install -e '.[rtpixel]' # + validador visual (numpy/scipy/scikit-image)
|
|
81
|
+
pip install -e '.[ocr]' # + wrapper pytesseract (engine é externo)
|
|
82
|
+
pip install -e '.[all]' # tudo que é pip-puro seguro
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Capacidades externas (NÃO instaláveis por pip deste pacote — `pdf2md doctor` valida):**
|
|
86
|
+
|
|
87
|
+
| Capacidade | Como obter | Por que externo |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| **marker/GPU** (math+layout nativo) | venv próprio + `PDF2MD_MARKER` | conflito `pillow<11` + torch/CUDA |
|
|
90
|
+
| **pix2tex** (math→LaTeX CPU) | venv com torch + `PDF2MD_PIX2TEX_PYTHON` | torch é pesado/OS-específico |
|
|
91
|
+
| **tesseract** (OCR scan) | engine UB-Mannheim no PATH + extra `[ocr]` | binário de sistema |
|
|
92
|
+
| **pandoc + Chrome** (MD→PDF) | no PATH | binários de sistema |
|
|
93
|
+
| **ollama + gemma3/qwen** (logos) | daemon `:11434` + `ollama pull` | server + modelos fora do pip |
|
|
94
|
+
|
|
95
|
+
> `pip install pdf2md-tool[gpu]` **não existe de propósito**: marker fixa `Pillow<11` e é
|
|
96
|
+
> impossível co-instalar no mesmo ambiente. A interface honesta para a stack pesada é o
|
|
97
|
+
> `doctor`, não um extra pip. Dev roda a suíte com `uv sync --all-extras`.
|
|
98
|
+
>
|
|
99
|
+
> *Setup do autor (não é requisito):* venv junction em `Z:\venvs\pdf2md`; ver
|
|
100
|
+
> [`docs/reference/conventions.md`](docs/reference/conventions.md).
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Uso
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
MACRO por intent (roteador T090 — escolhe a stack por host+doc)
|
|
108
|
+
pdf2md convert FILE.pdf --intent rapido # CPU puro, velocidade máxima
|
|
109
|
+
pdf2md convert FILE.pdf --intent qualidade # marker/GPU se houver; senão degrada
|
|
110
|
+
pdf2md convert FILE.pdf --intent auto # melhor stack que CABE no host
|
|
111
|
+
pdf2md convert FILE.pdf --intent indexacao # pass1 indexa tudo; pass2 enfileira math-heavy
|
|
112
|
+
pdf2md route FILE.pdf --intent qualidade # dry-run: mostra o pipeline (--execute roda)
|
|
113
|
+
|
|
114
|
+
LEGADO (mantido; --intent substitui)
|
|
115
|
+
pdf2md convert FILE.pdf --quick / --best
|
|
116
|
+
|
|
117
|
+
SUBCOMANDOS FINOS (controle granular / retomar pipeline parcial)
|
|
118
|
+
pdf2md extract · restruct · optimize · stats · rt · rt-multi · aggr · prov · norm · pdfs
|
|
119
|
+
|
|
120
|
+
META
|
|
121
|
+
pdf2md doctor [--intent N] # capabilities do host (+ o que um intent usaria aqui)
|
|
122
|
+
pdf2md version · help <cmd>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Os 6 intents (resumo; detalhes em [how-to/escolher_intent](docs/how-to/escolher_intent.md))
|
|
126
|
+
|
|
127
|
+
| Intent | PRIMARY | Para quê |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `--rapido` | pdftotext (mesmo com GPU) | indexar/pré-processar em massa; wall-time mínimo |
|
|
130
|
+
| `--low-resource` | pdftotext (teto RAM 160 MB) | máquinas magras; optimize desliga se estourar |
|
|
131
|
+
| `--indexacao` | pdftotext (pass1) + marker (pass2 enfileirável) | milhares de docs; pass2 só nos de perda recuperável |
|
|
132
|
+
| `--balanceado` (default) | marker se houver, senão pdftotext | uso geral |
|
|
133
|
+
| `--qualidade` | marker (degrada p/ pdftotext+pix2tex sem GPU) | máxima fidelidade; math LaTeX, layout |
|
|
134
|
+
| `--auto` | melhor que o host comporta | "faça a melhor coisa possível aqui" |
|
|
135
|
+
|
|
136
|
+
Sem GPU, `--qualidade` **degrada com aviso honesto** (`.rationale` vai pra proveniência),
|
|
137
|
+
nunca finge qualidade nem quebra em silêncio.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Variáveis de ambiente
|
|
142
|
+
|
|
143
|
+
| Variável | Função |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `PDF2MD_MARKER` | path do `marker_single` (sem fallback de máquina — **necessária** p/ usar marker) |
|
|
146
|
+
| `PDF2MD_PIX2TEX_PYTHON` | python de um venv com `pix2tex` (runtime math→LaTeX CPU) |
|
|
147
|
+
| `PDF2MD_TESSERACT` | path do `tesseract` (senão PATH → local padrão do SO) |
|
|
148
|
+
| `PDF2MD_PANDOC` / `PDF2MD_CHROME` | paths de pandoc / Chrome (senão PATH → local padrão do SO) |
|
|
149
|
+
| `PDF2MD_ZCACHE` / `PDF2MD_AULAQUANTUM` | raízes de corpus zcache / source privado (default = drives do autor) |
|
|
150
|
+
|
|
151
|
+
Descoberta (em `pdf2md/discovery.py`): `env → PATH (multi-nome, multi-SO) → local padrão
|
|
152
|
+
do SO → nome do comando`. Sem paths absolutos presos a uma máquina.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Estrutura do repo
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
pdf2md/
|
|
160
|
+
├── src/pdf2md/ (lógica do pacote)
|
|
161
|
+
│ ├── cli.py CLI (macro --intent, route, doctor, subcomandos)
|
|
162
|
+
│ ├── routing.py ROTEADOR T090: route() puro + HostInfo/DocInfo + pass2_warranted
|
|
163
|
+
│ ├── _profiles.py MAPA: perfis medidos (route-relevant), dep-free
|
|
164
|
+
│ ├── executor.py executa o Pipeline que route() decide
|
|
165
|
+
│ ├── extractors.py PRIMARYs CPU: pdftotext + tesseract
|
|
166
|
+
│ ├── formula_cropper.py cropper de fórmula display CPU (built-in)
|
|
167
|
+
│ ├── discovery.py descoberta portável de ferramentas externas
|
|
168
|
+
│ ├── optimize.py otimização adaptativa de imagens
|
|
169
|
+
│ ├── telemetry.py INSTRUMENTO: wall/cpu/mem/gpu/io por step
|
|
170
|
+
│ ├── pixel_roundtrip.py validador visual L0.5 (extra [rtpixel])
|
|
171
|
+
│ ├── roundtrip.py · multi_roundtrip.py round-trip textual + estabilidade
|
|
172
|
+
│ ├── pdfs.py · restructure.py · normalize.py · provenance.py · stats.py · aggregate.py
|
|
173
|
+
│ └── _pix2tex_runner.py script no venv externo do pix2tex (subprocess)
|
|
174
|
+
├── corpus/ dataset em 3 tiers (ver corpus/RIGHTS.md)
|
|
175
|
+
│ ├── examples/ excertos LIVRES commitados (prova pronta, sem baixar nada)
|
|
176
|
+
│ └── registry.py resolve(doc_id) → in-repo | zcache | private
|
|
177
|
+
├── docs/ Diátaxis (tutorials/how-to/reference/explanation/profiles)
|
|
178
|
+
├── lab/ bancada experimental (eNN por experimento)
|
|
179
|
+
└── tickets/ work items (open/closed/research) + INDEX.md
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
PDFs pesados ficam **fora do repo** (zcache `Z:/caches/...` via `PDF2MD_ZCACHE`, ou sources
|
|
183
|
+
privados). O tier in-repo (`corpus/examples/`) tem só excertos livres pequenos.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Resultados (medidos)
|
|
188
|
+
|
|
189
|
+
Round-trip e telemetria sobre **Nielsen & Chuang QCQI** (704 pág, RTX 3060):
|
|
190
|
+
|
|
191
|
+
| Métrica | Valor |
|
|
192
|
+
|---|---:|
|
|
193
|
+
| Round-trip textual (cap. 4, marker) | **95.09%** |
|
|
194
|
+
| Multi-iteração (paper) | drift 0.86% em 5 iter (**estável**) |
|
|
195
|
+
| Otimização de imagens (cap. 4) | 4.5 → 2.7 MB (**−38.6%**) |
|
|
196
|
+
| Extração full-doc (marker) | ~4145 s (~70 min) |
|
|
197
|
+
|
|
198
|
+
> Os números do N&C são **resultados derivados** (métricas, não reprodução da obra),
|
|
199
|
+
> usados sob licença legítima do detentor — ver [`corpus/RIGHTS.md`](corpus/RIGHTS.md). O
|
|
200
|
+
> PDF original e reproduções completas ficam fora do repositório.
|
|
201
|
+
|
|
202
|
+
Caminho CPU validado em corpus livre in-repo: pdftotext prosa WER 0.016 (pg estruturada),
|
|
203
|
+
Tesseract scan impresso WER 0.052, pix2tex math display 0.80 (linha única; matriz ~0.50).
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Documentação
|
|
208
|
+
|
|
209
|
+
- Arquitetura: [`docs/explanation/arquitetura.md`](docs/explanation/arquitetura.md) · Filosofia: [`philosophy.md`](docs/explanation/philosophy.md)
|
|
210
|
+
- Perfis medidos: [`docs/profiles/`](docs/profiles/) · Tecnologias: [`docs/reference/tecnologias.md`](docs/reference/tecnologias.md)
|
|
211
|
+
- Referência de CLI: [`docs/reference/cli.md`](docs/reference/cli.md) · Escolher intent: [`docs/how-to/escolher_intent.md`](docs/how-to/escolher_intent.md)
|
|
212
|
+
- Direitos do corpus: [`corpus/RIGHTS.md`](corpus/RIGHTS.md) · Timeline: [`docs/explanation/diario.md`](docs/explanation/diario.md)
|
|
213
|
+
|
|
214
|
+
Licença: MIT (ver [`LICENSE`](LICENSE)).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
# Nome de DISTRIBUIÇÃO no PyPI. O slug "pdf2md" está reservado por outra conta
|
|
3
|
+
# (registrado sem releases). Além disso o PyPI rejeita nomes "muito similares" por
|
|
4
|
+
# ultranormalização (remove ._- e mapeia o->0, i/l->1): p.ex. "pdftomd" colidiria
|
|
5
|
+
# com "pdf-to-md" (ambos -> pdft0md). "pdf2md-tool" tem chave "pdf2mdt001", distinta
|
|
6
|
+
# de tudo (verificado, 81 grafias 404). Import e comando CLI permanecem "pdf2md".
|
|
7
|
+
name = "pdf2md-tool"
|
|
8
|
+
version = "0.8.0"
|
|
9
|
+
description = "Conversor PDF→MD CPU-first: roteamento por intent (rápido/qualidade), núcleo offline e round-trip mensurável"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Leonardo Marques de Souza", email = "leonardo.marques.souza@gmail.com" }
|
|
12
|
+
]
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
requires-python = ">=3.11"
|
|
16
|
+
keywords = ["pdf", "markdown", "pdf-to-markdown", "ocr", "latex", "document-conversion", "cli", "round-trip"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: Text Processing :: Markup :: Markdown",
|
|
29
|
+
"Topic :: Scientific/Engineering",
|
|
30
|
+
"Topic :: Utilities",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# Deps base do CLI. Marker/pandoc/Chrome são checados em runtime via `pdf2md doctor`,
|
|
34
|
+
# não declarados aqui (cada um vive no seu venv ou no PATH, sem conflito pillow<11).
|
|
35
|
+
dependencies = [
|
|
36
|
+
"typer>=0.12",
|
|
37
|
+
"pymupdf>=1.24",
|
|
38
|
+
"pillow>=10",
|
|
39
|
+
"psutil>=5.9",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
# Extras pip REAIS (pure-pip, sem conflito pillow/torch). marker/pix2tex/ollama NÃO
|
|
44
|
+
# entram aqui — são capabilities EXTERNAS (venv/binário/server), checadas por
|
|
45
|
+
# `pdf2md doctor`. Ver tickets/closed/T090 + docs. (publish-prep marco B)
|
|
46
|
+
rtpixel = ["numpy>=1.26", "scipy>=1.14", "scikit-image>=0.22"] # validador visual L0.5 (SSIM/align)
|
|
47
|
+
ocr = ["pytesseract>=0.3"] # wrapper; engine tesseract é externo (doctor)
|
|
48
|
+
all = ["pdf2md-tool[rtpixel,ocr]"] # tudo que é pip-puro seguro (auto-ref: nome de distribuição)
|
|
49
|
+
|
|
50
|
+
[project.scripts]
|
|
51
|
+
pdf2md = "pdf2md.cli:app"
|
|
52
|
+
|
|
53
|
+
[project.urls]
|
|
54
|
+
Homepage = "https://github.com/LeoPR/pdf2md"
|
|
55
|
+
Repository = "https://github.com/LeoPR/pdf2md"
|
|
56
|
+
Issues = "https://github.com/LeoPR/pdf2md/issues"
|
|
57
|
+
|
|
58
|
+
[build-system]
|
|
59
|
+
requires = ["setuptools>=64"]
|
|
60
|
+
build-backend = "setuptools.build_meta"
|
|
61
|
+
|
|
62
|
+
[tool.setuptools.packages.find]
|
|
63
|
+
where = ["src"]
|
|
64
|
+
include = ["pdf2md*"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff]
|
|
67
|
+
line-length = 100
|
|
68
|
+
target-version = "py311"
|
|
69
|
+
|
|
70
|
+
[dependency-groups]
|
|
71
|
+
dev = [
|
|
72
|
+
"pip>=26.1.1",
|
|
73
|
+
"setuptools>=82.0.1",
|
|
74
|
+
"wheel>=0.47.0",
|
|
75
|
+
"pytest>=8",
|
|
76
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""pdf2md — conversor PDF→Markdown CPU-first com roteamento por intent.
|
|
2
|
+
|
|
3
|
+
Núcleo CPU (pdftotext/PyMuPDF) offline e determinístico; capacidades pesadas
|
|
4
|
+
(marker/GPU, pix2tex, OCR, VLM) são opcionais e detectadas em runtime via
|
|
5
|
+
`pdf2md doctor`. CLI em `pdf2md.cli:app`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.8.0"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Runner standalone de pix2tex — executado pelo python de um venv EXTERNO (torch).
|
|
2
|
+
|
|
3
|
+
NÃO importa pdf2md (o venv externo não tem o pacote). Só pix2tex + PIL + stdlib.
|
|
4
|
+
O executor (pdf2md.executor) cropa as fórmulas no venv geral e chama:
|
|
5
|
+
|
|
6
|
+
<pix2tex_python> _pix2tex_runner.py <crop_dir> <out_json>
|
|
7
|
+
|
|
8
|
+
Lê todos os PNGs de <crop_dir>, roda LatexOCR e escreve {filename: latex} em
|
|
9
|
+
<out_json>. Mantém o torch fora do venv geral (ver feedback_venv_efemero_para_labs).
|
|
10
|
+
"""
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(argv) -> int:
|
|
17
|
+
if len(argv) != 2:
|
|
18
|
+
print("uso: _pix2tex_runner.py <crop_dir> <out_json>", file=sys.stderr)
|
|
19
|
+
return 2
|
|
20
|
+
crop_dir, out_json = Path(argv[0]), Path(argv[1])
|
|
21
|
+
crops = sorted(crop_dir.glob("*.png"))
|
|
22
|
+
if not crops:
|
|
23
|
+
out_json.write_text("{}", encoding="utf-8")
|
|
24
|
+
return 0
|
|
25
|
+
try:
|
|
26
|
+
from PIL import Image
|
|
27
|
+
from pix2tex.cli import LatexOCR
|
|
28
|
+
except ImportError as e:
|
|
29
|
+
print(f"[ERRO] runtime pix2tex ausente: {e}", file=sys.stderr)
|
|
30
|
+
return 3
|
|
31
|
+
|
|
32
|
+
model = LatexOCR()
|
|
33
|
+
out = {}
|
|
34
|
+
for png in crops:
|
|
35
|
+
try:
|
|
36
|
+
out[png.name] = model(Image.open(png))
|
|
37
|
+
except Exception as e: # uma fórmula ruim não derruba o lote
|
|
38
|
+
out[png.name] = ""
|
|
39
|
+
print(f"[warn] {png.name}: {e}", file=sys.stderr)
|
|
40
|
+
out_json.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
41
|
+
return 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
raise SystemExit(main(sys.argv[1:]))
|