BabelDOC 0.2.20__tar.gz → 0.2.22__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.
- {babeldoc-0.2.20 → babeldoc-0.2.22}/PKG-INFO +2 -1
- {babeldoc-0.2.20 → babeldoc-0.2.22}/README.md +1 -0
- babeldoc-0.2.22/babeldoc/__init__.py +1 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/const.py +1 -1
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/il_translator.py +11 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/il_translator_llm_only.py +35 -15
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/translator/translator.py +1 -25
- babeldoc-0.2.22/babeldoc/document_il/utils/atomic_integer.py +26 -0
- babeldoc-0.2.22/babeldoc/document_il/utils/priority_thread_pool_executor.py +259 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/main.py +1 -1
- {babeldoc-0.2.20 → babeldoc-0.2.22}/pyproject.toml +2 -2
- {babeldoc-0.2.20 → babeldoc-0.2.22}/uv.lock +1 -1
- babeldoc-0.2.20/babeldoc/__init__.py +0 -1
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.cursorignore +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/dependabot.yml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/labels.yml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/release-drafter.yml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/codeql.yml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/docs.yml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/labeler.yml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/publish-to-pypi.yml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/test.yml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.gitignore +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/.pre-commit-config.yaml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/LICENSE +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/assets/assets.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/assets/embedding_assets_metadata.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/asynchronize/__init__.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/converter.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/__init__.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/backend/__init__.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/backend/pdf_creater.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/frontend/__init__.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/frontend/il_creater.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/il_version_1.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/il_version_1.rnc +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/il_version_1.rng +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/il_version_1.xsd +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/__init__.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/add_debug_information.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/detect_scanned_file.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/layout_parser.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/paragraph_finder.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/remove_descent.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/styles_and_formulas.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/table_parser.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/typesetting.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/translator/__init__.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/translator/cache.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/utils/fontmap.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/utils/layout_helper.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/utils/style_helper.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/xml_converter.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/README.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/__init__.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/doclayout.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/rpc_doclayout.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/table_detection/rapidocr.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/high_level.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/pdfinterp.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/progress_monitor.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/result_merger.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/split_manager.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/tools/generate_font_metadata.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/translation_config.py +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/CODE_OF_CONDUCT.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/CONTRIBUTING.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/CONTRIBUTOR_REWARD.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/README.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/README.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/deploy.sh +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-banner.png +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo.png +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-contributor_reward_example.png +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-preview.gif +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-small-logo.png +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/index.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/intro-to-pdf-object.md +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/requirements.txt +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/mkdocs.yml +0 -0
- {babeldoc-0.2.20 → babeldoc-0.2.22}/tests/test_translation_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: BabelDOC
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.22
|
|
4
4
|
Summary: Yet Another Document Translator
|
|
5
5
|
Project-URL: Homepage, https://github.com/funstory-ai/BabelDOC
|
|
6
6
|
Project-URL: Issues, https://github.com/funstory-ai/BabelDOC/issues
|
|
@@ -399,6 +399,7 @@ Everyone interacting in YADT and its sub-projects' codebases, issue trackers, ch
|
|
|
399
399
|
- [pdfminer](https://github.com/pdfminer/pdfminer.six)
|
|
400
400
|
- [PyMuPDF](https://github.com/pymupdf/PyMuPDF)
|
|
401
401
|
- [Asynchronize](https://github.com/multimeric/Asynchronize/tree/master?tab=readme-ov-file)
|
|
402
|
+
- [PriorityThreadPoolExecutor](https://github.com/oleglpts/PriorityThreadPoolExecutor)
|
|
402
403
|
|
|
403
404
|
<h2 id="star_hist">Star History</h2>
|
|
404
405
|
|
|
@@ -353,6 +353,7 @@ Everyone interacting in YADT and its sub-projects' codebases, issue trackers, ch
|
|
|
353
353
|
- [pdfminer](https://github.com/pdfminer/pdfminer.six)
|
|
354
354
|
- [PyMuPDF](https://github.com/pymupdf/PyMuPDF)
|
|
355
355
|
- [Asynchronize](https://github.com/multimeric/Asynchronize/tree/master?tab=readme-ov-file)
|
|
356
|
+
- [PriorityThreadPoolExecutor](https://github.com/oleglpts/PriorityThreadPoolExecutor)
|
|
356
357
|
|
|
357
358
|
<h2 id="star_hist">Star History</h2>
|
|
358
359
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.22"
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import re
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
import tiktoken
|
|
7
8
|
from tqdm import tqdm
|
|
8
9
|
|
|
9
10
|
from babeldoc.document_il import Document
|
|
@@ -119,11 +120,20 @@ class ILTranslator:
|
|
|
119
120
|
self,
|
|
120
121
|
translate_engine: BaseTranslator,
|
|
121
122
|
translation_config: TranslationConfig,
|
|
123
|
+
tokenizer=None,
|
|
122
124
|
):
|
|
123
125
|
self.translate_engine = translate_engine
|
|
124
126
|
self.translation_config = translation_config
|
|
125
127
|
self.font_mapper = FontMapper(translation_config)
|
|
126
128
|
|
|
129
|
+
if tokenizer is None:
|
|
130
|
+
self.tokenizer = tiktoken.encoding_for_model("gpt-4o")
|
|
131
|
+
else:
|
|
132
|
+
self.tokenizer = tokenizer
|
|
133
|
+
|
|
134
|
+
def calc_token_count(self, text: str) -> int:
|
|
135
|
+
return len(self.tokenizer.encode(text))
|
|
136
|
+
|
|
127
137
|
def translate(self, docs: Document):
|
|
128
138
|
tracker = DocumentTranslateTracker()
|
|
129
139
|
# count total paragraph
|
|
@@ -173,6 +183,7 @@ class ILTranslator:
|
|
|
173
183
|
tracker.new_paragraph(),
|
|
174
184
|
page_font_map,
|
|
175
185
|
page_xobj_font_map,
|
|
186
|
+
priority=1048576 - self.calc_token_count(paragraph.unicode),
|
|
176
187
|
)
|
|
177
188
|
|
|
178
189
|
class TranslateInput:
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import concurrent.futures
|
|
2
1
|
import json
|
|
3
2
|
import logging
|
|
4
3
|
import re
|
|
@@ -17,10 +16,11 @@ from babeldoc.document_il.midend.il_translator import ILTranslator
|
|
|
17
16
|
from babeldoc.document_il.midend.il_translator import PageTranslateTracker
|
|
18
17
|
from babeldoc.document_il.translator.translator import BaseTranslator
|
|
19
18
|
from babeldoc.document_il.utils.fontmap import FontMapper
|
|
19
|
+
from babeldoc.document_il.utils.priority_thread_pool_executor import (
|
|
20
|
+
PriorityThreadPoolExecutor,
|
|
21
|
+
)
|
|
20
22
|
from babeldoc.translation_config import TranslationConfig
|
|
21
23
|
|
|
22
|
-
enc = tiktoken.encoding_for_model("gpt-4o")
|
|
23
|
-
|
|
24
24
|
logger = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
|
|
@@ -39,6 +39,7 @@ class ILTranslatorLLMOnly:
|
|
|
39
39
|
self,
|
|
40
40
|
translate_engine: BaseTranslator,
|
|
41
41
|
translation_config: TranslationConfig,
|
|
42
|
+
tokenizer=None,
|
|
42
43
|
):
|
|
43
44
|
self.translate_engine = translate_engine
|
|
44
45
|
self.translation_config = translation_config
|
|
@@ -47,9 +48,15 @@ class ILTranslatorLLMOnly:
|
|
|
47
48
|
translation_config.shared_context_cross_split_part
|
|
48
49
|
)
|
|
49
50
|
|
|
51
|
+
if tokenizer is None:
|
|
52
|
+
self.tokenizer = tiktoken.encoding_for_model("gpt-4o")
|
|
53
|
+
else:
|
|
54
|
+
self.tokenizer = tokenizer
|
|
55
|
+
|
|
50
56
|
self.il_translator = ILTranslator(
|
|
51
57
|
translate_engine=translate_engine,
|
|
52
58
|
translation_config=translation_config,
|
|
59
|
+
tokenizer=self.tokenizer,
|
|
53
60
|
)
|
|
54
61
|
|
|
55
62
|
try:
|
|
@@ -57,6 +64,9 @@ class ILTranslatorLLMOnly:
|
|
|
57
64
|
except NotImplementedError as e:
|
|
58
65
|
raise ValueError("LLM translator not supported") from e
|
|
59
66
|
|
|
67
|
+
def calc_token_count(self, text: str) -> int:
|
|
68
|
+
return len(self.tokenizer.encode(text))
|
|
69
|
+
|
|
60
70
|
def find_title_paragraph(self, docs: Document) -> PdfParagraph | None:
|
|
61
71
|
"""Find the first paragraph with layout_label 'title' in the document.
|
|
62
72
|
|
|
@@ -103,10 +113,10 @@ class ILTranslatorLLMOnly:
|
|
|
103
113
|
self.stage_name,
|
|
104
114
|
total,
|
|
105
115
|
) as pbar:
|
|
106
|
-
with
|
|
116
|
+
with PriorityThreadPoolExecutor(
|
|
107
117
|
max_workers=self.translation_config.qps,
|
|
108
118
|
) as executor2:
|
|
109
|
-
with
|
|
119
|
+
with PriorityThreadPoolExecutor(
|
|
110
120
|
max_workers=self.translation_config.qps,
|
|
111
121
|
) as executor:
|
|
112
122
|
for page in docs.page:
|
|
@@ -128,10 +138,10 @@ class ILTranslatorLLMOnly:
|
|
|
128
138
|
def process_page(
|
|
129
139
|
self,
|
|
130
140
|
page: Page,
|
|
131
|
-
executor:
|
|
141
|
+
executor: PriorityThreadPoolExecutor,
|
|
132
142
|
pbar: tqdm | None = None,
|
|
133
143
|
tracker: PageTranslateTracker = None,
|
|
134
|
-
executor2:
|
|
144
|
+
executor2: PriorityThreadPoolExecutor | None = None,
|
|
135
145
|
):
|
|
136
146
|
self.translation_config.raise_if_cancelled()
|
|
137
147
|
page_font_map = {}
|
|
@@ -145,17 +155,17 @@ class ILTranslatorLLMOnly:
|
|
|
145
155
|
|
|
146
156
|
paragraphs = []
|
|
147
157
|
|
|
148
|
-
|
|
158
|
+
total_token_count = 0
|
|
149
159
|
for paragraph in page.pdf_paragraph:
|
|
150
160
|
if paragraph.debug_id is None or paragraph.unicode is None:
|
|
151
161
|
continue
|
|
152
162
|
# self.translate_paragraph(paragraph, pbar,tracker.new_paragraph(), page_font_map, page_xobj_font_map)
|
|
153
|
-
|
|
163
|
+
total_token_count += self.calc_token_count(paragraph.unicode)
|
|
154
164
|
paragraphs.append(paragraph)
|
|
155
165
|
if paragraph.layout_label == "title":
|
|
156
166
|
self.shared_context_cross_split_part.recent_title_paragraph = paragraph
|
|
157
167
|
|
|
158
|
-
if
|
|
168
|
+
if total_token_count > 800 or len(paragraphs) > 5:
|
|
159
169
|
executor.submit(
|
|
160
170
|
self.translate_paragraph,
|
|
161
171
|
BatchParagraph(paragraphs, tracker),
|
|
@@ -165,9 +175,10 @@ class ILTranslatorLLMOnly:
|
|
|
165
175
|
self.translation_config.shared_context_cross_split_part.first_paragraph,
|
|
166
176
|
self.translation_config.shared_context_cross_split_part.recent_title_paragraph,
|
|
167
177
|
executor2,
|
|
178
|
+
priority=1048576 - total_token_count,
|
|
168
179
|
)
|
|
169
180
|
paragraphs = []
|
|
170
|
-
|
|
181
|
+
total_token_count = 0
|
|
171
182
|
|
|
172
183
|
if paragraphs:
|
|
173
184
|
executor.submit(
|
|
@@ -179,6 +190,7 @@ class ILTranslatorLLMOnly:
|
|
|
179
190
|
self.translation_config.shared_context_cross_split_part.first_paragraph,
|
|
180
191
|
self.translation_config.shared_context_cross_split_part.recent_title_paragraph,
|
|
181
192
|
executor2,
|
|
193
|
+
priority=1048576 - total_token_count,
|
|
182
194
|
)
|
|
183
195
|
|
|
184
196
|
def translate_paragraph(
|
|
@@ -189,10 +201,11 @@ class ILTranslatorLLMOnly:
|
|
|
189
201
|
xobj_font_map: dict[int, dict[str, PdfFont]] = None,
|
|
190
202
|
title_paragraph: PdfParagraph | None = None,
|
|
191
203
|
local_title_paragraph: PdfParagraph | None = None,
|
|
192
|
-
executor=None,
|
|
204
|
+
executor: PriorityThreadPoolExecutor | None = None,
|
|
193
205
|
):
|
|
194
206
|
"""Translate a paragraph using pre and post processing functions."""
|
|
195
207
|
self.translation_config.raise_if_cancelled()
|
|
208
|
+
should_translate_paragraph = []
|
|
196
209
|
try:
|
|
197
210
|
inputs = []
|
|
198
211
|
|
|
@@ -205,6 +218,7 @@ class ILTranslatorLLMOnly:
|
|
|
205
218
|
if text is None:
|
|
206
219
|
pbar.advance(1)
|
|
207
220
|
continue
|
|
221
|
+
should_translate_paragraph.append(i)
|
|
208
222
|
inputs.append((text, translate_input, paragraph, tracker))
|
|
209
223
|
if not inputs:
|
|
210
224
|
return
|
|
@@ -313,8 +327,8 @@ class ILTranslatorLLMOnly:
|
|
|
313
327
|
input_unicode = inputs[id_][2].unicode
|
|
314
328
|
output_unicode = translated_text
|
|
315
329
|
|
|
316
|
-
input_token_count =
|
|
317
|
-
output_token_count =
|
|
330
|
+
input_token_count = self.calc_token_count(input_unicode)
|
|
331
|
+
output_token_count = self.calc_token_count(output_unicode)
|
|
318
332
|
|
|
319
333
|
if not (0.3 < output_token_count / input_token_count < 3):
|
|
320
334
|
logger.warning(
|
|
@@ -354,11 +368,16 @@ class ILTranslatorLLMOnly:
|
|
|
354
368
|
inputs[id_][3],
|
|
355
369
|
page_font_map,
|
|
356
370
|
xobj_font_map,
|
|
371
|
+
priority=1048576 - self.calc_token_count(paragraph.unicode),
|
|
357
372
|
)
|
|
358
373
|
|
|
359
374
|
except Exception as e:
|
|
360
375
|
logger.warning(f"Error {e} during translation. try fallback")
|
|
361
|
-
|
|
376
|
+
if not should_translate_paragraph:
|
|
377
|
+
should_translate_paragraph = list(
|
|
378
|
+
range(len(batch_paragraph.paragraphs))
|
|
379
|
+
)
|
|
380
|
+
for i in should_translate_paragraph:
|
|
362
381
|
paragraph = batch_paragraph.paragraphs[i]
|
|
363
382
|
tracker = batch_paragraph.trackers[i]
|
|
364
383
|
if paragraph.debug_id is None:
|
|
@@ -370,6 +389,7 @@ class ILTranslatorLLMOnly:
|
|
|
370
389
|
tracker,
|
|
371
390
|
page_font_map,
|
|
372
391
|
xobj_font_map,
|
|
392
|
+
priority=1048576 - self.calc_token_count(paragraph.unicode),
|
|
373
393
|
)
|
|
374
394
|
|
|
375
395
|
def _clean_json_output(self, llm_output: str) -> str:
|
|
@@ -13,6 +13,7 @@ from tenacity import stop_after_attempt
|
|
|
13
13
|
from tenacity import wait_exponential
|
|
14
14
|
|
|
15
15
|
from babeldoc.document_il.translator.cache import TranslationCache
|
|
16
|
+
from babeldoc.document_il.utils.atomic_integer import AtomicInteger
|
|
16
17
|
|
|
17
18
|
logger = logging.getLogger(__name__)
|
|
18
19
|
|
|
@@ -62,31 +63,6 @@ def set_translate_rate_limiter(max_qps):
|
|
|
62
63
|
_translate_rate_limiter.set_max_qps(max_qps)
|
|
63
64
|
|
|
64
65
|
|
|
65
|
-
class AtomicInteger:
|
|
66
|
-
def __init__(self, value=0):
|
|
67
|
-
self._value = int(value)
|
|
68
|
-
self._lock = threading.Lock()
|
|
69
|
-
|
|
70
|
-
def inc(self, d=1):
|
|
71
|
-
with self._lock:
|
|
72
|
-
self._value += int(d)
|
|
73
|
-
return self._value
|
|
74
|
-
|
|
75
|
-
def dec(self, d=1):
|
|
76
|
-
return self.inc(-d)
|
|
77
|
-
|
|
78
|
-
@property
|
|
79
|
-
def value(self):
|
|
80
|
-
with self._lock:
|
|
81
|
-
return self._value
|
|
82
|
-
|
|
83
|
-
@value.setter
|
|
84
|
-
def value(self, v):
|
|
85
|
-
with self._lock:
|
|
86
|
-
self._value = int(v)
|
|
87
|
-
return self._value
|
|
88
|
-
|
|
89
|
-
|
|
90
66
|
class BaseTranslator(ABC):
|
|
91
67
|
# Due to cache limitations, name should be within 20 characters.
|
|
92
68
|
# cache.py: translate_engine = CharField(max_length=20)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AtomicInteger:
|
|
5
|
+
def __init__(self, value=0):
|
|
6
|
+
self._value = int(value)
|
|
7
|
+
self._lock = threading.Lock()
|
|
8
|
+
|
|
9
|
+
def inc(self, d=1):
|
|
10
|
+
with self._lock:
|
|
11
|
+
self._value += int(d)
|
|
12
|
+
return self._value
|
|
13
|
+
|
|
14
|
+
def dec(self, d=1):
|
|
15
|
+
return self.inc(-d)
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def value(self):
|
|
19
|
+
with self._lock:
|
|
20
|
+
return self._value
|
|
21
|
+
|
|
22
|
+
@value.setter
|
|
23
|
+
def value(self, v):
|
|
24
|
+
with self._lock:
|
|
25
|
+
self._value = int(v)
|
|
26
|
+
return self._value
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# thanks to:
|
|
2
|
+
# https://github.com/oleglpts/PriorityThreadPoolExecutor/blob/master/PriorityThreadPoolExecutor/__init__.py
|
|
3
|
+
# https://github.com/oleglpts/PriorityThreadPoolExecutor/issues/4
|
|
4
|
+
|
|
5
|
+
import atexit
|
|
6
|
+
import itertools
|
|
7
|
+
import logging
|
|
8
|
+
import queue
|
|
9
|
+
import random
|
|
10
|
+
import sys
|
|
11
|
+
import threading
|
|
12
|
+
import weakref
|
|
13
|
+
from concurrent.futures import _base
|
|
14
|
+
from concurrent.futures.thread import BrokenThreadPool
|
|
15
|
+
from concurrent.futures.thread import ThreadPoolExecutor
|
|
16
|
+
from concurrent.futures.thread import _python_exit
|
|
17
|
+
from concurrent.futures.thread import _threads_queues
|
|
18
|
+
from concurrent.futures.thread import _WorkItem
|
|
19
|
+
from heapq import heappop
|
|
20
|
+
from heapq import heappush
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
########################################################################################################################
|
|
25
|
+
# Global variables #
|
|
26
|
+
########################################################################################################################
|
|
27
|
+
|
|
28
|
+
NULL_ENTRY = (sys.maxsize, _WorkItem(None, None, (), {}))
|
|
29
|
+
_shutdown = False
|
|
30
|
+
|
|
31
|
+
########################################################################################################################
|
|
32
|
+
# Before system exit procedure #
|
|
33
|
+
########################################################################################################################
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def python_exit():
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
Cleanup before system exit
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
global _shutdown
|
|
43
|
+
_shutdown = True
|
|
44
|
+
items = list(_threads_queues.items())
|
|
45
|
+
for _t, q in items:
|
|
46
|
+
q.put(NULL_ENTRY)
|
|
47
|
+
for t, _q in items:
|
|
48
|
+
t.join()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# change default cleanup
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
atexit.unregister(_python_exit)
|
|
55
|
+
atexit.register(python_exit)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PriorityQueue(queue.Queue):
|
|
59
|
+
"""Variant of Queue that retrieves open entries in priority order (lowest first).
|
|
60
|
+
|
|
61
|
+
Entries are typically tuples of the form: (priority number, data).
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
REMOVED = "<removed-task>"
|
|
65
|
+
DEFAULT_PRIORITY = 100
|
|
66
|
+
|
|
67
|
+
def _init(self, maxsize):
|
|
68
|
+
self.queue = []
|
|
69
|
+
self.entry_finder = {}
|
|
70
|
+
self.counter = itertools.count()
|
|
71
|
+
|
|
72
|
+
def _qsize(self):
|
|
73
|
+
return len(self.queue)
|
|
74
|
+
|
|
75
|
+
def _put(self, item):
|
|
76
|
+
# heappush(self.queue, item)
|
|
77
|
+
try:
|
|
78
|
+
if item[1] in self.entry_finder:
|
|
79
|
+
self.remove(item[1])
|
|
80
|
+
count = next(self.counter)
|
|
81
|
+
entry = [item[0], count, item[1]]
|
|
82
|
+
self.entry_finder[item[1]] = entry
|
|
83
|
+
heappush(self.queue, entry)
|
|
84
|
+
except TypeError: # handle item==None
|
|
85
|
+
self._put((self.DEFAULT_PRIORITY, None))
|
|
86
|
+
|
|
87
|
+
def remove(self, task):
|
|
88
|
+
"""
|
|
89
|
+
This simply replaces the data with the REMOVED value,
|
|
90
|
+
which will get cleared out once _get reaches it.
|
|
91
|
+
"""
|
|
92
|
+
entry = self.entry_finder.pop(task)
|
|
93
|
+
entry[-1] = self.REMOVED
|
|
94
|
+
|
|
95
|
+
def _get(self):
|
|
96
|
+
while self.queue:
|
|
97
|
+
entry = heappop(self.queue)
|
|
98
|
+
if entry[2] is not self.REMOVED:
|
|
99
|
+
del self.entry_finder[entry[2]]
|
|
100
|
+
return entry
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _worker(executor_reference, work_queue, initializer, initargs):
|
|
105
|
+
if initializer is not None:
|
|
106
|
+
try:
|
|
107
|
+
initializer(*initargs)
|
|
108
|
+
except BaseException:
|
|
109
|
+
_base.LOGGER.critical("Exception in initializer:", exc_info=True)
|
|
110
|
+
executor = executor_reference()
|
|
111
|
+
if executor is not None:
|
|
112
|
+
executor._initializer_failed()
|
|
113
|
+
return
|
|
114
|
+
try:
|
|
115
|
+
while True:
|
|
116
|
+
work_item = work_queue.get(block=True)
|
|
117
|
+
try:
|
|
118
|
+
if work_item[2] is not None:
|
|
119
|
+
work_item[2].run()
|
|
120
|
+
# Delete references to object. See issue16284
|
|
121
|
+
del work_item
|
|
122
|
+
|
|
123
|
+
# attempt to increment idle count
|
|
124
|
+
executor = executor_reference()
|
|
125
|
+
if executor is not None:
|
|
126
|
+
executor._idle_semaphore.release()
|
|
127
|
+
del executor
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
executor = executor_reference()
|
|
131
|
+
# Exit if:
|
|
132
|
+
# - The interpreter is shutting down OR
|
|
133
|
+
# - The executor that owns the worker has been collected OR
|
|
134
|
+
# - The executor that owns the worker has been shutdown.
|
|
135
|
+
if _shutdown or executor is None or executor._shutdown:
|
|
136
|
+
# Flag the executor as shutting down as early as possible if it
|
|
137
|
+
# is not gc-ed yet.
|
|
138
|
+
if executor is not None:
|
|
139
|
+
executor._shutdown = True
|
|
140
|
+
# Notice other workers
|
|
141
|
+
work_queue.put(None)
|
|
142
|
+
return
|
|
143
|
+
del executor
|
|
144
|
+
finally:
|
|
145
|
+
work_queue.task_done()
|
|
146
|
+
except BaseException:
|
|
147
|
+
_base.LOGGER.critical("Exception in worker", exc_info=True)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class PriorityThreadPoolExecutor(ThreadPoolExecutor):
|
|
151
|
+
"""
|
|
152
|
+
Thread pool executor with priority queue (priorities must be different, lowest first)
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(self, *args, **kwargs):
|
|
156
|
+
super().__init__(*args, **kwargs)
|
|
157
|
+
|
|
158
|
+
# change work queue type to queue.PriorityQueue
|
|
159
|
+
self._work_queue: PriorityQueue = PriorityQueue()
|
|
160
|
+
|
|
161
|
+
def submit(self, fn, *args, **kwargs):
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
Sending the function to the execution queue
|
|
165
|
+
|
|
166
|
+
:param fn: function being executed
|
|
167
|
+
:type fn: callable
|
|
168
|
+
:param args: function's positional arguments
|
|
169
|
+
:param kwargs: function's keywords arguments
|
|
170
|
+
:return: future instance
|
|
171
|
+
:rtype: _base.Future
|
|
172
|
+
|
|
173
|
+
Added keyword:
|
|
174
|
+
|
|
175
|
+
- priority (integer later sys.maxsize)
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
with self._shutdown_lock:
|
|
179
|
+
if self._broken:
|
|
180
|
+
raise BrokenThreadPool(self._broken)
|
|
181
|
+
|
|
182
|
+
if self._shutdown:
|
|
183
|
+
raise RuntimeError("cannot schedule new futures after shutdown")
|
|
184
|
+
if _shutdown:
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
"cannot schedule new futures after interpreter shutdown"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
priority = kwargs.get("priority", random.randint(0, sys.maxsize - 1)) # noqa: S311
|
|
190
|
+
if "priority" in kwargs:
|
|
191
|
+
del kwargs["priority"]
|
|
192
|
+
|
|
193
|
+
f = _base.Future()
|
|
194
|
+
w = _WorkItem(f, fn, args, kwargs)
|
|
195
|
+
|
|
196
|
+
self._work_queue.put((priority, w))
|
|
197
|
+
self._adjust_thread_count()
|
|
198
|
+
return f
|
|
199
|
+
|
|
200
|
+
def _adjust_thread_count(self):
|
|
201
|
+
# if idle threads are available, don't spin new threads
|
|
202
|
+
if self._idle_semaphore.acquire(timeout=0):
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
# When the executor gets lost, the weakref callback will wake up
|
|
206
|
+
# the worker threads.
|
|
207
|
+
def weakref_cb(_, q=self._work_queue):
|
|
208
|
+
q.put(None)
|
|
209
|
+
|
|
210
|
+
num_threads = len(self._threads)
|
|
211
|
+
if num_threads < self._max_workers:
|
|
212
|
+
thread_name = f"{self._thread_name_prefix or self}_{num_threads:d}"
|
|
213
|
+
t = threading.Thread(
|
|
214
|
+
name=thread_name,
|
|
215
|
+
target=_worker,
|
|
216
|
+
args=(
|
|
217
|
+
weakref.ref(self, weakref_cb),
|
|
218
|
+
self._work_queue,
|
|
219
|
+
self._initializer,
|
|
220
|
+
self._initargs,
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
t.start()
|
|
224
|
+
self._threads.add(t)
|
|
225
|
+
_threads_queues[t] = self._work_queue
|
|
226
|
+
|
|
227
|
+
def shutdown(self, wait=True, *, cancel_futures=False):
|
|
228
|
+
logger.info("Shutting down executor %s", self._thread_name_prefix or self)
|
|
229
|
+
if wait:
|
|
230
|
+
logger.info(
|
|
231
|
+
"Waiting for all tasks done %s", self._thread_name_prefix or self
|
|
232
|
+
)
|
|
233
|
+
self._work_queue.join()
|
|
234
|
+
logger.info("All tasks done %s", self._thread_name_prefix or self)
|
|
235
|
+
|
|
236
|
+
with self._shutdown_lock:
|
|
237
|
+
self._shutdown = True
|
|
238
|
+
if cancel_futures:
|
|
239
|
+
# Drain all work items from the queue, and then cancel their
|
|
240
|
+
# associated futures.
|
|
241
|
+
while True:
|
|
242
|
+
try:
|
|
243
|
+
work_item = self._work_queue.get_nowait()
|
|
244
|
+
except queue.Empty:
|
|
245
|
+
break
|
|
246
|
+
if work_item is not None:
|
|
247
|
+
work_item.future.cancel()
|
|
248
|
+
|
|
249
|
+
# Send a wake-up to prevent threads calling
|
|
250
|
+
# _work_queue.get(block=True) from permanently blocking.
|
|
251
|
+
self._work_queue.put(None)
|
|
252
|
+
if wait:
|
|
253
|
+
logger.info(
|
|
254
|
+
"Waiting for all thread done %s", self._thread_name_prefix or self
|
|
255
|
+
)
|
|
256
|
+
for t in self._threads:
|
|
257
|
+
self._work_queue.put(None)
|
|
258
|
+
t.join()
|
|
259
|
+
logger.info("shutdown finish %s", self._thread_name_prefix or self)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "BabelDOC"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.22"
|
|
4
4
|
description = "Yet Another Document Translator"
|
|
5
5
|
license = "AGPL-3.0"
|
|
6
6
|
readme = "README.md"
|
|
@@ -148,7 +148,7 @@ pythonpath = [".", "src"]
|
|
|
148
148
|
testpaths = ["tests"]
|
|
149
149
|
|
|
150
150
|
[bumpver]
|
|
151
|
-
current_version = "0.2.
|
|
151
|
+
current_version = "0.2.22"
|
|
152
152
|
version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
|
|
153
153
|
|
|
154
154
|
[bumpver.file_patterns]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.20"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-with-transparent-background.png
RENAMED
|
File without changes
|
{babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-with-transparent-background.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-small-logo-with-transparent-background.png
RENAMED
|
File without changes
|
{babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-small-logo-with-transparent-background.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|