BabelDOC 0.1.6__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.
- babeldoc/__init__.py +1 -0
- babeldoc/asynchronize/__init__.py +51 -0
- babeldoc/const.py +7 -0
- babeldoc/converter.py +510 -0
- babeldoc/document_il/__init__.py +43 -0
- babeldoc/document_il/backend/__init__.py +0 -0
- babeldoc/document_il/backend/pdf_creater.py +402 -0
- babeldoc/document_il/frontend/__init__.py +0 -0
- babeldoc/document_il/frontend/il_creater.py +348 -0
- babeldoc/document_il/il_version_1.py +799 -0
- babeldoc/document_il/il_version_1.rnc +141 -0
- babeldoc/document_il/il_version_1.rng +390 -0
- babeldoc/document_il/il_version_1.xsd +235 -0
- babeldoc/document_il/midend/__init__.py +0 -0
- babeldoc/document_il/midend/add_debug_information.py +138 -0
- babeldoc/document_il/midend/il_translator.py +553 -0
- babeldoc/document_il/midend/layout_parser.py +180 -0
- babeldoc/document_il/midend/paragraph_finder.py +452 -0
- babeldoc/document_il/midend/remove_descent.py +168 -0
- babeldoc/document_il/midend/styles_and_formulas.py +701 -0
- babeldoc/document_il/midend/typesetting.py +761 -0
- babeldoc/document_il/translator/__init__.py +0 -0
- babeldoc/document_il/translator/cache.py +145 -0
- babeldoc/document_il/translator/translator.py +267 -0
- babeldoc/document_il/utils/fontmap.py +227 -0
- babeldoc/document_il/utils/layout_helper.py +467 -0
- babeldoc/document_il/utils/style_helper.py +86 -0
- babeldoc/document_il/xml_converter.py +51 -0
- babeldoc/docvision/README.md +0 -0
- babeldoc/docvision/__init__.py +0 -0
- babeldoc/docvision/doclayout.py +278 -0
- babeldoc/docvision/rpc_doclayout.py +162 -0
- babeldoc/high_level.py +468 -0
- babeldoc/main.py +466 -0
- babeldoc/pdfinterp.py +464 -0
- babeldoc/progress_monitor.py +146 -0
- babeldoc/translation_config.py +156 -0
- babeldoc-0.1.6.dist-info/METADATA +283 -0
- babeldoc-0.1.6.dist-info/RECORD +42 -0
- babeldoc-0.1.6.dist-info/WHEEL +4 -0
- babeldoc-0.1.6.dist-info/entry_points.txt +2 -0
- babeldoc-0.1.6.dist-info/licenses/LICENSE +661 -0
babeldoc/high_level.py
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import hashlib
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from asyncio import CancelledError
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
from typing import BinaryIO
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from pdfminer.pdfdocument import PDFDocument
|
|
13
|
+
from pdfminer.pdfinterp import PDFResourceManager
|
|
14
|
+
from pdfminer.pdfpage import PDFPage
|
|
15
|
+
from pdfminer.pdfparser import PDFParser
|
|
16
|
+
from pymupdf import Document
|
|
17
|
+
from pymupdf import Font
|
|
18
|
+
|
|
19
|
+
from babeldoc import asynchronize
|
|
20
|
+
from babeldoc.const import CACHE_FOLDER
|
|
21
|
+
from babeldoc.const import get_cache_file_path
|
|
22
|
+
from babeldoc.converter import TranslateConverter
|
|
23
|
+
from babeldoc.document_il.backend.pdf_creater import PDFCreater
|
|
24
|
+
from babeldoc.document_il.frontend.il_creater import ILCreater
|
|
25
|
+
from babeldoc.document_il.midend.add_debug_information import AddDebugInformation
|
|
26
|
+
from babeldoc.document_il.midend.il_translator import ILTranslator
|
|
27
|
+
from babeldoc.document_il.midend.layout_parser import LayoutParser
|
|
28
|
+
from babeldoc.document_il.midend.paragraph_finder import ParagraphFinder
|
|
29
|
+
from babeldoc.document_il.midend.remove_descent import RemoveDescent
|
|
30
|
+
from babeldoc.document_il.midend.styles_and_formulas import StylesAndFormulas
|
|
31
|
+
from babeldoc.document_il.midend.typesetting import Typesetting
|
|
32
|
+
from babeldoc.document_il.utils.fontmap import FontMapper
|
|
33
|
+
from babeldoc.document_il.xml_converter import XMLConverter
|
|
34
|
+
from babeldoc.pdfinterp import PDFPageInterpreterEx
|
|
35
|
+
from babeldoc.progress_monitor import ProgressMonitor
|
|
36
|
+
from babeldoc.translation_config import TranslateResult
|
|
37
|
+
from babeldoc.translation_config import TranslationConfig
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
TRANSLATE_STAGES = [
|
|
42
|
+
ILCreater.stage_name,
|
|
43
|
+
LayoutParser.stage_name,
|
|
44
|
+
ParagraphFinder.stage_name,
|
|
45
|
+
StylesAndFormulas.stage_name,
|
|
46
|
+
RemoveDescent.stage_name,
|
|
47
|
+
ILTranslator.stage_name,
|
|
48
|
+
Typesetting.stage_name,
|
|
49
|
+
FontMapper.stage_name,
|
|
50
|
+
PDFCreater.stage_name,
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
resfont_map = {
|
|
54
|
+
"zh-cn": "china-ss",
|
|
55
|
+
"zh-tw": "china-ts",
|
|
56
|
+
"zh-hans": "china-ss",
|
|
57
|
+
"zh-hant": "china-ts",
|
|
58
|
+
"zh": "china-ss",
|
|
59
|
+
"ja": "japan-s",
|
|
60
|
+
"ko": "korea-s",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
FONT_ASSETS = [
|
|
64
|
+
(
|
|
65
|
+
"noto.ttf",
|
|
66
|
+
"https://github.com/satbyy/go-noto-universal"
|
|
67
|
+
"/releases/download/v7.0/GoNotoKurrent-Regular.ttf",
|
|
68
|
+
"2f2cee5fbb2403df352ca2005247f6c4faa70f3086ebd31b6c62308b5f2f9865",
|
|
69
|
+
),
|
|
70
|
+
(
|
|
71
|
+
"source-han-serif-cn.ttf",
|
|
72
|
+
"https://github.com/junmer/source-han-serif-ttf/"
|
|
73
|
+
"raw/refs/heads/master/SubsetTTF/CN/SourceHanSerifCN-Regular.ttf",
|
|
74
|
+
"1e60cc2eedfa25bf5e4ecaa794402f581ad770d4c8be46d338bf52064b307ec7",
|
|
75
|
+
),
|
|
76
|
+
(
|
|
77
|
+
"source-han-serif-cn-bold.ttf",
|
|
78
|
+
"https://github.com/junmer/source-han-serif-ttf/"
|
|
79
|
+
"raw/refs/heads/master/SubsetTTF/CN/SourceHanSerifCN-Bold.ttf",
|
|
80
|
+
"84c24723a47537fcf5057b788a51c41978ee6173931f19b8a9f5a4595b677dc9",
|
|
81
|
+
),
|
|
82
|
+
(
|
|
83
|
+
"SourceHanSansSC-Regular.ttf",
|
|
84
|
+
"https://github.com/iizyd/SourceHanSansCN-TTF-Min/"
|
|
85
|
+
"raw/refs/heads/main/source-file/ttf/SourceHanSansSC-Regular.ttf",
|
|
86
|
+
"a878f16eed162dc5b211d888a4a29b1730b73f4cf632e720abca4eab7bd8a152",
|
|
87
|
+
),
|
|
88
|
+
(
|
|
89
|
+
"SourceHanSansSC-Bold.ttf",
|
|
90
|
+
"https://github.com/iizyd/SourceHanSansCN-TTF-Min/"
|
|
91
|
+
"raw/refs/heads/main/source-file/ttf/SourceHanSansSC-Bold.ttf",
|
|
92
|
+
"485b27eb4f3603223e9c3c5ebfa317aee77772ea8f642f9330df7f030c8b7b43",
|
|
93
|
+
),
|
|
94
|
+
(
|
|
95
|
+
"LXGWWenKai-Regular.ttf",
|
|
96
|
+
"https://github.com/lxgw/LxgwWenKai/"
|
|
97
|
+
"raw/refs/heads/main/fonts/TTF/LXGWWenKai-Regular.ttf",
|
|
98
|
+
"ea47ec17d0f3d0ed1e6d9c51d6146402d4d1e2f0ff397a90765aaaa0ddd382fb",
|
|
99
|
+
),
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def verify_file_hash(file_path: str, expected_hash: str) -> bool:
|
|
104
|
+
"""Verify the SHA256 hash of a file."""
|
|
105
|
+
sha256_hash = hashlib.sha256()
|
|
106
|
+
with Path(file_path).open("rb") as f:
|
|
107
|
+
# Read the file in chunks to handle large files efficiently
|
|
108
|
+
for byte_block in iter(lambda: f.read(4096), b""):
|
|
109
|
+
sha256_hash.update(byte_block)
|
|
110
|
+
return sha256_hash.hexdigest() == expected_hash
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def start_parse_il(
|
|
114
|
+
inf: BinaryIO,
|
|
115
|
+
pages: list[int] | None = None,
|
|
116
|
+
vfont: str = "",
|
|
117
|
+
vchar: str = "",
|
|
118
|
+
thread: int = 0,
|
|
119
|
+
doc_zh: Document = None,
|
|
120
|
+
lang_in: str = "",
|
|
121
|
+
lang_out: str = "",
|
|
122
|
+
service: str = "",
|
|
123
|
+
resfont: str = "",
|
|
124
|
+
noto: Font = None,
|
|
125
|
+
cancellation_event: asyncio.Event = None,
|
|
126
|
+
il_creater: ILCreater = None,
|
|
127
|
+
translation_config: TranslationConfig = None,
|
|
128
|
+
**kwarg: Any,
|
|
129
|
+
) -> None:
|
|
130
|
+
rsrcmgr = PDFResourceManager()
|
|
131
|
+
layout = {}
|
|
132
|
+
device = TranslateConverter(
|
|
133
|
+
rsrcmgr,
|
|
134
|
+
vfont,
|
|
135
|
+
vchar,
|
|
136
|
+
thread,
|
|
137
|
+
layout,
|
|
138
|
+
lang_in,
|
|
139
|
+
lang_out,
|
|
140
|
+
service,
|
|
141
|
+
resfont,
|
|
142
|
+
noto,
|
|
143
|
+
kwarg.get("envs", {}),
|
|
144
|
+
kwarg.get("prompt", []),
|
|
145
|
+
il_creater=il_creater,
|
|
146
|
+
)
|
|
147
|
+
# model = DocLayoutModel.load_available()
|
|
148
|
+
|
|
149
|
+
assert device is not None
|
|
150
|
+
assert il_creater is not None
|
|
151
|
+
assert translation_config is not None
|
|
152
|
+
obj_patch = {}
|
|
153
|
+
interpreter = PDFPageInterpreterEx(rsrcmgr, device, obj_patch, il_creater)
|
|
154
|
+
if pages:
|
|
155
|
+
total_pages = len(pages)
|
|
156
|
+
else:
|
|
157
|
+
total_pages = doc_zh.page_count
|
|
158
|
+
|
|
159
|
+
il_creater.on_total_pages(total_pages)
|
|
160
|
+
|
|
161
|
+
parser = PDFParser(inf)
|
|
162
|
+
doc = PDFDocument(parser)
|
|
163
|
+
|
|
164
|
+
for pageno, page in enumerate(PDFPage.create_pages(doc)):
|
|
165
|
+
if cancellation_event and cancellation_event.is_set():
|
|
166
|
+
raise CancelledError("task cancelled")
|
|
167
|
+
if pages and (pageno not in pages):
|
|
168
|
+
continue
|
|
169
|
+
page.pageno = pageno
|
|
170
|
+
if not translation_config.should_translate_page(pageno + 1):
|
|
171
|
+
continue
|
|
172
|
+
translation_config.raise_if_cancelled()
|
|
173
|
+
# The current program no longer relies on
|
|
174
|
+
# the following layout recognition results,
|
|
175
|
+
# but in order to facilitate the migration of pdf2zh,
|
|
176
|
+
# the relevant code is temporarily retained.
|
|
177
|
+
# pix = doc_zh[page.pageno].get_pixmap()
|
|
178
|
+
# image = np.fromstring(pix.samples, np.uint8).reshape(
|
|
179
|
+
# pix.height, pix.width, 3
|
|
180
|
+
# )[:, :, ::-1]
|
|
181
|
+
# page_layout = model.predict(
|
|
182
|
+
# image, imgsz=int(pix.height / 32) * 32)[0]
|
|
183
|
+
# # kdtree 是不可能 kdtree 的,不如直接渲染成图片,用空间换时间
|
|
184
|
+
# box = np.ones((pix.height, pix.width))
|
|
185
|
+
# h, w = box.shape
|
|
186
|
+
# vcls = ["abandon", "figure", "table",
|
|
187
|
+
# "isolate_formula", "formula_caption"]
|
|
188
|
+
# for i, d in enumerate(page_layout.boxes):
|
|
189
|
+
# if page_layout.names[int(d.cls)] not in vcls:
|
|
190
|
+
# x0, y0, x1, y1 = d.xyxy.squeeze()
|
|
191
|
+
# x0, y0, x1, y1 = (
|
|
192
|
+
# np.clip(int(x0 - 1), 0, w - 1),
|
|
193
|
+
# np.clip(int(h - y1 - 1), 0, h - 1),
|
|
194
|
+
# np.clip(int(x1 + 1), 0, w - 1),
|
|
195
|
+
# np.clip(int(h - y0 + 1), 0, h - 1),
|
|
196
|
+
# )
|
|
197
|
+
# box[y0:y1, x0:x1] = i + 2
|
|
198
|
+
# for i, d in enumerate(page_layout.boxes):
|
|
199
|
+
# if page_layout.names[int(d.cls)] in vcls:
|
|
200
|
+
# x0, y0, x1, y1 = d.xyxy.squeeze()
|
|
201
|
+
# x0, y0, x1, y1 = (
|
|
202
|
+
# np.clip(int(x0 - 1), 0, w - 1),
|
|
203
|
+
# np.clip(int(h - y1 - 1), 0, h - 1),
|
|
204
|
+
# np.clip(int(x1 + 1), 0, w - 1),
|
|
205
|
+
# np.clip(int(h - y0 + 1), 0, h - 1),
|
|
206
|
+
# )
|
|
207
|
+
# box[y0:y1, x0:x1] = 0
|
|
208
|
+
# layout[page.pageno] = box
|
|
209
|
+
# 新建一个 xref 存放新指令流
|
|
210
|
+
page.page_xref = doc_zh.get_new_xref() # hack 插入页面的新 xref
|
|
211
|
+
doc_zh.update_object(page.page_xref, "<<>>")
|
|
212
|
+
doc_zh.update_stream(page.page_xref, b"")
|
|
213
|
+
doc_zh[page.pageno].set_contents(page.page_xref)
|
|
214
|
+
ops_base = interpreter.process_page(page)
|
|
215
|
+
il_creater.on_page_base_operation(ops_base)
|
|
216
|
+
il_creater.on_page_end()
|
|
217
|
+
il_creater.on_finish()
|
|
218
|
+
device.close()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def translate(translation_config: TranslationConfig) -> TranslateResult:
|
|
222
|
+
with ProgressMonitor(
|
|
223
|
+
translation_config,
|
|
224
|
+
TRANSLATE_STAGES,
|
|
225
|
+
) as pm:
|
|
226
|
+
return do_translate(pm, translation_config)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
async def async_translate(translation_config: TranslationConfig):
|
|
230
|
+
"""Asynchronously translate a PDF file with real-time progress reporting.
|
|
231
|
+
|
|
232
|
+
This function yields progress events that can be used to update progress bars
|
|
233
|
+
or other UI elements. The events are dictionaries with the following structure:
|
|
234
|
+
|
|
235
|
+
- progress_start: {
|
|
236
|
+
"type": "progress_start",
|
|
237
|
+
"stage": str, # Stage name
|
|
238
|
+
"stage_progress": float, # Always 0.0
|
|
239
|
+
"stage_current": int, # Current count (0)
|
|
240
|
+
"stage_total": int # Total items in stage
|
|
241
|
+
}
|
|
242
|
+
- progress_update: {
|
|
243
|
+
"type": "progress_update",
|
|
244
|
+
"stage": str, # Stage name
|
|
245
|
+
"stage_progress": float, # Stage progress (0-100)
|
|
246
|
+
"stage_current": int, # Current items processed
|
|
247
|
+
"stage_total": int, # Total items in stage
|
|
248
|
+
"overall_progress": float # Overall progress (0-100)
|
|
249
|
+
}
|
|
250
|
+
- progress_end: {
|
|
251
|
+
"type": "progress_end",
|
|
252
|
+
"stage": str, # Stage name
|
|
253
|
+
"stage_progress": float, # Always 100.0
|
|
254
|
+
"stage_current": int, # Equal to stage_total
|
|
255
|
+
"stage_total": int, # Total items processed
|
|
256
|
+
"overall_progress": float # Overall progress (0-100)
|
|
257
|
+
}
|
|
258
|
+
- finish: {
|
|
259
|
+
"type": "finish",
|
|
260
|
+
"translate_result": TranslateResult
|
|
261
|
+
}
|
|
262
|
+
- error: {
|
|
263
|
+
"type": "error",
|
|
264
|
+
"error": str
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
translation_config: Configuration for the translation process
|
|
269
|
+
|
|
270
|
+
Yields:
|
|
271
|
+
dict: Progress events during translation
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
CancelledError: If the translation is cancelled
|
|
275
|
+
Exception: Any other errors during translation
|
|
276
|
+
"""
|
|
277
|
+
loop = asyncio.get_running_loop()
|
|
278
|
+
callback = asynchronize.AsyncCallback()
|
|
279
|
+
|
|
280
|
+
finish_event = asyncio.Event()
|
|
281
|
+
cancel_event = threading.Event()
|
|
282
|
+
with ProgressMonitor(
|
|
283
|
+
translation_config,
|
|
284
|
+
TRANSLATE_STAGES,
|
|
285
|
+
progress_change_callback=callback.step_callback,
|
|
286
|
+
finish_callback=callback.finished_callback,
|
|
287
|
+
finish_event=finish_event,
|
|
288
|
+
cancel_event=cancel_event,
|
|
289
|
+
loop=loop,
|
|
290
|
+
) as pm:
|
|
291
|
+
future = loop.run_in_executor(None, do_translate, pm, translation_config)
|
|
292
|
+
try:
|
|
293
|
+
async for event in callback:
|
|
294
|
+
event = event.kwargs
|
|
295
|
+
yield event
|
|
296
|
+
if event["type"] == "error":
|
|
297
|
+
break
|
|
298
|
+
except CancelledError:
|
|
299
|
+
cancel_event.set()
|
|
300
|
+
except KeyboardInterrupt:
|
|
301
|
+
logger.info("Translation cancelled by user through keyboard interrupt")
|
|
302
|
+
cancel_event.set()
|
|
303
|
+
if cancel_event.is_set():
|
|
304
|
+
future.cancel()
|
|
305
|
+
logger.info("Waiting for translation to finish...")
|
|
306
|
+
await finish_event.wait()
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def do_translate(pm, translation_config):
|
|
310
|
+
try:
|
|
311
|
+
translation_config.progress_monitor = pm
|
|
312
|
+
original_pdf_path = translation_config.input_file
|
|
313
|
+
logger.info(f"start to translate: {original_pdf_path}")
|
|
314
|
+
start_time = time.time()
|
|
315
|
+
doc_input = Document(original_pdf_path)
|
|
316
|
+
if translation_config.debug:
|
|
317
|
+
logger.debug("debug mode, save decompressed input pdf")
|
|
318
|
+
output_path = translation_config.get_working_file_path(
|
|
319
|
+
"input.decompressed.pdf",
|
|
320
|
+
)
|
|
321
|
+
doc_input.save(output_path, expand=True, pretty=True)
|
|
322
|
+
# Continue with original processing
|
|
323
|
+
temp_pdf_path = translation_config.get_working_file_path("input.pdf")
|
|
324
|
+
doc_pdf2zh = Document(original_pdf_path)
|
|
325
|
+
resfont = "china-ss"
|
|
326
|
+
for page in doc_pdf2zh:
|
|
327
|
+
page.insert_font(resfont, None)
|
|
328
|
+
doc_pdf2zh.save(temp_pdf_path)
|
|
329
|
+
il_creater = ILCreater(translation_config)
|
|
330
|
+
il_creater.mupdf = doc_input
|
|
331
|
+
xml_converter = XMLConverter()
|
|
332
|
+
logger.debug(f"start parse il from {temp_pdf_path}")
|
|
333
|
+
with Path(temp_pdf_path).open("rb") as f:
|
|
334
|
+
start_parse_il(
|
|
335
|
+
f,
|
|
336
|
+
doc_zh=doc_pdf2zh,
|
|
337
|
+
resfont=resfont,
|
|
338
|
+
il_creater=il_creater,
|
|
339
|
+
translation_config=translation_config,
|
|
340
|
+
)
|
|
341
|
+
logger.debug(f"finish parse il from {temp_pdf_path}")
|
|
342
|
+
docs = il_creater.create_il()
|
|
343
|
+
logger.debug(f"finish create il from {temp_pdf_path}")
|
|
344
|
+
if translation_config.debug:
|
|
345
|
+
xml_converter.write_json(
|
|
346
|
+
docs,
|
|
347
|
+
translation_config.get_working_file_path("create_il.debug.json"),
|
|
348
|
+
)
|
|
349
|
+
# Generate layouts for all pages
|
|
350
|
+
logger.debug("start generating layouts")
|
|
351
|
+
docs = LayoutParser(translation_config).process(docs, doc_input)
|
|
352
|
+
logger.debug("finish generating layouts")
|
|
353
|
+
if translation_config.debug:
|
|
354
|
+
xml_converter.write_json(
|
|
355
|
+
docs,
|
|
356
|
+
translation_config.get_working_file_path("layout_generator.json"),
|
|
357
|
+
)
|
|
358
|
+
ParagraphFinder(translation_config).process(docs)
|
|
359
|
+
logger.debug(f"finish paragraph finder from {temp_pdf_path}")
|
|
360
|
+
if translation_config.debug:
|
|
361
|
+
xml_converter.write_json(
|
|
362
|
+
docs,
|
|
363
|
+
translation_config.get_working_file_path("paragraph_finder.json"),
|
|
364
|
+
)
|
|
365
|
+
StylesAndFormulas(translation_config).process(docs)
|
|
366
|
+
logger.debug(f"finish styles and formulas from {temp_pdf_path}")
|
|
367
|
+
if translation_config.debug:
|
|
368
|
+
xml_converter.write_json(
|
|
369
|
+
docs,
|
|
370
|
+
translation_config.get_working_file_path("styles_and_formulas.json"),
|
|
371
|
+
)
|
|
372
|
+
RemoveDescent(translation_config).process(docs)
|
|
373
|
+
logger.debug(f"finish remove descent from {temp_pdf_path}")
|
|
374
|
+
if translation_config.debug:
|
|
375
|
+
xml_converter.write_json(
|
|
376
|
+
docs,
|
|
377
|
+
translation_config.get_working_file_path("remove_descent.json"),
|
|
378
|
+
)
|
|
379
|
+
translate_engine = translation_config.translator
|
|
380
|
+
ILTranslator(translate_engine, translation_config).translate(docs)
|
|
381
|
+
logger.debug(f"finish ILTranslator from {temp_pdf_path}")
|
|
382
|
+
if translation_config.debug:
|
|
383
|
+
xml_converter.write_json(
|
|
384
|
+
docs,
|
|
385
|
+
translation_config.get_working_file_path("il_translated.json"),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
if translation_config.debug:
|
|
389
|
+
AddDebugInformation(translation_config).process(docs)
|
|
390
|
+
xml_converter.write_json(
|
|
391
|
+
docs,
|
|
392
|
+
translation_config.get_working_file_path("add_debug_information.json"),
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
Typesetting(translation_config).typsetting_document(docs)
|
|
396
|
+
logger.debug(f"finish typsetting from {temp_pdf_path}")
|
|
397
|
+
if translation_config.debug:
|
|
398
|
+
xml_converter.write_json(
|
|
399
|
+
docs,
|
|
400
|
+
translation_config.get_working_file_path("typsetting.json"),
|
|
401
|
+
)
|
|
402
|
+
# deepcopy
|
|
403
|
+
# docs2 = xml_converter.deepcopy(docs)
|
|
404
|
+
pdf_creater = PDFCreater(temp_pdf_path, docs, translation_config)
|
|
405
|
+
result = pdf_creater.write(translation_config)
|
|
406
|
+
finish_time = time.time()
|
|
407
|
+
result.original_pdf_path = original_pdf_path
|
|
408
|
+
result.total_seconds = finish_time - start_time
|
|
409
|
+
logger.info(
|
|
410
|
+
f"finish translate: {original_pdf_path}, cost: {finish_time - start_time} s",
|
|
411
|
+
)
|
|
412
|
+
pm.translate_done(result)
|
|
413
|
+
return result
|
|
414
|
+
except Exception as e:
|
|
415
|
+
logger.exception(f"translate error: {e}")
|
|
416
|
+
pm.translate_error(e)
|
|
417
|
+
raise
|
|
418
|
+
finally:
|
|
419
|
+
logger.debug("do_translate finally")
|
|
420
|
+
pm.on_finish()
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def download_font_assets():
|
|
424
|
+
"""Download and verify font assets."""
|
|
425
|
+
for name, url, expected_hash in FONT_ASSETS:
|
|
426
|
+
save_path = get_cache_file_path(name)
|
|
427
|
+
|
|
428
|
+
# Check if file exists and has correct hash
|
|
429
|
+
if Path(save_path).exists():
|
|
430
|
+
if verify_file_hash(save_path, expected_hash):
|
|
431
|
+
continue
|
|
432
|
+
else:
|
|
433
|
+
logger.warning(f"Hash mismatch for {name}, re-downloading...")
|
|
434
|
+
Path(save_path).unlink()
|
|
435
|
+
|
|
436
|
+
# Download file
|
|
437
|
+
r = httpx.get(url, follow_redirects=True)
|
|
438
|
+
if not r.is_success:
|
|
439
|
+
logger.critical("cannot download %s font", name, exc_info=True)
|
|
440
|
+
exit(1)
|
|
441
|
+
|
|
442
|
+
# Save and verify
|
|
443
|
+
with Path(save_path).open("wb") as f:
|
|
444
|
+
f.write(r.content)
|
|
445
|
+
|
|
446
|
+
if not verify_file_hash(save_path, expected_hash):
|
|
447
|
+
logger.critical(f"Downloaded file {name} has incorrect hash!")
|
|
448
|
+
Path(save_path).unlink()
|
|
449
|
+
exit(1)
|
|
450
|
+
|
|
451
|
+
logger.info(f"Successfully downloaded and verified {name}")
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def create_cache_folder():
|
|
455
|
+
try:
|
|
456
|
+
logger.debug(f"create cache folder at {CACHE_FOLDER}")
|
|
457
|
+
Path(CACHE_FOLDER).mkdir(parents=True, exist_ok=True)
|
|
458
|
+
except OSError:
|
|
459
|
+
logger.critical(
|
|
460
|
+
f"Failed to create cache folder at {CACHE_FOLDER}",
|
|
461
|
+
exc_info=True,
|
|
462
|
+
)
|
|
463
|
+
exit(1)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def init():
|
|
467
|
+
create_cache_folder()
|
|
468
|
+
download_font_assets()
|