leonsimon23 commited on
Commit
1bf0b3b
·
verified ·
1 Parent(s): a53b93a

Update pdf2zh/gui.py

Browse files
Files changed (1) hide show
  1. pdf2zh/gui.py +62 -358
pdf2zh/gui.py CHANGED
@@ -1,26 +1,11 @@
1
  import os
2
  import shutil
3
  from pathlib import Path
4
- from pdf2zh import __version__
5
- from pdf2zh.pdf2zh import extract_text
6
-
7
  import gradio as gr
8
- import numpy as np
9
- import pymupdf
10
  import tqdm
11
- import requests
12
- import cgi
13
 
14
- # Map service names to pdf2zh service options
15
- service_map = {
16
- #"Google": ("google", None, None),
17
- #"DeepL": ("deepl", "DEEPL_AUTH_KEY", None),
18
- #"DeepLX": ("deeplx", "DEEPLX_AUTH_KEY", None),
19
- #"Ollama": ("ollama", None, "gemma2"),
20
- "OpenAI": ("openai", "OPENAI_API_KEY", "gpt-4o"),
21
- #"Azure": ("azure", "AZURE_APIKEY", None),
22
- #"Tencent": ("tencent", "TENCENT_SECRET_KEY", None),
23
- }
24
  lang_map = {
25
  "Chinese": "zh",
26
  "English": "en",
@@ -32,82 +17,31 @@ lang_map = {
32
  "Spanish": "es",
33
  "Italian": "it",
34
  }
 
 
35
  page_map = {
36
- "All": None,
37
- "First": [0],
38
- "First 5 pages": list(range(0, 5)),
39
  }
40
 
41
- flag_demo = False
42
- if os.environ.get("PDF2ZH_DEMO"):
43
- flag_demo = True
44
- service_map = {
45
- "Google": ("google", None, None),
46
- }
47
- page_map = {
48
- "First": [0],
49
- "First 20 pages": list(range(0, 20)),
50
- }
51
- client_key = os.environ.get("PDF2ZH_CLIENT_KEY")
52
- server_key = os.environ.get("PDF2ZH_SERVER_KEY")
53
-
54
-
55
- def verify_recaptcha(response):
56
- recaptcha_url = "https://www.google.com/recaptcha/api/siteverify"
57
-
58
- print("reCAPTCHA", server_key, response)
59
-
60
- data = {"secret": server_key, "response": response}
61
- result = requests.post(recaptcha_url, data=data).json()
62
-
63
- print("reCAPTCHA", result.get("success"))
64
-
65
- return result.get("success")
66
-
67
-
68
- def pdf_preview(file):
69
- doc = pymupdf.open(file)
70
- page = doc[0]
71
- pix = page.get_pixmap()
72
- image = np.frombuffer(pix.samples, np.uint8).reshape(pix.height, pix.width, 3)
73
- return image
74
-
75
-
76
- def upload_file(file, service, progress=gr.Progress()):
77
- """Handle file upload, validation, and initial preview."""
78
- if not file or not os.path.exists(file):
79
- return None, None
80
-
81
- try:
82
- # Convert first page for preview
83
- preview_image = pdf_preview(file)
84
-
85
- return file, preview_image
86
- except Exception as e:
87
- print(f"Error converting PDF: {e}")
88
- return None, None
89
-
90
-
91
- def download_with_limit(url, save_path, size_limit):
92
- chunk_size = 1024
93
- total_size = 0
94
- with requests.get(url, stream=True, timeout=10) as response:
95
- response.raise_for_status()
96
- content = response.headers.get("Content-Disposition")
97
- try:
98
- _, params = cgi.parse_header(content)
99
- filename = params["filename"]
100
- except Exception:
101
- filename = os.path.basename(url)
102
- with open(save_path / filename, "wb") as file:
103
- for chunk in response.iter_content(chunk_size=chunk_size):
104
- total_size += len(chunk)
105
- if size_limit and total_size > size_limit:
106
- raise gr.Error("Exceeds file size limit")
107
- file.write(chunk)
108
- return save_path / filename
109
-
110
 
 
111
  def translate(
112
  file_type,
113
  file_input,
@@ -118,13 +52,8 @@ def translate(
118
  lang_from,
119
  lang_to,
120
  page_range,
121
- recaptcha_response,
122
  progress=gr.Progress(),
123
  ):
124
- """Translate PDF content using selected service."""
125
- if flag_demo and not verify_recaptcha(recaptcha_response):
126
- raise gr.Error("reCAPTCHA fail")
127
-
128
  progress(0, desc="Starting translation...")
129
 
130
  output = Path("pdf2zh_files")
@@ -140,7 +69,7 @@ def translate(
140
  file_path = download_with_limit(
141
  link_input,
142
  output,
143
- 5 * 1024 * 1024 if flag_demo else None,
144
  )
145
 
146
  filename = os.path.splitext(os.path.basename(file_path))[0]
@@ -196,258 +125,56 @@ def translate(
196
  gr.update(visible=True),
197
  )
198
 
199
-
200
- # Global setup
201
- custom_blue = gr.themes.Color(
202
- c50="#E8F3FF",
203
- c100="#BEDAFF",
204
- c200="#94BFFF",
205
- c300="#6AA1FF",
206
- c400="#4080FF",
207
- c500="#165DFF", # Primary color
208
- c600="#0E42D2",
209
- c700="#0A2BA6",
210
- c800="#061D79",
211
- c900="#03114D",
212
- c950="#020B33",
213
- )
214
-
215
  with gr.Blocks(
216
- title="PDFBestTranslate - PDF Translation with preserved formats",
217
- theme=gr.themes.Default(
218
- primary_hue=custom_blue, spacing_size="md", radius_size="lg"
219
- ),
220
- css="""
221
- .secondary-text {color: #999 !important;}
222
- footer {visibility: hidden}
223
- .env-warning {color: #dd5500 !important;}
224
- .env-success {color: #559900 !important;}
225
- /* Add dashed border to input-file class */
226
- .input-file {
227
- border: 1.2px dashed #165DFF !important;
228
- border-radius: 6px !important;
229
- # background-color: #ffffff !important;
230
- transition: background-color 0.4s ease-out;
231
- }
232
- .input-file:hover {
233
- border: 1.2px dashed #165DFF !important;
234
- border-radius: 6px !important;
235
- color: #165DFF !important;
236
- background-color: #E8F3FF !important;
237
- transition: background-color 0.2s ease-in;
238
- }
239
- .progress-bar-wrap {
240
- border-radius: 8px !important;
241
- }
242
- .progress-bar {
243
- border-radius: 8px !important;
244
- }
245
- # .input-file label {
246
- # color: #165DFF !important;
247
- # border: 1.2px dashed #165DFF !important;
248
- # border-left: none !important;
249
- # border-top: none !important;
250
- # }
251
- # .input-file .wrap {
252
- # color: #165DFF !important;
253
- # }
254
- # .input-file .or {
255
- # color: #165DFF !important;
256
- # }
257
- """,
258
- head=(
259
- """
260
- <script src="https://www.google.com/recaptcha/api.js?render=explicit" async defer></script>
261
- <script type="text/javascript">
262
- var onVerify = function(token) {
263
- el=document.getElementById('verify').getElementsByTagName('textarea')[0];
264
- el.value=token;
265
- el.dispatchEvent(new Event('input'));
266
- };
267
- </script>
268
- """
269
- if flag_demo
270
- else ""
271
- ),
272
  ) as demo:
273
- gr.Markdown(
274
- #"# [PDFMathTranslate @ GitHub](https://github.com/Byaidu/PDFMathTranslate)"
275
- "# [PDFMathTranslate——科研之心免费提供(更多科研AI智能体请点击)](https://ai.linkagi.top)"
276
- )
277
-
278
  with gr.Row():
279
  with gr.Column(scale=1):
280
- gr.Markdown("## File | < 5 MB" if flag_demo else "## File")
281
  file_type = gr.Radio(
282
  choices=["File", "Link"],
283
- label="Type",
284
  value="File",
 
285
  )
286
- file_input = gr.File(
287
- label="File",
288
- file_count="single",
289
- file_types=[".pdf"],
290
- type="filepath",
291
- elem_classes=["input-file"],
292
- )
293
- link_input = gr.Textbox(
294
- label="Link",
295
- visible=False,
296
- interactive=True,
297
- )
298
- gr.Markdown("## Option(请先选择翻译模型)")
299
- with gr.Row():
300
- service = gr.Dropdown(
301
- label="Service",
302
- choices=service_map.keys(),
303
- value="Google",
304
- )
305
- apikey = gr.Textbox(
306
- label="API Key",
307
- max_lines=1,
308
- visible=False,
309
- )
310
- with gr.Row():
311
- lang_from = gr.Dropdown(
312
- label="Translate from",
313
- choices=lang_map.keys(),
314
- value="English",
315
- )
316
- lang_to = gr.Dropdown(
317
- label="Translate to",
318
- choices=lang_map.keys(),
319
- value="Chinese",
320
- )
321
- page_range = gr.Radio(
322
- choices=page_map.keys(),
323
- label="Pages",
324
- value=list(page_map.keys())[0],
325
- )
326
- model_id = gr.Textbox(
327
- label="Model ID",
328
- visible=False,
329
- interactive=True,
330
- )
331
- envs_status = "<span class='env-success'>- Properly configured.</span><br>"
332
-
333
- def details_wrapper(text_markdown):
334
- text = f"""
335
- <summary>Technical details</summary>
336
- {text_markdown}
337
- - GitHub: <a href="https://github.com/Byaidu/PDFMathTranslate">Byaidu/PDFMathTranslate</a><br>
338
- - GUI by: <a href="https://github.com/reycn">Rongxin</a><br>
339
- - Version: {__version__}
340
- """
341
- return text
342
-
343
- def env_var_checker(env_var_name: str) -> str:
344
- if env_var_name:
345
- if not os.environ.get(env_var_name):
346
- envs_status = (
347
- f"<span class='env-warning'>- Warning: environmental not found or error ({env_var_name})."
348
- + "</span><br>- Please make sure that the environment variables are properly configured "
349
- + "(<a href='https://github.com/Byaidu/PDFMathTranslate'>guide</a>).<br>"
350
- )
351
- else:
352
- value = str(os.environ.get(env_var_name))
353
- envs_status = "<span class='env-success'>- Properly configured.</span><br>"
354
- envs_status += (
355
- f"- {env_var_name}: <code>{value[:13]}***</code><br>"
356
- )
357
- else:
358
- envs_status = (
359
- "<span class='env-success'>- Properly configured.</span><br>"
360
- )
361
- return details_wrapper(envs_status)
362
 
363
- def on_select_service(service, evt: gr.EventData):
364
- if service_map[service][1]:
365
- apikey_content = gr.update(
366
- visible=False, value=os.environ.get(service_map[service][1])
367
- )
368
- else:
369
- apikey_content = gr.update(visible=False)
370
- if service_map[service][2]:
371
- model_visibility = gr.update(
372
- visible=True, value=service_map[service][2]
373
- )
374
- else:
375
- model_visibility = gr.update(visible=False)
376
- return (
377
- env_var_checker(service_map[service][1]),
378
- model_visibility,
379
- apikey_content,
380
- )
381
-
382
- def on_select_filetype(file_type):
383
- return (
384
- gr.update(visible=file_type == "File"),
385
- gr.update(visible=file_type == "Link"),
386
- )
387
-
388
- output_title = gr.Markdown("## Translated", visible=False)
389
- output_file = gr.File(label="Download Translation", visible=False)
390
- output_file_dual = gr.File(
391
- label="Download Translation (Dual)", visible=False
392
  )
393
- recaptcha_response = gr.Textbox(
394
- label="reCAPTCHA Response", elem_id="verify", visible=False
 
 
 
395
  )
396
- recaptcha_box = gr.HTML('<div id="recaptcha-box"></div>')
397
- translate_btn = gr.Button("Translate", variant="primary")
398
- tech_details_tog = gr.Markdown(
399
- details_wrapper(envs_status),
400
- elem_classes=["secondary-text"],
 
 
401
  )
402
- service.select(
403
- on_select_service, service, [tech_details_tog, model_id, apikey]
 
 
404
  )
405
- file_type.select(
406
- on_select_filetype,
407
- file_type,
408
- [file_input, link_input],
409
- js=(
410
- f"""
411
- (a,b)=>{{
412
- try{{
413
- grecaptcha.render('recaptcha-box',{{
414
- 'sitekey':'{client_key}',
415
- 'callback':'onVerify'
416
- }});
417
- }}catch(error){{}}
418
- return [a];
419
- }}
420
- """
421
- if flag_demo
422
- else ""
423
- ),
424
  )
 
425
 
426
  with gr.Column(scale=2):
427
- gr.Markdown("## Preview")
428
- preview = gr.Image(label="Document Preview", visible=True)
429
-
430
- # Event handlers
431
- file_input.upload(
432
- upload_file,
433
- inputs=[file_input, service],
434
- outputs=[file_input, preview],
435
- js=(
436
- f"""
437
- (a,b)=>{{
438
- try{{
439
- grecaptcha.render('recaptcha-box',{{
440
- 'sitekey':'{client_key}',
441
- 'callback':'onVerify'
442
- }});
443
- }}catch(error){{}}
444
- return [a];
445
- }}
446
- """
447
- if flag_demo
448
- else ""
449
- ),
450
- )
451
 
452
  translate_btn.click(
453
  translate,
@@ -461,7 +188,6 @@ with gr.Blocks(
461
  lang_from,
462
  lang_to,
463
  page_range,
464
- recaptcha_response,
465
  ],
466
  outputs=[
467
  output_file,
@@ -469,32 +195,10 @@ with gr.Blocks(
469
  output_file_dual,
470
  output_file,
471
  output_file_dual,
472
- output_title,
473
  ],
474
- ).then(lambda: None, js="()=>{grecaptcha.reset()}" if flag_demo else "")
475
-
476
-
477
- def setup_gui(share=False):
478
- if flag_demo:
479
- demo.launch(server_name="0.0.0.0", max_file_size="5mb", inbrowser=True)
480
- else:
481
- try:
482
- demo.launch(server_name="0.0.0.0", debug=True, inbrowser=True, share=share)
483
- except Exception:
484
- print(
485
- "Error launching GUI using 0.0.0.0.\nThis may be caused by global mode of proxy software."
486
- )
487
- try:
488
- demo.launch(
489
- server_name="127.0.0.1", debug=True, inbrowser=True, share=share
490
- )
491
- except Exception:
492
- print(
493
- "Error launching GUI using 127.0.0.1.\nThis may be caused by global mode of proxy software."
494
- )
495
- demo.launch(debug=True, inbrowser=True, share=True)
496
-
497
 
498
- # For auto-reloading while developing
499
  if __name__ == "__main__":
500
- setup_gui()
 
1
  import os
2
  import shutil
3
  from pathlib import Path
 
 
 
4
  import gradio as gr
 
 
5
  import tqdm
6
+ from pdf_translate import extract_text, pdf_preview
 
7
 
8
+ # 语言映射
 
 
 
 
 
 
 
 
 
9
  lang_map = {
10
  "Chinese": "zh",
11
  "English": "en",
 
17
  "Spanish": "es",
18
  "Italian": "it",
19
  }
20
+
21
+ # 页范围映射
22
  page_map = {
23
+ "All Pages": "all",
24
+ "First Page": [0],
25
+ "First 5 Pages": list(range(5)),
26
  }
27
 
28
+ # 服务映射
29
+ # Map service names to pdf2zh service options
30
+ service_map = {
31
+ #"Google": ("google", None, None),
32
+ #"DeepL": ("deepl", "DEEPL_AUTH_KEY", None),
33
+ #"DeepLX": ("deeplx", "DEEPLX_AUTH_KEY", None),
34
+ #"Ollama": ("ollama", None, "gemma2"),
35
+ "OpenAI": ("openai", "OPENAI_API_KEY", "gpt-4o"),
36
+ #"Azure": ("azure", "AZURE_APIKEY", None),
37
+ #"Tencent": ("tencent", "TENCENT_SECRET_KEY", None),
38
+ }
39
+ def download_with_limit(url, output_path, size_limit=None):
40
+ """下载文件并限制大小"""
41
+ # 这里可以实现你的文件下载逻辑
42
+ raise NotImplementedError
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ # 核心翻译函数
45
  def translate(
46
  file_type,
47
  file_input,
 
52
  lang_from,
53
  lang_to,
54
  page_range,
 
55
  progress=gr.Progress(),
56
  ):
 
 
 
 
57
  progress(0, desc="Starting translation...")
58
 
59
  output = Path("pdf2zh_files")
 
69
  file_path = download_with_limit(
70
  link_input,
71
  output,
72
+ 5 * 1024 * 1024, # 限制为 5MB
73
  )
74
 
75
  filename = os.path.splitext(os.path.basename(file_path))[0]
 
125
  gr.update(visible=True),
126
  )
127
 
128
+ # Gradio App 配置
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  with gr.Blocks(
130
+ title="PDFBestTranslate - PDF Translation with Preserved Formats",
131
+ theme=gr.themes.Default(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  ) as demo:
 
 
 
 
 
133
  with gr.Row():
134
  with gr.Column(scale=1):
 
135
  file_type = gr.Radio(
136
  choices=["File", "Link"],
 
137
  value="File",
138
+ label="Input Type",
139
  )
140
+ file_input = gr.File(label="Upload PDF File")
141
+ link_input = gr.Textbox(label="Enter File URL", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
+ file_type.change(
144
+ lambda x: (gr.update(visible=x == "File"), gr.update(visible=x == "Link")),
145
+ file_type,
146
+ [file_input, link_input],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  )
148
+
149
+ service = gr.Radio(
150
+ choices=["Google Translate", "DeepL"],
151
+ value="Google Translate",
152
+ label="Translation Service",
153
  )
154
+ apikey = gr.Textbox(label="API Key (Optional)")
155
+ model_id = gr.Textbox(label="Model ID (Optional)", visible=False)
156
+
157
+ lang_from = gr.Dropdown(
158
+ choices=list(lang_map.keys()),
159
+ value="Auto",
160
+ label="From Language",
161
  )
162
+ lang_to = gr.Dropdown(
163
+ choices=list(lang_map.keys()),
164
+ value="Chinese",
165
+ label="To Language",
166
  )
167
+ page_range = gr.Radio(
168
+ choices=list(page_map.keys()),
169
+ value="All Pages",
170
+ label="Page Range",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  )
172
+ translate_btn = gr.Button("Translate")
173
 
174
  with gr.Column(scale=2):
175
+ output_file = gr.File(label="Translated PDF File", visible=False)
176
+ preview = gr.Textbox(label="Translated Preview", visible=False)
177
+ output_file_dual = gr.File(label="Dual-Language PDF File", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
  translate_btn.click(
180
  translate,
 
188
  lang_from,
189
  lang_to,
190
  page_range,
 
191
  ],
192
  outputs=[
193
  output_file,
 
195
  output_file_dual,
196
  output_file,
197
  output_file_dual,
198
+ preview,
199
  ],
200
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
+ # 启动 Gradio 应用
203
  if __name__ == "__main__":
204
+ demo.launch()