parse-anything 0.1.0__py3-none-any.whl

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,54 @@
1
+ """parse-anything: one interface over mineru, docling, unstructured, markitdown,
2
+ lite (PyMuPDF) and raw OCR (PaddleOCR / Tesseract).
3
+
4
+ from parse_anything import parse
5
+
6
+ result = parse("report.pdf", engine="mineru")
7
+ print(result.content) # markdown
8
+ """
9
+
10
+ import logging
11
+ from pathlib import Path
12
+
13
+ from .factory import engine_names, get_engine
14
+ from .result import ParseResult
15
+
16
+ __version__ = "0.1.0"
17
+
18
+ # Library convention: don't configure handlers here, just make sure "no
19
+ # handlers found" warnings don't show up for callers who haven't set up
20
+ # logging themselves. The CLI (cli.py) calls logging.basicConfig() instead.
21
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def parse(
26
+ path: str | Path,
27
+ engine: str = "auto",
28
+ ocr: str = "paddle",
29
+ **options,
30
+ ) -> ParseResult:
31
+ """Parses a document and returns its extracted content.
32
+
33
+ path: file to parse (pdf, docx, pptx, xlsx, html, images, ...)
34
+ engine: "auto" (default), "mineru"/"miner-u", "docling", "unstructured",
35
+ "markitdown", "lite"/"liteparse", or "ocr"
36
+ ocr: "paddle" (default, PP-OCRv5), "tesseract", or "none"
37
+ options: engine-specific knobs, e.g. strategy="hi_res" (unstructured),
38
+ force_ocr=True (mineru), dpi=300 (ocr), lang="ch" (ocr backends)
39
+ """
40
+ file_path = Path(path)
41
+ if not file_path.exists():
42
+ raise FileNotFoundError(f"No such file: {file_path}")
43
+
44
+ logger.info("parsing %s (engine=%s, ocr=%s)", file_path.name, engine, ocr)
45
+ backend = get_engine(engine, path=file_path)
46
+ result = backend.parse(file_path, ocr=ocr, **options)
47
+ logger.info(
48
+ "finished %s: engine=%s ocr=%s %d chars in %.2fs",
49
+ file_path.name, result.engine, result.ocr, len(result.content), result.duration_s,
50
+ )
51
+ return result
52
+
53
+
54
+ __all__ = ["parse", "ParseResult", "get_engine", "engine_names", "__version__"]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
parse_anything/cli.py ADDED
@@ -0,0 +1,104 @@
1
+ import argparse
2
+ import logging
3
+ import sys
4
+
5
+ from . import __version__, parse
6
+ from .factory import engine_names
7
+
8
+ logger = logging.getLogger("parse_anything.cli")
9
+
10
+
11
+ def build_parser() -> argparse.ArgumentParser:
12
+ p = argparse.ArgumentParser(
13
+ prog="parse-anything",
14
+ description="Parse any document with your engine of choice and print the extracted content.",
15
+ )
16
+ p.add_argument("path", nargs="?", help="File to parse (pdf, docx, pptx, html, png, ...)")
17
+ p.add_argument(
18
+ "-e", "--engine", default="auto",
19
+ help="auto | mineru (miner-u) | docling | unstructured | markitdown | lite (liteparse) | ocr",
20
+ )
21
+ p.add_argument(
22
+ "--ocr", default="paddle", choices=["paddle", "tesseract", "none"],
23
+ help="OCR backend for scanned pages/images (default: paddle / PP-OCRv5)",
24
+ )
25
+ p.add_argument("-o", "--output", help="Write result here (.md/.txt for content, .json for full result)")
26
+ p.add_argument(
27
+ "-f", "--format", default="content", choices=["content", "json"],
28
+ help="stdout format: raw extracted content (default) or the full JSON result",
29
+ )
30
+ p.add_argument("--strategy", help="unstructured only: fast | hi_res | ocr_only | auto")
31
+ p.add_argument("--force-ocr", action="store_true", help="mineru only: OCR every page")
32
+ p.add_argument("--lang", help="OCR language (paddle: en/ch/..., tesseract: eng/deu/...)")
33
+ p.add_argument("--dpi", type=int, help="ocr engine only: PDF rasterization DPI (default 200)")
34
+ p.add_argument("--timeout", type=int, default=900, help="mineru timeout in seconds (default 900 / 15 min)")
35
+ p.add_argument("--list-engines", action="store_true", help="List engines and exit")
36
+ p.add_argument(
37
+ "-v", "--verbose", action="count", default=0,
38
+ help="raise log verbosity to debug (resolved engine names, per-page timing, ...); "
39
+ "progress logs (auto-selection, mineru output, OCR page progress) print by default",
40
+ )
41
+ p.add_argument("-q", "--quiet", action="store_true", help="suppress progress logs, print only errors")
42
+ p.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
43
+ return p
44
+
45
+
46
+ def _configure_logging(verbose: int, quiet: bool) -> None:
47
+ if quiet:
48
+ level = logging.ERROR
49
+ elif verbose >= 1:
50
+ level = logging.DEBUG
51
+ else:
52
+ level = logging.INFO
53
+ logging.basicConfig(
54
+ level=level,
55
+ format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
56
+ datefmt="%H:%M:%S",
57
+ stream=sys.stderr,
58
+ )
59
+
60
+
61
+ def main(argv: list[str] | None = None) -> int:
62
+ args = build_parser().parse_args(argv)
63
+ _configure_logging(args.verbose, args.quiet)
64
+
65
+ if args.list_engines:
66
+ print("\n".join(engine_names()))
67
+ return 0
68
+ if not args.path:
69
+ build_parser().print_help()
70
+ return 2
71
+
72
+ options = {}
73
+ if args.strategy:
74
+ options["strategy"] = args.strategy
75
+ if args.force_ocr:
76
+ options["force_ocr"] = True
77
+ if args.lang:
78
+ options["lang"] = args.lang
79
+ if args.dpi:
80
+ options["dpi"] = args.dpi
81
+ if args.timeout != 900:
82
+ options["timeout"] = args.timeout
83
+
84
+ try:
85
+ result = parse(args.path, engine=args.engine, ocr=args.ocr, **options)
86
+ except (FileNotFoundError, ValueError, ImportError, RuntimeError) as exc:
87
+ logger.error(str(exc))
88
+ return 1
89
+
90
+ if args.output:
91
+ saved = result.save(args.output)
92
+ if not args.quiet:
93
+ print(
94
+ f"[{result.engine}] {result.path} -> {saved} "
95
+ f"({len(result.content)} chars, {result.duration_s:.1f}s)",
96
+ file=sys.stderr,
97
+ )
98
+ else:
99
+ print(result.to_json() if args.format == "json" else result.content)
100
+ return 0
101
+
102
+
103
+ if __name__ == "__main__":
104
+ raise SystemExit(main())
@@ -0,0 +1,3 @@
1
+ from .base import ParserEngine
2
+
3
+ __all__ = ["ParserEngine"]
@@ -0,0 +1,34 @@
1
+ from abc import ABC, abstractmethod
2
+ from pathlib import Path
3
+
4
+ from ..result import ParseResult
5
+
6
+
7
+ class ParserEngine(ABC):
8
+ """Common interface every parsing backend implements.
9
+
10
+ Deliberately minimal — file path in, ParseResult out — so callers can swap
11
+ engines (mineru, docling, unstructured, markitdown, lite) without changing
12
+ any downstream code.
13
+ """
14
+
15
+ name: str
16
+
17
+ #: pip extra that pulls in this engine's dependencies, used in error messages.
18
+ extra: str
19
+
20
+ @abstractmethod
21
+ def parse(self, path: Path, ocr: str = "paddle", **options) -> ParseResult:
22
+ """Extracts content from the file.
23
+
24
+ ocr: "paddle" (default), "tesseract", or "none". Engines that bundle
25
+ their own OCR pipeline (mineru) or don't support choosing (markitdown)
26
+ document how they interpret this.
27
+ """
28
+ raise NotImplementedError
29
+
30
+ def _missing_dep(self, package: str) -> ImportError:
31
+ return ImportError(
32
+ f"Engine '{self.name}' needs '{package}'. "
33
+ f"Install it with: pip install parse-anything[{self.extra}]"
34
+ )
@@ -0,0 +1,99 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ from ..result import ParseResult, Timer
5
+ from .base import ParserEngine
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class DoclingEngine(ParserEngine):
11
+ """Docling (IBM) — high-fidelity structure extraction (reading order,
12
+ tables, headings) for PDFs and office formats, exported as markdown.
13
+
14
+ OCR backends docling supports natively: easyocr (its default), tesseract,
15
+ rapidocr. It has no PaddleOCR plugin, so ocr="paddle" falls back to
16
+ docling's default with a note in metadata — pick engine="mineru" (whose
17
+ pipeline is built on PaddleOCR models) when paddle-quality OCR matters.
18
+ """
19
+
20
+ name = "docling"
21
+ extra = "docling"
22
+
23
+ def parse(self, path: Path, ocr: str = "paddle", **options) -> ParseResult:
24
+ try:
25
+ from docling.document_converter import DocumentConverter
26
+ except ImportError:
27
+ raise self._missing_dep("docling") from None
28
+
29
+ converter, ocr_used, note = self._build_converter(ocr)
30
+ if note:
31
+ logger.warning(note)
32
+ logger.info("running docling on %s (ocr=%s)", path.name, ocr_used)
33
+
34
+ with Timer() as t:
35
+ result = converter.convert(str(path))
36
+ content = result.document.export_to_markdown()
37
+
38
+ metadata = {"pages": len(getattr(result.document, "pages", []) or [])}
39
+ if note:
40
+ metadata["ocr_note"] = note
41
+ logger.info("docling finished %s: %d pages, %d chars", path.name, metadata["pages"], len(content))
42
+ return ParseResult(
43
+ path=str(path),
44
+ engine=self.name,
45
+ content=content,
46
+ format="markdown",
47
+ ocr=ocr_used,
48
+ metadata=metadata,
49
+ duration_s=t.elapsed,
50
+ )
51
+
52
+ def _build_converter(self, ocr: str):
53
+ from docling.document_converter import DocumentConverter
54
+
55
+ if ocr == "tesseract":
56
+ try:
57
+ from docling.datamodel.base_models import InputFormat
58
+ from docling.datamodel.pipeline_options import (
59
+ PdfPipelineOptions,
60
+ TesseractOcrOptions,
61
+ )
62
+ from docling.document_converter import PdfFormatOption
63
+
64
+ opts = PdfPipelineOptions()
65
+ opts.do_ocr = True
66
+ opts.ocr_options = TesseractOcrOptions()
67
+ converter = DocumentConverter(
68
+ format_options={InputFormat.PDF: PdfFormatOption(pipeline_options=opts)}
69
+ )
70
+ return converter, "tesseract", None
71
+ except ImportError:
72
+ return (
73
+ DocumentConverter(),
74
+ "easyocr (docling default)",
75
+ "tesseract requested but docling's tesseract option is unavailable",
76
+ )
77
+
78
+ if ocr == "none":
79
+ try:
80
+ from docling.datamodel.base_models import InputFormat
81
+ from docling.datamodel.pipeline_options import PdfPipelineOptions
82
+ from docling.document_converter import PdfFormatOption
83
+
84
+ opts = PdfPipelineOptions()
85
+ opts.do_ocr = False
86
+ converter = DocumentConverter(
87
+ format_options={InputFormat.PDF: PdfFormatOption(pipeline_options=opts)}
88
+ )
89
+ return converter, None, None
90
+ except ImportError:
91
+ return DocumentConverter(), None, None
92
+
93
+ note = (
94
+ "docling has no PaddleOCR backend; used its default (easyocr). "
95
+ "Use engine='mineru' for a PaddleOCR-based pipeline."
96
+ if ocr in ("paddle", "paddleocr")
97
+ else None
98
+ )
99
+ return DocumentConverter(), "easyocr (docling default)", note
@@ -0,0 +1,67 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ from ..ocr import IMAGE_SUFFIXES, get_ocr
5
+ from ..result import ParseResult, Timer
6
+ from .base import ParserEngine
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class LiteEngine(ParserEngine):
12
+ """liteparse — the lightweight, fast path. PyMuPDF-based markdown for PDFs
13
+ (no ML models, near-instant), direct OCR for images, plain text extraction
14
+ for everything else PyMuPDF can open (epub, xps, txt).
15
+
16
+ Best when documents have a real text layer and speed matters more than
17
+ layout reconstruction.
18
+ """
19
+
20
+ name = "lite"
21
+ extra = "lite"
22
+
23
+ def parse(self, path: Path, ocr: str = "paddle", **options) -> ParseResult:
24
+ suffix = path.suffix.lower()
25
+
26
+ if suffix in IMAGE_SUFFIXES:
27
+ if ocr == "none":
28
+ raise ValueError(f"'{path.name}' is an image; lite needs ocr='paddle' or 'tesseract' for images.")
29
+ logger.info("lite: routing image %s to OCR backend '%s'", path.name, ocr)
30
+ backend = get_ocr(ocr, lang=options.get("lang"))
31
+ with Timer() as t:
32
+ text, meta = backend.parse_file(path)
33
+ return ParseResult(
34
+ path=str(path), engine=self.name, content=text, format="text",
35
+ ocr=backend.name, metadata=meta, duration_s=t.elapsed,
36
+ )
37
+
38
+ if suffix == ".pdf":
39
+ try:
40
+ import pymupdf4llm
41
+ except ImportError:
42
+ raise self._missing_dep("pymupdf4llm") from None
43
+ logger.info("lite: extracting text layer from %s via pymupdf4llm", path.name)
44
+ with Timer() as t:
45
+ content = pymupdf4llm.to_markdown(str(path))
46
+ logger.info("lite: finished %s, %d chars in %.2fs", path.name, len(content), t.elapsed)
47
+ return ParseResult(
48
+ path=str(path), engine=self.name, content=content, format="markdown",
49
+ ocr=None, metadata={}, duration_s=t.elapsed,
50
+ )
51
+
52
+ # Anything else PyMuPDF understands (epub, xps, cbz, txt...)
53
+ try:
54
+ import pymupdf as fitz
55
+ except ImportError:
56
+ try:
57
+ import fitz
58
+ except ImportError:
59
+ raise self._missing_dep("pymupdf") from None
60
+ with Timer() as t:
61
+ with fitz.open(str(path)) as doc:
62
+ page_count = doc.page_count
63
+ content = "\n\n".join(page.get_text() for page in doc)
64
+ return ParseResult(
65
+ path=str(path), engine=self.name, content=content, format="text",
66
+ ocr=None, metadata={"pages": page_count}, duration_s=t.elapsed,
67
+ )
@@ -0,0 +1,41 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ from ..result import ParseResult, Timer
5
+ from .base import ParserEngine
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class MarkItDownEngine(ParserEngine):
11
+ """MarkItDown (Microsoft) — fast conversion of office/web formats
12
+ (docx, pptx, xlsx, html, csv, json, zip, ...) straight to markdown.
13
+
14
+ It performs no OCR of its own, so the `ocr` parameter is ignored; route
15
+ scanned PDFs and images to mineru, docling, unstructured, or engine="ocr".
16
+ """
17
+
18
+ name = "markitdown"
19
+ extra = "markitdown"
20
+
21
+ def parse(self, path: Path, ocr: str = "paddle", **options) -> ParseResult:
22
+ try:
23
+ from markitdown import MarkItDown
24
+ except ImportError:
25
+ raise self._missing_dep("markitdown") from None
26
+
27
+ logger.info("running markitdown on %s", path.name)
28
+ with Timer() as t:
29
+ converter = MarkItDown(enable_plugins=options.get("enable_plugins", False))
30
+ result = converter.convert(str(path))
31
+ logger.info("markitdown finished %s: %d chars", path.name, len(result.text_content))
32
+
33
+ return ParseResult(
34
+ path=str(path),
35
+ engine=self.name,
36
+ content=result.text_content,
37
+ format="markdown",
38
+ ocr=None,
39
+ metadata={"title": getattr(result, "title", None)},
40
+ duration_s=t.elapsed,
41
+ )
@@ -0,0 +1,121 @@
1
+ import logging
2
+ import shutil
3
+ import subprocess
4
+ import tempfile
5
+ import threading
6
+ import time
7
+ from pathlib import Path
8
+
9
+ from ..result import ParseResult, Timer
10
+ from .base import ParserEngine
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class MinerUEngine(ParserEngine):
16
+ """MinerU — layout-aware PDF-to-markdown, strongest choice for scanned or
17
+ complex scientific PDFs (formulas, multi-column, tables).
18
+
19
+ Driven through the `mineru` CLI because its Python API churns between
20
+ releases while the CLI contract stays stable. MinerU ships its own OCR
21
+ pipeline built on PaddleOCR models, so the `ocr` parameter only controls
22
+ *whether* OCR runs, not which backend does it:
23
+ ocr="none" -> text-layer extraction only (mineru -m txt)
24
+ anything else -> auto-detect per page (mineru -m auto)
25
+ options force_ocr -> OCR every page (mineru -m ocr)
26
+ """
27
+
28
+ name = "mineru"
29
+ extra = "mineru"
30
+
31
+ def parse(self, path: Path, ocr: str = "paddle", **options) -> ParseResult:
32
+ exe = shutil.which("mineru")
33
+ if exe is None:
34
+ raise self._missing_dep("mineru (CLI not found on PATH)")
35
+
36
+ if options.get("force_ocr"):
37
+ method = "ocr"
38
+ elif ocr == "none":
39
+ method = "txt"
40
+ else:
41
+ method = "auto"
42
+
43
+ timeout = options.get("timeout", 900)
44
+
45
+ with Timer() as t, tempfile.TemporaryDirectory(prefix="parse_anything_mineru_") as tmp:
46
+ cmd = [exe, "-p", str(path), "-o", tmp, "-m", method]
47
+ if options.get("lang"):
48
+ cmd += ["--lang", options["lang"]]
49
+ logger.info("running mineru (method=%s, timeout=%ss): %s", method, timeout, " ".join(cmd))
50
+
51
+ self._run_with_live_logs(cmd, timeout=timeout, path=path)
52
+
53
+ # Output lands at <tmp>/<stem>/<method>/<stem>.md; rglob keeps us
54
+ # robust to layout changes across MinerU versions.
55
+ md_files = sorted(Path(tmp).rglob("*.md"), key=lambda p: p.stat().st_size)
56
+ if not md_files:
57
+ raise RuntimeError(f"mineru produced no markdown output for {path}")
58
+ content = md_files[-1].read_text(encoding="utf-8")
59
+ logger.info("mineru wrote %s (%d chars)", md_files[-1].name, len(content))
60
+
61
+ return ParseResult(
62
+ path=str(path),
63
+ engine=self.name,
64
+ content=content,
65
+ format="markdown",
66
+ ocr=None if method == "txt" else "mineru-builtin (PaddleOCR models)",
67
+ metadata={"method": method},
68
+ duration_s=t.elapsed,
69
+ )
70
+
71
+ @staticmethod
72
+ def _run_with_live_logs(cmd: list[str], timeout: int, path: Path) -> None:
73
+ """Runs mineru streaming its stdout/stderr to the logger as it happens.
74
+
75
+ mineru can take minutes on large or scanned PDFs with no output at
76
+ all if captured all-at-once, which made a hung/slow run indistinguishable
77
+ from a silently-progressing one. Streaming line-by-line plus a periodic
78
+ "still running" heartbeat fixes that.
79
+ """
80
+ proc = subprocess.Popen(
81
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1
82
+ )
83
+ tail: list[str] = []
84
+
85
+ def _pump():
86
+ assert proc.stdout is not None
87
+ for line in proc.stdout:
88
+ line = line.rstrip()
89
+ if line:
90
+ tail.append(line)
91
+ del tail[:-40]
92
+ logger.info("[mineru] %s", line)
93
+
94
+ pump = threading.Thread(target=_pump, daemon=True)
95
+ pump.start()
96
+
97
+ start = time.monotonic()
98
+ last_heartbeat = start
99
+ while True:
100
+ try:
101
+ returncode = proc.wait(timeout=5)
102
+ break
103
+ except subprocess.TimeoutExpired:
104
+ elapsed = time.monotonic() - start
105
+ if elapsed > timeout:
106
+ proc.kill()
107
+ proc.wait()
108
+ raise RuntimeError(
109
+ f"mineru timed out after {timeout}s parsing {path.name}. "
110
+ "Pass a larger timeout=... (or --timeout on the CLI), "
111
+ "or try engine='docling'/'lite' instead."
112
+ ) from None
113
+ if time.monotonic() - last_heartbeat > 30:
114
+ logger.info("[mineru] still running... %.0fs elapsed", elapsed)
115
+ last_heartbeat = time.monotonic()
116
+
117
+ pump.join(timeout=5)
118
+ if returncode != 0:
119
+ raise RuntimeError(
120
+ f"mineru failed (exit {returncode}):\n" + "\n".join(tail)
121
+ )
@@ -0,0 +1,34 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ from ..ocr import get_ocr
5
+ from ..result import ParseResult, Timer
6
+ from .base import ParserEngine
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class OCROnlyEngine(ParserEngine):
12
+ """Pure OCR: no layout analysis, just recognized text from images or
13
+ rasterized PDF pages — via PaddleOCR (default) or Tesseract."""
14
+
15
+ name = "ocr"
16
+ extra = "ocr"
17
+
18
+ def parse(self, path: Path, ocr: str = "paddle", **options) -> ParseResult:
19
+ backend_name = "paddle" if ocr in (None, "", "none") else ocr
20
+ dpi = options.get("dpi", 200)
21
+ logger.info("running OCR on %s with backend '%s' (dpi=%d)", path.name, backend_name, dpi)
22
+ backend = get_ocr(backend_name, lang=options.get("lang"))
23
+ with Timer() as t:
24
+ text, meta = backend.parse_file(path, dpi=dpi)
25
+ logger.info("OCR finished %s: %s pages, %d chars in %.2fs", path.name, meta.get("pages"), len(text), t.elapsed)
26
+ return ParseResult(
27
+ path=str(path),
28
+ engine=self.name,
29
+ content=text,
30
+ format="text",
31
+ ocr=backend.name,
32
+ metadata=meta,
33
+ duration_s=t.elapsed,
34
+ )
@@ -0,0 +1,81 @@
1
+ import logging
2
+ import os
3
+ from pathlib import Path
4
+
5
+ from ..result import ParseResult, Timer
6
+ from .base import ParserEngine
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # unstructured selects its OCR agent via this env var at partition time.
11
+ _OCR_AGENTS = {
12
+ "paddle": "unstructured.partition.utils.ocr_models.paddle_ocr.OCRAgentPaddle",
13
+ "tesseract": "unstructured.partition.utils.ocr_models.tesseract_ocr.OCRAgentTesseract",
14
+ }
15
+
16
+
17
+ class UnstructuredEngine(ParserEngine):
18
+ """unstructured.io — broadest format coverage (emails, html, office, pdf...),
19
+ returns typed elements which we render to markdown.
20
+
21
+ ocr="paddle" needs the `unstructured.paddleocr` package; ocr="tesseract"
22
+ uses unstructured's default tesseract agent.
23
+ """
24
+
25
+ name = "unstructured"
26
+ extra = "unstructured"
27
+
28
+ def parse(self, path: Path, ocr: str = "paddle", **options) -> ParseResult:
29
+ try:
30
+ from unstructured.partition.auto import partition
31
+ except ImportError:
32
+ raise self._missing_dep("unstructured") from None
33
+
34
+ strategy = options.get("strategy", "auto")
35
+ if ocr == "none":
36
+ strategy = options.get("strategy", "fast")
37
+
38
+ previous_agent = os.environ.get("OCR_AGENT")
39
+ if ocr in _OCR_AGENTS:
40
+ os.environ["OCR_AGENT"] = _OCR_AGENTS[ocr]
41
+ logger.info("running unstructured on %s (strategy=%s, ocr=%s)", path.name, strategy, ocr)
42
+ try:
43
+ with Timer() as t:
44
+ elements = partition(filename=str(path), strategy=strategy)
45
+ finally:
46
+ if ocr in _OCR_AGENTS:
47
+ if previous_agent is None:
48
+ os.environ.pop("OCR_AGENT", None)
49
+ else:
50
+ os.environ["OCR_AGENT"] = previous_agent
51
+
52
+ logger.info("unstructured finished %s: %d elements", path.name, len(elements))
53
+ content = self._to_markdown(elements)
54
+ return ParseResult(
55
+ path=str(path),
56
+ engine=self.name,
57
+ content=content,
58
+ format="markdown",
59
+ ocr=ocr if ocr in _OCR_AGENTS else None,
60
+ metadata={"strategy": strategy, "elements": len(elements)},
61
+ duration_s=t.elapsed,
62
+ )
63
+
64
+ @staticmethod
65
+ def _to_markdown(elements) -> str:
66
+ lines: list[str] = []
67
+ for el in elements:
68
+ category = getattr(el, "category", "")
69
+ text = str(el).strip()
70
+ if not text:
71
+ continue
72
+ if category == "Title":
73
+ lines.append(f"## {text}")
74
+ elif category == "ListItem":
75
+ lines.append(f"- {text}")
76
+ elif category == "Table":
77
+ html = getattr(getattr(el, "metadata", None), "text_as_html", None)
78
+ lines.append(html if html else text)
79
+ else:
80
+ lines.append(text)
81
+ return "\n\n".join(lines)
@@ -0,0 +1,117 @@
1
+ import importlib.util
2
+ import logging
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ from .engines.base import ParserEngine
7
+ from .ocr.base import IMAGE_SUFFIXES
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # Canonical engine name -> (module, class). Imported lazily so that having one
12
+ # backend installed is enough to use it — the others can stay uninstalled.
13
+ _ENGINES: dict[str, tuple[str, str]] = {
14
+ "mineru": ("parse_anything.engines.mineru_engine", "MinerUEngine"),
15
+ "docling": ("parse_anything.engines.docling_engine", "DoclingEngine"),
16
+ "unstructured": ("parse_anything.engines.unstructured_engine", "UnstructuredEngine"),
17
+ "markitdown": ("parse_anything.engines.markitdown_engine", "MarkItDownEngine"),
18
+ "lite": ("parse_anything.engines.lite_engine", "LiteEngine"),
19
+ "ocr": ("parse_anything.engines.ocr_engine", "OCROnlyEngine"),
20
+ }
21
+
22
+ _ALIASES = {
23
+ "miner-u": "mineru",
24
+ "miner_u": "mineru",
25
+ "magic-pdf": "mineru",
26
+ "unstructured.io": "unstructured",
27
+ "markitdown": "markitdown",
28
+ "mark-it-down": "markitdown",
29
+ "liteparse": "lite",
30
+ "lite-parse": "lite",
31
+ "pymupdf": "lite",
32
+ }
33
+
34
+ # Python package whose presence means the engine is usable (for auto mode).
35
+ _AVAILABILITY_PROBE = {
36
+ "mineru": "mineru",
37
+ "docling": "docling",
38
+ "unstructured": "unstructured",
39
+ "markitdown": "markitdown",
40
+ "lite": "pymupdf",
41
+ }
42
+
43
+ OFFICE_SUFFIXES = {".docx", ".doc", ".pptx", ".ppt", ".xlsx", ".xls"}
44
+ MARKUP_SUFFIXES = {".html", ".htm", ".csv", ".json", ".xml", ".epub", ".md", ".txt", ".zip", ".msg", ".eml"}
45
+
46
+
47
+ def engine_names() -> list[str]:
48
+ return list(_ENGINES)
49
+
50
+
51
+ def resolve_engine_name(name: str) -> str:
52
+ key = name.strip().lower()
53
+ key = _ALIASES.get(key, key)
54
+ if key not in _ENGINES and key != "auto":
55
+ supported = ", ".join(["auto", *_ENGINES])
56
+ raise ValueError(f"Unknown engine '{name}'. Supported: {supported}")
57
+ return key
58
+
59
+
60
+ def get_engine(name: str, path: Path | None = None) -> ParserEngine:
61
+ key = resolve_engine_name(name)
62
+ if key == "auto":
63
+ if path is None:
64
+ raise ValueError("engine='auto' needs a file path to pick from")
65
+ key = _pick_engine_for(path)
66
+ else:
67
+ logger.debug("resolved engine '%s' -> '%s'", name, key)
68
+
69
+ module_name, class_name = _ENGINES[key]
70
+ import importlib
71
+
72
+ module = importlib.import_module(module_name)
73
+ logger.debug("loaded engine class %s from %s", class_name, module_name)
74
+ return getattr(module, class_name)()
75
+
76
+
77
+ def _is_available(engine: str) -> bool:
78
+ if engine == "mineru":
79
+ # mineru is driven via its CLI; either the package or binary suffices.
80
+ return shutil.which("mineru") is not None or importlib.util.find_spec("mineru") is not None
81
+ probe = _AVAILABILITY_PROBE.get(engine)
82
+ return probe is not None and importlib.util.find_spec(probe) is not None
83
+
84
+
85
+ def _ocr_available() -> bool:
86
+ return (
87
+ importlib.util.find_spec("paddleocr") is not None
88
+ or importlib.util.find_spec("pytesseract") is not None
89
+ )
90
+
91
+
92
+ def _pick_engine_for(path: Path) -> str:
93
+ """Best installed engine for the file type; preference orders reflect each
94
+ engine's strengths (mineru on PDFs, docling on office docs, etc.)."""
95
+ suffix = path.suffix.lower()
96
+
97
+ if suffix in IMAGE_SUFFIXES and _ocr_available():
98
+ return "ocr"
99
+ if suffix == ".pdf":
100
+ order = ["mineru", "docling", "unstructured", "lite"]
101
+ elif suffix in OFFICE_SUFFIXES:
102
+ order = ["docling", "markitdown", "unstructured"]
103
+ elif suffix in MARKUP_SUFFIXES:
104
+ order = ["markitdown", "unstructured", "lite"]
105
+ else:
106
+ order = ["unstructured", "markitdown", "docling", "lite"]
107
+
108
+ logger.debug("auto-selection order for '%s' (%s): %s", path.name, suffix, order)
109
+ for engine in order:
110
+ if _is_available(engine):
111
+ logger.info("auto-selected engine '%s' for %s", engine, path.name)
112
+ return engine
113
+
114
+ raise ImportError(
115
+ f"No parsing engine installed that can handle '{path.name}'. "
116
+ f"Tried: {', '.join(order)}. Install one, e.g.: pip install parse-anything[docling]"
117
+ )
@@ -0,0 +1,18 @@
1
+ from .base import IMAGE_SUFFIXES, OCREngine
2
+
3
+
4
+ def get_ocr(name: str = "paddle", lang: str | None = None) -> OCREngine:
5
+ """Returns the requested OCR backend: 'paddle' (default) or 'tesseract'."""
6
+ key = (name or "paddle").lower()
7
+ if key in ("paddle", "paddleocr", "pp-ocr", "ppocr"):
8
+ from .paddle_engine import PaddleOCREngine
9
+
10
+ return PaddleOCREngine(lang=lang or "en")
11
+ if key in ("tesseract", "pytesseract"):
12
+ from .tesseract_engine import TesseractEngine
13
+
14
+ return TesseractEngine(lang=lang or "eng")
15
+ raise ValueError(f"Unknown OCR backend '{name}'. Supported: paddle, tesseract")
16
+
17
+
18
+ __all__ = ["OCREngine", "IMAGE_SUFFIXES", "get_ocr"]
@@ -0,0 +1,59 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from pathlib import Path
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ IMAGE_SUFFIXES = {".png", ".jpg", ".jpeg", ".tif", ".tiff", ".bmp", ".webp", ".gif"}
8
+
9
+
10
+ class OCREngine(ABC):
11
+ """Text recognition backend: image (or scanned PDF) in, plain text out."""
12
+
13
+ name: str
14
+
15
+ @abstractmethod
16
+ def image_to_text(self, image_path: Path) -> str:
17
+ raise NotImplementedError
18
+
19
+ def parse_file(self, path: Path, dpi: int = 200) -> tuple[str, dict]:
20
+ """OCRs an image directly, or renders each PDF page to an image first.
21
+
22
+ Returns (text, metadata).
23
+ """
24
+ if path.suffix.lower() == ".pdf":
25
+ pages = []
26
+ logger.info("[%s] rasterizing %s at %d dpi", self.name, path.name, dpi)
27
+ for i, image_path in enumerate(render_pdf_pages(path, dpi=dpi), start=1):
28
+ logger.info("[%s] OCR page %d of %s", self.name, i, path.name)
29
+ pages.append(self.image_to_text(image_path))
30
+ text = "\n\n".join(pages)
31
+ logger.info("[%s] finished %s: %d pages", self.name, path.name, len(pages))
32
+ return text, {"pages": len(pages), "dpi": dpi}
33
+ logger.info("[%s] OCR %s", self.name, path.name)
34
+ return self.image_to_text(path), {"pages": 1}
35
+
36
+
37
+ def render_pdf_pages(path: Path, dpi: int = 200):
38
+ """Yields temporary PNG paths, one per PDF page, for OCR backends that want files."""
39
+ import tempfile
40
+
41
+ try:
42
+ import pymupdf # noqa: F401 (>= 1.24 exposes the new name)
43
+ import pymupdf as fitz
44
+ except ImportError:
45
+ try:
46
+ import fitz # older PyMuPDF releases
47
+ except ImportError as exc:
48
+ raise ImportError(
49
+ "OCR on PDFs needs PyMuPDF to rasterize pages. "
50
+ "Install it with: pip install parse-anything[ocr]"
51
+ ) from exc
52
+
53
+ tmpdir = Path(tempfile.mkdtemp(prefix="parse_anything_ocr_"))
54
+ with fitz.open(path) as doc:
55
+ for i, page in enumerate(doc, start=1):
56
+ pix = page.get_pixmap(dpi=dpi)
57
+ out = tmpdir / f"page_{i:04d}.png"
58
+ pix.save(out)
59
+ yield out
@@ -0,0 +1,66 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ from .base import OCREngine
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class PaddleOCREngine(OCREngine):
10
+ """PaddleOCR (PP-OCRv5) — the default OCR backend.
11
+
12
+ Chosen as default because PP-OCRv5 currently posts the strongest open-source
13
+ accuracy benchmarks across printed, handwritten, and multilingual text.
14
+ Works with both the PaddleOCR 3.x `predict` API and the legacy 2.x `ocr` API.
15
+ """
16
+
17
+ name = "paddle"
18
+
19
+ def __init__(self, lang: str = "en"):
20
+ try:
21
+ from paddleocr import PaddleOCR
22
+ except ImportError as exc:
23
+ raise ImportError(
24
+ "PaddleOCR is not installed. Install it with: pip install parse-anything[paddle]"
25
+ ) from exc
26
+
27
+ logger.info("loading PaddleOCR (PP-OCRv5, lang=%s) — first run downloads model weights", lang)
28
+ try:
29
+ # 3.x: turn off document-rotation/unwarp preprocessing — we feed
30
+ # clean rasterized pages, and these extra models slow cold start.
31
+ # enable_mkldnn=False: PaddleX's default oneDNN + new-IR (PIR) CPU
32
+ # path can't convert some double-array attributes used by the
33
+ # PP-OCRv5 detection model, raising "ConvertPirAttribute2Runtime
34
+ # Attribute not support" — falling back to the plain Paddle
35
+ # executor avoids that unimplemented path.
36
+ self._ocr = PaddleOCR(
37
+ lang=lang,
38
+ use_doc_orientation_classify=False,
39
+ use_doc_unwarping=False,
40
+ use_textline_orientation=False,
41
+ enable_mkldnn=False,
42
+ )
43
+ except TypeError:
44
+ self._ocr = PaddleOCR(lang=lang)
45
+ logger.info("PaddleOCR ready")
46
+
47
+ def image_to_text(self, image_path: Path) -> str:
48
+ if hasattr(self._ocr, "predict"): # PaddleOCR >= 3.0
49
+ lines: list[str] = []
50
+ for res in self._ocr.predict(input=str(image_path)):
51
+ texts = None
52
+ try:
53
+ texts = res["rec_texts"]
54
+ except (TypeError, KeyError, IndexError):
55
+ texts = getattr(res, "rec_texts", None)
56
+ if texts:
57
+ lines.extend(texts)
58
+ return "\n".join(lines)
59
+
60
+ # PaddleOCR 2.x: list of [box, (text, confidence)] per page
61
+ lines = []
62
+ for page in self._ocr.ocr(str(image_path)) or []:
63
+ for entry in page or []:
64
+ if entry and len(entry) > 1:
65
+ lines.append(entry[1][0])
66
+ return "\n".join(lines)
@@ -0,0 +1,47 @@
1
+ import logging
2
+ import shutil
3
+ from pathlib import Path
4
+
5
+ from .base import OCREngine
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class TesseractEngine(OCREngine):
11
+ """Tesseract via pytesseract — ubiquitous, CPU-light fallback OCR.
12
+
13
+ Needs the tesseract binary on PATH (Windows: UB-Mannheim installer or
14
+ `choco install tesseract`; Linux: `apt install tesseract-ocr`).
15
+ """
16
+
17
+ name = "tesseract"
18
+
19
+ def __init__(self, lang: str = "eng"):
20
+ try:
21
+ import pytesseract
22
+ from PIL import Image # noqa: F401
23
+ except ImportError as exc:
24
+ raise ImportError(
25
+ "pytesseract/Pillow are not installed. "
26
+ "Install them with: pip install parse-anything[tesseract]"
27
+ ) from exc
28
+
29
+ self._pytesseract = pytesseract
30
+ self._lang = lang
31
+
32
+ if shutil.which("tesseract") is None and not getattr(
33
+ pytesseract.pytesseract, "tesseract_cmd", None
34
+ ):
35
+ raise RuntimeError(
36
+ "The tesseract binary was not found on PATH. Install it "
37
+ "(Windows: https://github.com/UB-Mannheim/tesseract/wiki, "
38
+ "or `choco install tesseract`) or set "
39
+ "pytesseract.pytesseract.tesseract_cmd to its location."
40
+ )
41
+ logger.info("Tesseract OCR ready (lang=%s)", lang)
42
+
43
+ def image_to_text(self, image_path: Path) -> str:
44
+ from PIL import Image
45
+
46
+ with Image.open(image_path) as img:
47
+ return self._pytesseract.image_to_string(img, lang=self._lang).strip()
@@ -0,0 +1,60 @@
1
+ import json
2
+ import time
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+
6
+
7
+ @dataclass
8
+ class ParseResult:
9
+ """What every engine returns: extracted content plus enough metadata to trace it.
10
+
11
+ `content` is markdown whenever the engine can produce it (headings, tables),
12
+ plain text otherwise — `format` says which one you got.
13
+ """
14
+
15
+ path: str
16
+ engine: str
17
+ content: str
18
+ format: str = "markdown" # "markdown" | "text"
19
+ ocr: str | None = None # OCR backend used, if any ("paddle", "tesseract")
20
+ metadata: dict = field(default_factory=dict)
21
+ duration_s: float = 0.0
22
+
23
+ @property
24
+ def text(self) -> str:
25
+ return self.content
26
+
27
+ def to_dict(self) -> dict:
28
+ return {
29
+ "path": self.path,
30
+ "engine": self.engine,
31
+ "format": self.format,
32
+ "ocr": self.ocr,
33
+ "duration_s": round(self.duration_s, 3),
34
+ "metadata": self.metadata,
35
+ "content": self.content,
36
+ }
37
+
38
+ def to_json(self, indent: int = 2) -> str:
39
+ return json.dumps(self.to_dict(), indent=indent, ensure_ascii=False)
40
+
41
+ def save(self, out_path: str | Path) -> Path:
42
+ out = Path(out_path)
43
+ out.parent.mkdir(parents=True, exist_ok=True)
44
+ if out.suffix == ".json":
45
+ out.write_text(self.to_json(), encoding="utf-8")
46
+ else:
47
+ out.write_text(self.content, encoding="utf-8")
48
+ return out
49
+
50
+
51
+ class Timer:
52
+ """Tiny context manager so engines can fill ParseResult.duration_s uniformly."""
53
+
54
+ def __enter__(self):
55
+ self.start = time.perf_counter()
56
+ return self
57
+
58
+ def __exit__(self, *exc):
59
+ self.elapsed = time.perf_counter() - self.start
60
+ return False
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: parse-anything
3
+ Version: 0.1.0
4
+ Summary: One interface to parse any document: pick an engine (mineru, docling, unstructured, markitdown, lite) and get clean markdown out.
5
+ Author: Yugesh
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/yugeshsr13/parse-anything
8
+ Project-URL: Repository, https://github.com/yugeshsr13/parse-anything
9
+ Project-URL: Issues, https://github.com/yugeshsr13/parse-anything/issues
10
+ Keywords: pdf,parsing,ocr,docling,mineru,unstructured,markitdown,paddleocr,tesseract
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Topic :: Text Processing :: Markup
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: lite
25
+ Requires-Dist: pymupdf4llm>=0.0.17; extra == "lite"
26
+ Provides-Extra: docling
27
+ Requires-Dist: docling>=2.15; extra == "docling"
28
+ Provides-Extra: unstructured
29
+ Requires-Dist: unstructured[all-docs]>=0.16; extra == "unstructured"
30
+ Provides-Extra: markitdown
31
+ Requires-Dist: markitdown[all]>=0.1.1; extra == "markitdown"
32
+ Provides-Extra: mineru
33
+ Requires-Dist: mineru[core]>=2.0; extra == "mineru"
34
+ Provides-Extra: tesseract
35
+ Requires-Dist: pytesseract>=0.3.10; extra == "tesseract"
36
+ Requires-Dist: Pillow>=10.0; extra == "tesseract"
37
+ Requires-Dist: pymupdf>=1.24; extra == "tesseract"
38
+ Provides-Extra: paddle
39
+ Requires-Dist: paddleocr>=3.0; extra == "paddle"
40
+ Requires-Dist: paddlepaddle>=3.0; extra == "paddle"
41
+ Requires-Dist: pymupdf>=1.24; extra == "paddle"
42
+ Provides-Extra: ocr
43
+ Requires-Dist: parse-anything[paddle,tesseract]; extra == "ocr"
44
+ Provides-Extra: all
45
+ Requires-Dist: parse-anything[docling,lite,markitdown,mineru,ocr,unstructured]; extra == "all"
46
+ Provides-Extra: dev
47
+ Requires-Dist: pytest>=8.0; extra == "dev"
48
+ Dynamic: license-file
49
+
50
+ # parse-anything
51
+
52
+ One interface to parse any document. Pick an engine — **MinerU**, **Docling**, **unstructured**, **MarkItDown**, or the PyMuPDF **lite** path — pass a file path, get clean markdown/text back. OCR runs on **PaddleOCR (PP-OCRv5, default)** or **Tesseract**.
53
+
54
+ ```python
55
+ from parse_anything import parse
56
+
57
+ result = parse("report.pdf", engine="miner-u")
58
+ print(result.content) # markdown
59
+ print(result.duration_s) # seconds
60
+ ```
61
+
62
+ ```bash
63
+ parse-anything report.pdf --engine miner-u -o report.md
64
+ parse-anything scan.png --engine ocr --ocr paddle
65
+ parse-anything deck.pptx # auto-picks the best installed engine
66
+ ```
67
+
68
+ ## Install
69
+
70
+ Core has **zero dependencies**; each engine is an optional extra:
71
+
72
+ ```bash
73
+ pip install -e .[all] # everything
74
+ pip install -e .[mineru] # MinerU (scanned/complex PDFs)
75
+ pip install -e .[docling] # Docling (structure fidelity)
76
+ pip install -e .[unstructured] # unstructured (broadest formats)
77
+ pip install -e .[markitdown] # MarkItDown (office/web -> md)
78
+ pip install -e .[lite] # PyMuPDF fast path
79
+ pip install -e .[paddle] # PaddleOCR (PP-OCRv5)
80
+ pip install -e .[tesseract] # pytesseract (+ install the tesseract binary)
81
+ ```
82
+
83
+ Windows Tesseract binary: [UB-Mannheim build](https://github.com/UB-Mannheim/tesseract/wiki) or `choco install tesseract`.
84
+
85
+ ## Engines
86
+
87
+ | `--engine` | Backed by | Reach for it when |
88
+ |------------|-----------|-------------------|
89
+ | `mineru` / `miner-u` | [MinerU](https://github.com/opendatalab/MinerU) | scanned or complex PDFs — formulas, tables, multi-column; OCR built on PaddleOCR models |
90
+ | `docling` | [Docling](https://github.com/docling-project/docling) | you care about reading order, tables, office formats |
91
+ | `unstructured` | [unstructured](https://github.com/Unstructured-IO/unstructured) | odd formats: email, html, mixed batches |
92
+ | `markitdown` | [MarkItDown](https://github.com/microsoft/markitdown) | fast docx/pptx/xlsx/html → markdown (no OCR) |
93
+ | `lite` / `liteparse` | [PyMuPDF4LLM](https://github.com/pymupdf/RAG) | speed on PDFs that already have a text layer |
94
+ | `ocr` | PaddleOCR / Tesseract | images or fully scanned docs, raw text out |
95
+ | `auto` (default) | — | picks the best installed engine for the file type |
96
+
97
+ ## OCR
98
+
99
+ `--ocr paddle` (default) uses **PaddleOCR PP-OCRv5** — the current open-source benchmark leader. `--ocr tesseract` is the portable fallback. `--ocr none` skips OCR (text-layer extraction only).
100
+
101
+ ## Logging
102
+
103
+ Progress prints to stderr by default (auto-selected engine, OCR page-by-page, live MinerU subprocess output with a heartbeat on long runs). `-v` for debug detail, `-q` to silence it. As a library, nothing prints unless you call `logging.basicConfig(...)` yourself.
104
+
105
+ See [SKILLS.md](SKILLS.md) for the full skill contract (inputs, outputs, failure modes).
106
+
107
+ ## Layout
108
+
109
+ ```
110
+ src/parse_anything/
111
+ ├── __init__.py # parse(path, engine=..., ocr=...) public API
112
+ ├── factory.py # engine registry, aliases, auto-selection
113
+ ├── result.py # ParseResult
114
+ ├── cli.py # parse-anything CLI
115
+ ├── engines/ # one module per backend (lazy imports)
116
+ └── ocr/ # paddle + tesseract backends, PDF rasterization
117
+ ```
118
+
119
+ ## Tests
120
+
121
+ ```bash
122
+ pip install -e .[dev]
123
+ pytest
124
+ ```
@@ -0,0 +1,23 @@
1
+ parse_anything/__init__.py,sha256=zEH3giD9uei5oQefVCHejGKok9qrFTofRg5KWV-AkL4,1918
2
+ parse_anything/__main__.py,sha256=k1ocEWawweo1qCJWNFAAvyxz3tcY13dzvCenHszij30,48
3
+ parse_anything/cli.py,sha256=fKoPsHBjDtQa0EGVim6v6Mh4zVDmT3mN7XZexSgh0ZM,3836
4
+ parse_anything/factory.py,sha256=8b5cw8Z-L0XFsjMx1ylXaoYQndwL7znwqEZd4pYUdII,4114
5
+ parse_anything/result.py,sha256=Gm1HpSlocGiZm1KbFUj_eAzGhjd9gl1bhHir46j0UMw,1744
6
+ parse_anything/engines/__init__.py,sha256=ouLF6kn4xU515kBDJFgeEFCkbWz5PE8-X-OArnvMyNU,59
7
+ parse_anything/engines/base.py,sha256=X-cBvJej18wMz7ZzKv4ALMGXKuXIHj6nnQ4tE1PbpHM,1104
8
+ parse_anything/engines/docling_engine.py,sha256=nFOF9O_B9lSIveUbG_9p6m8vJbzMm9ihAlDhXzS2VNk,3768
9
+ parse_anything/engines/lite_engine.py,sha256=2mB15MKhJBlx_TM2Q5ygWirAvCMKwadkVDq6Wyirvq0,2641
10
+ parse_anything/engines/markitdown_engine.py,sha256=vAV5iziUXToKWoygLMvANCMS3QJ3vn4k3jnFjqyBYxE,1383
11
+ parse_anything/engines/mineru_engine.py,sha256=nlFjbX_coNFK7a6jpiqJwVEZ6Y01YU6CrxuOYiZ_lYk,4653
12
+ parse_anything/engines/ocr_engine.py,sha256=gE9Si_skvrbWp7n2Jr4wCo-7vNKtmRtpobP1YKxcsUs,1191
13
+ parse_anything/engines/unstructured_engine.py,sha256=IpZL7qJY05nL_fUE7oU1gSqJzzCfNERZLYSQXZ-y3Zs,2892
14
+ parse_anything/ocr/__init__.py,sha256=_XTcDas7bp5xJbjwBz_WKi51uDXyHLpmbck1sgiLJDo,685
15
+ parse_anything/ocr/base.py,sha256=OWlNEZ0d8i3TRAoKvtKYY9P65HZDysF9w3eZ-7as1Ys,2165
16
+ parse_anything/ocr/paddle_engine.py,sha256=2PcaCtmNA5pTlJ4rDihozWPvHIT945YUdl3FW_DvJ9Y,2592
17
+ parse_anything/ocr/tesseract_engine.py,sha256=a4DBsLPw1lT-PvmdJYFnb22uY8_tr9283VCgwbFOKEg,1559
18
+ parse_anything-0.1.0.dist-info/licenses/LICENSE,sha256=brhk1hp9uq9FHo4jpQv6JY5FzWamZ9rp3O9QgfU_L1w,1063
19
+ parse_anything-0.1.0.dist-info/METADATA,sha256=tz1vNgO4FOupo9PIhBoxeaaRrYseU-hoI3_626AWiis,5732
20
+ parse_anything-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
21
+ parse_anything-0.1.0.dist-info/entry_points.txt,sha256=5Kwj9SMu3dr-H7plRU6wSPzJI1Ek3yV261Oy-lwkcJg,59
22
+ parse_anything-0.1.0.dist-info/top_level.txt,sha256=C9cS-cX-LF54R-82U7UFSQWGGu1-DIUVPktRlMIoOpQ,15
23
+ parse_anything-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ parse-anything = parse_anything.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yugesh
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 @@
1
+ parse_anything