forked from datalab-to/marker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpdf.py
More file actions
202 lines (178 loc) · 7.98 KB
/
Copy pathpdf.py
File metadata and controls
202 lines (178 loc) · 7.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import os
from marker.schema.document import Document
os.environ["TOKENIZERS_PARALLELISM"] = "false" # disables a tokenizers warning
from collections import defaultdict
from typing import Annotated, Any, Dict, List, Optional, Type, Tuple, Union
import io
from contextlib import contextmanager
import tempfile
from marker.processors import BaseProcessor
from marker.services import BaseService
from marker.processors.llm.llm_table_merge import LLMTableMergeProcessor
from marker.providers.registry import provider_from_filepath
from marker.builders.document import DocumentBuilder
from marker.builders.layout import LayoutBuilder
from marker.builders.line import LineBuilder
from marker.builders.ocr import OcrBuilder
from marker.builders.structure import StructureBuilder
from marker.converters import BaseConverter
from marker.processors.blockquote import BlockquoteProcessor
from marker.processors.code import CodeProcessor
from marker.processors.debug import DebugProcessor
from marker.processors.document_toc import DocumentTOCProcessor
from marker.processors.equation import EquationProcessor
from marker.processors.footnote import FootnoteProcessor
from marker.processors.ignoretext import IgnoreTextProcessor
from marker.processors.line_numbers import LineNumbersProcessor
from marker.processors.list import ListProcessor
from marker.processors.llm.llm_complex import LLMComplexRegionProcessor
from marker.processors.llm.llm_form import LLMFormProcessor
from marker.processors.llm.llm_image_description import LLMImageDescriptionProcessor
from marker.processors.llm.llm_table import LLMTableProcessor
from marker.processors.page_header import PageHeaderProcessor
from marker.processors.reference import ReferenceProcessor
from marker.processors.sectionheader import SectionHeaderProcessor
from marker.processors.table import TableProcessor
from marker.processors.text import TextProcessor
from marker.processors.block_relabel import BlockRelabelProcessor
from marker.processors.blank_page import BlankPageProcessor
from marker.processors.llm.llm_equation import LLMEquationProcessor
from marker.renderers.markdown import MarkdownRenderer
from marker.schema import BlockTypes
from marker.schema.blocks import Block
from marker.schema.registry import register_block_class
from marker.util import strings_to_classes
from marker.processors.llm.llm_handwriting import LLMHandwritingProcessor
from marker.processors.order import OrderProcessor
from marker.services.gemini import GoogleGeminiService
from marker.processors.line_merge import LineMergeProcessor
from marker.processors.list_line_explode import ListItemLineExplodeProcessor
from marker.processors.list_gap_cluster import ListItemGapClusterProcessor # noqa: F401 # alternate strategy
from marker.processors.llm.llm_mathblock import LLMMathBlockProcessor
from marker.processors.llm.llm_page_correction import LLMPageCorrectionProcessor
from marker.processors.llm.llm_sectionheader import LLMSectionHeaderProcessor
class PdfConverter(BaseConverter):
"""
A converter for processing and rendering PDF files into Markdown, JSON, HTML and other formats.
"""
override_map: Annotated[
Dict[BlockTypes, Type[Block]],
"A mapping to override the default block classes for specific block types.",
"The keys are `BlockTypes` enum values, representing the types of blocks,",
"and the values are corresponding `Block` class implementations to use",
"instead of the defaults.",
] = defaultdict()
use_llm: Annotated[
bool,
"Enable higher quality processing with LLMs.",
] = False
default_processors: Tuple[BaseProcessor, ...] = (
OrderProcessor,
BlockRelabelProcessor,
LineMergeProcessor,
ListItemLineExplodeProcessor, # swap to ListItemGapClusterProcessor for gap-clustered itemization
BlockquoteProcessor,
CodeProcessor,
DocumentTOCProcessor,
EquationProcessor,
FootnoteProcessor,
IgnoreTextProcessor,
LineNumbersProcessor,
ListProcessor,
PageHeaderProcessor,
SectionHeaderProcessor,
TableProcessor,
LLMTableProcessor,
LLMTableMergeProcessor,
LLMFormProcessor,
TextProcessor,
LLMComplexRegionProcessor,
LLMImageDescriptionProcessor,
LLMEquationProcessor,
LLMHandwritingProcessor,
LLMMathBlockProcessor,
LLMSectionHeaderProcessor,
LLMPageCorrectionProcessor,
ReferenceProcessor,
BlankPageProcessor,
DebugProcessor,
)
default_llm_service: BaseService = GoogleGeminiService
def __init__(
self,
artifact_dict: Dict[str, Any],
processor_list: Optional[List[str]] = None,
renderer: str | None = None,
llm_service: str | None = None,
config=None,
):
super().__init__(config)
if config is None:
config = {}
for block_type, override_block_type in self.override_map.items():
register_block_class(block_type, override_block_type)
if processor_list is not None:
processor_list = strings_to_classes(processor_list)
else:
processor_list = self.default_processors
if renderer:
renderer = strings_to_classes([renderer])[0]
else:
renderer = MarkdownRenderer
# Put here so that resolve_dependencies can access it
self.artifact_dict = artifact_dict
if llm_service:
llm_service_cls = strings_to_classes([llm_service])[0]
llm_service = self.resolve_dependencies(llm_service_cls)
elif config.get("use_llm", False):
llm_service = self.resolve_dependencies(self.default_llm_service)
# Inject llm service into artifact_dict so it can be picked up by processors, etc.
self.artifact_dict["llm_service"] = llm_service
self.llm_service = llm_service
self.renderer = renderer
processor_list = self.initialize_processors(processor_list)
self.processor_list = processor_list
self.layout_builder_class = LayoutBuilder
self.page_count = None # Track how many pages were converted
@contextmanager
def filepath_to_str(self, file_input: Union[str, io.BytesIO]):
temp_file = None
try:
if isinstance(file_input, str):
yield file_input
else:
with tempfile.NamedTemporaryFile(
delete=False, suffix=".pdf"
) as temp_file:
if isinstance(file_input, io.BytesIO):
file_input.seek(0)
temp_file.write(file_input.getvalue())
else:
raise TypeError(
f"Expected str or BytesIO, got {type(file_input)}"
)
yield temp_file.name
finally:
if temp_file is not None and os.path.exists(temp_file.name):
os.unlink(temp_file.name)
def build_document(self, filepath: str) -> Document:
provider_cls = provider_from_filepath(filepath)
layout_builder = self.resolve_dependencies(self.layout_builder_class)
line_builder = self.resolve_dependencies(LineBuilder)
ocr_builder = self.resolve_dependencies(OcrBuilder)
provider = provider_cls(filepath, self.config)
document = DocumentBuilder(self.config)(
provider, layout_builder, line_builder, ocr_builder
)
structure_builder_cls = self.resolve_dependencies(StructureBuilder)
structure_builder_cls(document)
for processor in self.processor_list:
processor(document)
return document
def __call__(self, filepath: str | io.BytesIO):
with self.filepath_to_str(filepath) as temp_path:
document = self.build_document(temp_path)
self.page_count = len(document.pages)
renderer = self.resolve_dependencies(self.renderer)
rendered = renderer(document)
return rendered