BabelDOC 0.1.25__tar.gz → 0.1.26__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.
Files changed (90) hide show
  1. {babeldoc-0.1.25 → babeldoc-0.1.26}/PKG-INFO +5 -1
  2. {babeldoc-0.1.25 → babeldoc-0.1.26}/README.md +4 -0
  3. babeldoc-0.1.26/babeldoc/__init__.py +1 -0
  4. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/const.py +1 -1
  5. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/backend/pdf_creater.py +6 -2
  6. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/midend/typesetting.py +6 -1
  7. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/docvision/doclayout.py +1 -1
  8. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/high_level.py +107 -0
  9. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/main.py +20 -6
  10. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/progress_monitor.py +33 -0
  11. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/translation_config.py +54 -0
  12. {babeldoc-0.1.25 → babeldoc-0.1.26}/pyproject.toml +2 -2
  13. {babeldoc-0.1.25 → babeldoc-0.1.26}/uv.lock +1 -1
  14. babeldoc-0.1.25/babeldoc/__init__.py +0 -1
  15. {babeldoc-0.1.25 → babeldoc-0.1.26}/.cursorignore +0 -0
  16. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
  17. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
  18. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/dependabot.yml +0 -0
  19. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/labels.yml +0 -0
  20. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/release-drafter.yml +0 -0
  21. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/workflows/codeql.yml +0 -0
  22. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/workflows/docs.yml +0 -0
  23. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/workflows/labeler.yml +0 -0
  24. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/workflows/publish-to-pypi.yml +0 -0
  25. {babeldoc-0.1.25 → babeldoc-0.1.26}/.github/workflows/test.yml +0 -0
  26. {babeldoc-0.1.25 → babeldoc-0.1.26}/.gitignore +0 -0
  27. {babeldoc-0.1.25 → babeldoc-0.1.26}/.pre-commit-config.yaml +0 -0
  28. {babeldoc-0.1.25 → babeldoc-0.1.26}/LICENSE +0 -0
  29. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/assets/assets.py +0 -0
  30. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/assets/embedding_assets_metadata.py +0 -0
  31. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/asynchronize/__init__.py +0 -0
  32. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/converter.py +0 -0
  33. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/__init__.py +0 -0
  34. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
  35. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/backend/__init__.py +0 -0
  36. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/frontend/__init__.py +0 -0
  37. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/frontend/il_creater.py +0 -0
  38. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/il_version_1.py +0 -0
  39. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/il_version_1.rnc +0 -0
  40. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/il_version_1.rng +0 -0
  41. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/il_version_1.xsd +0 -0
  42. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/midend/__init__.py +0 -0
  43. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/midend/add_debug_information.py +0 -0
  44. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/midend/detect_scanned_file.py +0 -0
  45. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/midend/il_translator.py +0 -0
  46. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/midend/layout_parser.py +0 -0
  47. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/midend/paragraph_finder.py +0 -0
  48. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/midend/remove_descent.py +0 -0
  49. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/midend/styles_and_formulas.py +0 -0
  50. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/translator/__init__.py +0 -0
  51. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/translator/cache.py +0 -0
  52. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/translator/translator.py +0 -0
  53. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/utils/fontmap.py +0 -0
  54. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/utils/layout_helper.py +0 -0
  55. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/utils/style_helper.py +0 -0
  56. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/document_il/xml_converter.py +0 -0
  57. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/docvision/README.md +0 -0
  58. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/docvision/__init__.py +0 -0
  59. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/docvision/rpc_doclayout.py +0 -0
  60. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/pdfinterp.py +0 -0
  61. {babeldoc-0.1.25 → babeldoc-0.1.26}/babeldoc/tools/generate_font_metadata.py +0 -0
  62. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/CODE_OF_CONDUCT.md +0 -0
  63. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/CONTRIBUTING.md +0 -0
  64. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/CONTRIBUTOR_REWARD.md +0 -0
  65. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
  66. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
  67. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
  68. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
  69. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
  70. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/ImplementationDetails/README.md +0 -0
  71. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
  72. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
  73. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/README.md +0 -0
  74. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/deploy.sh +0 -0
  75. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-banner.png +0 -0
  76. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
  77. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
  78. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
  79. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
  80. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-big-logo.png +0 -0
  81. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-contributor_reward_example.png +0 -0
  82. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-preview.gif +0 -0
  83. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
  84. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
  85. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/images/babeldoc-small-logo.png +0 -0
  86. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/index.md +0 -0
  87. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/intro-to-pdf-object.md +0 -0
  88. {babeldoc-0.1.25 → babeldoc-0.1.26}/docs/requirements.txt +0 -0
  89. {babeldoc-0.1.25 → babeldoc-0.1.26}/mkdocs.yml +0 -0
  90. {babeldoc-0.1.25 → babeldoc-0.1.26}/tests/test_translation_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BabelDOC
3
- Version: 0.1.25
3
+ Version: 0.1.26
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
@@ -157,6 +157,8 @@ uv run babeldoc --bing --files example.pdf --files example2.pdf
157
157
  - `--disable-rich-text-translate`: Disable rich text translation (may help improve compatibility with some PDFs)
158
158
  - `--enhance-compatibility`: Enable all compatibility enhancement options (equivalent to --skip-clean --dual-translate-first --disable-rich-text-translate)
159
159
  - `--use-alternating-pages-dual`: Use alternating pages mode for dual PDF. When enabled, original and translated pages are arranged in alternate order. When disabled (default), original and translated pages are shown side by side on the same page.
160
+ - `--watermark-output-mode`: Control watermark output mode: 'watermarked' (default) adds watermark to translated PDF, 'no_watermark' doesn't add watermark, 'both' outputs both versions.
161
+ - `--no-watermark`: [DEPRECATED] Use --watermark-output-mode=no_watermark instead.
160
162
 
161
163
  > [!TIP]
162
164
  > - Both `--skip-clean` and `--dual-translate-first` may help improve compatibility with some PDF readers
@@ -262,6 +264,8 @@ skip-clean = false
262
264
  dual-translate-first = false
263
265
  disable-rich-text-translate = false
264
266
  use-alternating-pages-dual = false
267
+ watermark-output-mode = "watermarked" # Choices: "watermarked", "no_watermark", "both"
268
+ # no-watermark = false # DEPRECATED: Use watermark-output-mode instead
265
269
 
266
270
  # Output control
267
271
  no-dual = false
@@ -122,6 +122,8 @@ uv run babeldoc --bing --files example.pdf --files example2.pdf
122
122
  - `--disable-rich-text-translate`: Disable rich text translation (may help improve compatibility with some PDFs)
123
123
  - `--enhance-compatibility`: Enable all compatibility enhancement options (equivalent to --skip-clean --dual-translate-first --disable-rich-text-translate)
124
124
  - `--use-alternating-pages-dual`: Use alternating pages mode for dual PDF. When enabled, original and translated pages are arranged in alternate order. When disabled (default), original and translated pages are shown side by side on the same page.
125
+ - `--watermark-output-mode`: Control watermark output mode: 'watermarked' (default) adds watermark to translated PDF, 'no_watermark' doesn't add watermark, 'both' outputs both versions.
126
+ - `--no-watermark`: [DEPRECATED] Use --watermark-output-mode=no_watermark instead.
125
127
 
126
128
  > [!TIP]
127
129
  > - Both `--skip-clean` and `--dual-translate-first` may help improve compatibility with some PDF readers
@@ -227,6 +229,8 @@ skip-clean = false
227
229
  dual-translate-first = false
228
230
  disable-rich-text-translate = false
229
231
  use-alternating-pages-dual = false
232
+ watermark-output-mode = "watermarked" # Choices: "watermarked", "no_watermark", "both"
233
+ # no-watermark = false # DEPRECATED: Use watermark-output-mode instead
230
234
 
231
235
  # Output control
232
236
  no-dual = false
@@ -0,0 +1 @@
1
+ __version__ = "0.1.26"
@@ -2,7 +2,7 @@ import shutil
2
2
  import subprocess
3
3
  from pathlib import Path
4
4
 
5
- __version__ = "0.1.25"
5
+ __version__ = "0.1.26"
6
6
 
7
7
  CACHE_FOLDER = Path.home() / ".cache" / "babeldoc"
8
8
 
@@ -12,6 +12,7 @@ from babeldoc.document_il import il_version_1
12
12
  from babeldoc.document_il.utils.fontmap import FontMapper
13
13
  from babeldoc.translation_config import TranslateResult
14
14
  from babeldoc.translation_config import TranslationConfig
15
+ from babeldoc.translation_config import WatermarkOutputMode
15
16
 
16
17
  logger = logging.getLogger(__name__)
17
18
 
@@ -416,8 +417,9 @@ class PDFCreater:
416
417
  pdf = self.subset_fonts_in_subprocess(pdf, translation_config, tag="debug")
417
418
  return pdf
418
419
 
420
+ @staticmethod
419
421
  def subset_fonts_in_subprocess(
420
- self, pdf: pymupdf.Document, translation_config: TranslationConfig, tag: str
422
+ pdf: pymupdf.Document, translation_config: TranslationConfig, tag: str
421
423
  ) -> pymupdf.Document:
422
424
  """Run font subsetting in a subprocess with timeout.
423
425
 
@@ -489,8 +491,8 @@ class PDFCreater:
489
491
  )
490
492
  return original_pdf
491
493
 
494
+ @staticmethod
492
495
  def save_pdf_with_timeout(
493
- self,
494
496
  pdf: pymupdf.Document,
495
497
  output_path: str,
496
498
  translation_config: TranslationConfig,
@@ -634,6 +636,8 @@ class PDFCreater:
634
636
  def write(self, translation_config: TranslationConfig) -> TranslateResult:
635
637
  basename = Path(translation_config.input_file).stem
636
638
  debug_suffix = ".debug" if translation_config.debug else ""
639
+ if translation_config.watermark_output_mode != WatermarkOutputMode.Watermarked:
640
+ debug_suffix += ".no_watermark"
637
641
  mono_out_path = translation_config.get_output_file_path(
638
642
  f"{basename}{debug_suffix}.{translation_config.lang_out}.mono.pdf",
639
643
  )
@@ -15,6 +15,7 @@ from babeldoc.document_il import PdfStyle
15
15
  from babeldoc.document_il import il_version_1
16
16
  from babeldoc.document_il.utils.fontmap import FontMapper
17
17
  from babeldoc.translation_config import TranslationConfig
18
+ from babeldoc.translation_config import WatermarkOutputMode
18
19
 
19
20
  logger = logging.getLogger(__name__)
20
21
 
@@ -450,7 +451,11 @@ class Typesetting:
450
451
  fonts[xobj.xobj_id] = page_fonts.copy()
451
452
  for font in xobj.pdf_font:
452
453
  fonts[xobj.xobj_id][font.font_id] = font
453
- if page.page_number == 0:
454
+ if (
455
+ page.page_number == 0
456
+ and self.translation_config.watermark_output_mode
457
+ == WatermarkOutputMode.Watermarked
458
+ ):
454
459
  self.add_watermark(page)
455
460
  # 开始实际的渲染过程
456
461
  for paragraph in page.pdf_paragraph:
@@ -192,7 +192,7 @@ class OnnxModel(DocLayoutModel):
192
192
  boxes[..., :4] = (boxes[..., :4] - [pad_x, pad_y, pad_x, pad_y]) / gain
193
193
  return boxes
194
194
 
195
- def predict(self, image, imgsz=1024, batch_size=16, **kwargs):
195
+ def predict(self, image, imgsz=800, batch_size=16, **kwargs):
196
196
  """
197
197
  Predict the layout of document pages.
198
198
 
@@ -1,6 +1,9 @@
1
1
  import asyncio
2
+ import copy
2
3
  import hashlib
4
+ import io
3
5
  import logging
6
+ import pathlib
4
7
  import threading
5
8
  import time
6
9
  from asyncio import CancelledError
@@ -19,6 +22,7 @@ from babeldoc import asynchronize
19
22
  from babeldoc.assets.assets import warmup
20
23
  from babeldoc.const import CACHE_FOLDER
21
24
  from babeldoc.converter import TranslateConverter
25
+ from babeldoc.document_il import il_version_1
22
26
  from babeldoc.document_il.backend.pdf_creater import SAVE_PDF_STAGE_NAME
23
27
  from babeldoc.document_il.backend.pdf_creater import SUBSET_FONT_STAGE_NAME
24
28
  from babeldoc.document_il.backend.pdf_creater import PDFCreater
@@ -37,6 +41,7 @@ from babeldoc.pdfinterp import PDFPageInterpreterEx
37
41
  from babeldoc.progress_monitor import ProgressMonitor
38
42
  from babeldoc.translation_config import TranslateResult
39
43
  from babeldoc.translation_config import TranslationConfig
44
+ from babeldoc.translation_config import WatermarkOutputMode
40
45
 
41
46
  logger = logging.getLogger(__name__)
42
47
 
@@ -365,6 +370,13 @@ def do_translate(pm, translation_config):
365
370
  docs,
366
371
  translation_config.get_working_file_path("add_debug_information.json"),
367
372
  )
373
+ mono_watermark_first_page_doc_bytes = None
374
+ dual_watermark_first_page_doc_bytes = None
375
+
376
+ if translation_config.watermark_output_mode == WatermarkOutputMode.Both:
377
+ mono_watermark_first_page_doc_bytes, dual_watermark_first_page_doc_bytes = (
378
+ generate_first_page_with_watermark(doc_input, translation_config, docs)
379
+ )
368
380
 
369
381
  Typesetting(translation_config).typsetting_document(docs)
370
382
  logger.debug(f"finish typsetting from {temp_pdf_path}")
@@ -377,6 +389,22 @@ def do_translate(pm, translation_config):
377
389
  # docs2 = xml_converter.deepcopy(docs)
378
390
  pdf_creater = PDFCreater(temp_pdf_path, docs, translation_config)
379
391
  result = pdf_creater.write(translation_config)
392
+
393
+ if mono_watermark_first_page_doc_bytes:
394
+ mono_watermark_pdf = merge_watermark_doc(
395
+ result.mono_pdf_path,
396
+ mono_watermark_first_page_doc_bytes,
397
+ translation_config,
398
+ )
399
+ result.mono_pdf_path = mono_watermark_pdf
400
+ if dual_watermark_first_page_doc_bytes:
401
+ dual_watermark_pdf = merge_watermark_doc(
402
+ result.dual_pdf_path,
403
+ dual_watermark_first_page_doc_bytes,
404
+ translation_config,
405
+ )
406
+ result.dual_pdf_path = dual_watermark_pdf
407
+
380
408
  finish_time = time.time()
381
409
  result.original_pdf_path = original_pdf_path
382
410
  result.total_seconds = finish_time - start_time
@@ -395,6 +423,85 @@ def do_translate(pm, translation_config):
395
423
  translation_config.cleanup_temp_files()
396
424
 
397
425
 
426
+ def generate_first_page_with_watermark(
427
+ mupdf: Document,
428
+ translation_config: TranslationConfig,
429
+ doc_il: il_version_1.Document,
430
+ ) -> (io.BytesIO, io.BytesIO):
431
+ first_page_doc = Document()
432
+ first_page_doc.insert_pdf(mupdf, from_page=0, to_page=0)
433
+
434
+ il_only_first_page_doc = il_version_1.Document()
435
+ il_only_first_page_doc.total_pages = 1
436
+ il_only_first_page_doc.page = [copy.deepcopy(doc_il.page[0])]
437
+
438
+ watermarked_config = copy.copy(translation_config)
439
+ watermarked_config.watermark_output_mode = WatermarkOutputMode.Watermarked
440
+ watermarked_config.progress_monitor.disable = True
441
+ watermarked_temp_pdf_path = watermarked_config.get_working_file_path(
442
+ "watermarked_temp_input.pdf"
443
+ )
444
+ first_page_doc.save(watermarked_temp_pdf_path)
445
+
446
+ Typesetting(watermarked_config).typsetting_document(il_only_first_page_doc)
447
+ pdf_creater = PDFCreater(
448
+ watermarked_temp_pdf_path.as_posix(), il_only_first_page_doc, watermarked_config
449
+ )
450
+ result = pdf_creater.write(watermarked_config)
451
+ watermarked_config.progress_monitor.disable = False
452
+ mono_pdf_bytes = None
453
+ dual_pdf_bytes = None
454
+ if result.mono_pdf_path:
455
+ mono_pdf_bytes = io.BytesIO()
456
+ with Path(result.mono_pdf_path).open("rb") as f:
457
+ mono_pdf_bytes.write(f.read())
458
+ result.mono_pdf_path.unlink()
459
+ mono_pdf_bytes.seek(0)
460
+
461
+ if result.dual_pdf_path:
462
+ dual_pdf_bytes = io.BytesIO()
463
+ with Path(result.dual_pdf_path).open("rb") as f:
464
+ dual_pdf_bytes.write(f.read())
465
+ result.dual_pdf_path.unlink()
466
+ dual_pdf_bytes.seek(0)
467
+
468
+ return mono_pdf_bytes, dual_pdf_bytes
469
+
470
+
471
+ def merge_watermark_doc(
472
+ no_watermark_pdf_path: pathlib.PosixPath,
473
+ watermark_first_page_pdf_bytes: io.BytesIO,
474
+ translation_config: TranslationConfig,
475
+ ) -> pathlib.PosixPath:
476
+ if not no_watermark_pdf_path.exists():
477
+ raise FileNotFoundError(
478
+ f"no_watermark_pdf_path not found: {no_watermark_pdf_path}"
479
+ )
480
+ if not watermark_first_page_pdf_bytes:
481
+ raise FileNotFoundError(
482
+ f"watermark_first_page_pdf_bytes not found: {watermark_first_page_pdf_bytes}"
483
+ )
484
+
485
+ no_watermark_pdf = Document(no_watermark_pdf_path.as_posix())
486
+ no_watermark_pdf.delete_page(0)
487
+
488
+ watermark_first_page_pdf = Document("pdf", watermark_first_page_pdf_bytes)
489
+ no_watermark_pdf.insert_pdf(
490
+ watermark_first_page_pdf, from_page=0, to_page=0, start_at=0
491
+ )
492
+
493
+ new_save_path = no_watermark_pdf_path.with_name(
494
+ no_watermark_pdf_path.name.replace(".no_watermark", "")
495
+ )
496
+
497
+ PDFCreater.save_pdf_with_timeout(
498
+ no_watermark_pdf,
499
+ new_save_path.as_posix(),
500
+ translation_config=translation_config,
501
+ )
502
+ return new_save_path
503
+
504
+
398
505
  def download_font_assets():
399
506
  warmup()
400
507
 
@@ -21,9 +21,10 @@ from babeldoc.document_il.translator.translator import set_translate_rate_limite
21
21
  from babeldoc.docvision.doclayout import DocLayoutModel
22
22
  from babeldoc.docvision.rpc_doclayout import RpcDocLayoutModel
23
23
  from babeldoc.translation_config import TranslationConfig
24
+ from babeldoc.translation_config import WatermarkOutputMode
24
25
 
25
26
  logger = logging.getLogger(__name__)
26
- __version__ = "0.1.25"
27
+ __version__ = "0.1.26"
27
28
 
28
29
 
29
30
  def create_parser():
@@ -169,6 +170,18 @@ def create_parser():
169
170
  action="store_true",
170
171
  help="Use alternating pages mode for dual PDF. When enabled, original and translated pages are arranged in alternate order.",
171
172
  )
173
+ translation_group.add_argument(
174
+ "--watermark-output-mode",
175
+ type=str,
176
+ choices=["watermarked", "no_watermark", "both"],
177
+ default="watermarked",
178
+ help="Control watermark output mode: 'watermarked' (default) adds watermark to translated PDF, 'no_watermark' doesn't add watermark, 'both' outputs both versions.",
179
+ )
180
+ translation_group.add_argument(
181
+ "--no-watermark",
182
+ action="store_true",
183
+ help="[DEPRECATED] Use --watermark-output-mode=no_watermark instead. Do not add watermark to the translated PDF.",
184
+ )
172
185
  translation_group.add_argument(
173
186
  "--report-interval",
174
187
  type=float,
@@ -336,6 +349,11 @@ async def main():
336
349
  use_alternating_pages_dual=args.use_alternating_pages_dual,
337
350
  report_interval=args.report_interval,
338
351
  min_text_length=args.min_text_length,
352
+ watermark_output_mode=(
353
+ WatermarkOutputMode.NoWatermark
354
+ if args.no_watermark
355
+ else getattr(WatermarkOutputMode, args.watermark_output_mode.title())
356
+ ),
339
357
  )
340
358
 
341
359
  # Create progress handler
@@ -349,11 +367,7 @@ async def main():
349
367
  logger.debug(event)
350
368
  if event["type"] == "finish":
351
369
  result = event["translate_result"]
352
- logger.info("Translation Result:")
353
- logger.info(f" Original PDF: {result.original_pdf_path}")
354
- logger.info(f" Time Cost: {result.total_seconds:.2f}s")
355
- logger.info(f" Mono PDF: {result.mono_pdf_path or 'None'}")
356
- logger.info(f" Dual PDF: {result.dual_pdf_path or 'None'}")
370
+ logger.info(str(result))
357
371
  break
358
372
 
359
373
 
@@ -35,6 +35,7 @@ class ProgressMonitor:
35
35
  self.finish_event = finish_event
36
36
  self.cancel_event = cancel_event
37
37
  self.loop = loop
38
+ self.disable = False
38
39
  if finish_event and not loop:
39
40
  raise ValueError("finish_event requires a loop")
40
41
  if self.progress_change_callback:
@@ -51,6 +52,8 @@ class ProgressMonitor:
51
52
  self.lock = threading.Lock()
52
53
 
53
54
  def stage_start(self, stage_name: str, total: int):
55
+ if self.disable:
56
+ return DummyTranslationStage(stage_name, total, self, 0)
54
57
  stage = self.stage[stage_name]
55
58
  stage.run_time += 1
56
59
  stage.name = stage_name
@@ -78,6 +81,8 @@ class ProgressMonitor:
78
81
  logger.debug("ProgressMonitor __exit__")
79
82
 
80
83
  def on_finish(self):
84
+ if self.disable:
85
+ return
81
86
  if self.cancel_event:
82
87
  self.cancel_event.set()
83
88
  if self.finish_event and self.loop:
@@ -86,6 +91,8 @@ class ProgressMonitor:
86
91
  self.finish_callback(type="error", error=CancelledError)
87
92
 
88
93
  def stage_done(self, stage):
94
+ if self.disable:
95
+ return
89
96
  self.last_report_time = 0.0
90
97
  self.finish_stage_count += 1
91
98
  if (
@@ -128,6 +135,8 @@ class ProgressMonitor:
128
135
  return progress
129
136
 
130
137
  def stage_update(self, stage, n: int):
138
+ if self.disable:
139
+ return
131
140
  with self.lock:
132
141
  report_time_delta = time.time() - self.last_report_time
133
142
  if report_time_delta < self.report_interval and stage.total > 3:
@@ -144,10 +153,14 @@ class ProgressMonitor:
144
153
  self.last_report_time = time.time()
145
154
 
146
155
  def translate_done(self, translate_result):
156
+ if self.disable:
157
+ return
147
158
  if self.finish_callback:
148
159
  self.finish_callback(type="finish", translate_result=translate_result)
149
160
 
150
161
  def translate_error(self, error):
162
+ if self.disable:
163
+ return
151
164
  if self.finish_callback:
152
165
  self.finish_callback(type="error", error=error)
153
166
 
@@ -156,6 +169,8 @@ class ProgressMonitor:
156
169
  raise asyncio.CancelledError
157
170
 
158
171
  def cancel(self):
172
+ if self.disable:
173
+ return
159
174
  if self.cancel_event:
160
175
  logger.info("Translation canceled")
161
176
  self.cancel_event.set()
@@ -180,3 +195,21 @@ class TranslationStage:
180
195
  def advance(self, n: int = 1):
181
196
  self.current += n
182
197
  self.pm.stage_update(self, n)
198
+
199
+
200
+ class DummyTranslationStage:
201
+ def __init__(self, name: str, total: int, pm: ProgressMonitor, weight: float):
202
+ self.name = name
203
+ self.display_name = name
204
+ self.current = 0
205
+ self.total = total
206
+ self.pm = pm
207
+
208
+ def __enter__(self):
209
+ return self
210
+
211
+ def __exit__(self, exc_type, exc_val, exc_tb):
212
+ pass
213
+
214
+ def advance(self, n: int = 1):
215
+ pass
@@ -1,3 +1,4 @@
1
+ import enum
1
2
  import logging
2
3
  import shutil
3
4
  import tempfile
@@ -12,6 +13,12 @@ from babeldoc.progress_monitor import ProgressMonitor
12
13
  logger = logging.getLogger(__name__)
13
14
 
14
15
 
16
+ class WatermarkOutputMode(enum.Enum):
17
+ Watermarked = "watermarked"
18
+ NoWatermark = "no_watermark"
19
+ Both = "both"
20
+
21
+
15
22
  class TranslationConfig:
16
23
  def __init__(
17
24
  self,
@@ -43,6 +50,7 @@ class TranslationConfig:
43
50
  min_text_length: int = 5,
44
51
  use_side_by_side_dual: bool = True, # Deprecated: 是否使用拼版式双语 PDF(并排显示原文和译文) 向下兼容选项,已停用。
45
52
  use_alternating_pages_dual: bool = False,
53
+ watermark_output_mode: WatermarkOutputMode = WatermarkOutputMode.Watermarked,
46
54
  ):
47
55
  self.translator = translator
48
56
 
@@ -55,6 +63,7 @@ class TranslationConfig:
55
63
  self.pages = pages
56
64
  self.page_ranges = self._parse_pages(pages) if pages else None
57
65
  self.debug = debug
66
+ self.watermark_output_mode = watermark_output_mode
58
67
 
59
68
  self.output_dir = output_dir
60
69
  self.working_dir = working_dir
@@ -175,7 +184,52 @@ class TranslateResult:
175
184
  total_seconds: float
176
185
  mono_pdf_path: str | None
177
186
  dual_pdf_path: str | None
187
+ no_watermark_mono_pdf_path: str | None
188
+ no_watermark_dual_pdf_path: str | None
178
189
 
179
190
  def __init__(self, mono_pdf_path: str | None, dual_pdf_path: str | None):
180
191
  self.mono_pdf_path = mono_pdf_path
181
192
  self.dual_pdf_path = dual_pdf_path
193
+
194
+ # For compatibility considerations, if only a non-watermarked PDF is generated,
195
+ # the values of mono_pdf_path and no_watermark_mono_pdf_path are the same.
196
+ self.no_watermark_mono_pdf_path = mono_pdf_path
197
+ self.no_watermark_dual_pdf_path = dual_pdf_path
198
+
199
+ def __str__(self):
200
+ """Return a human-readable string representation of the translation result."""
201
+ result = []
202
+ if hasattr(self, "original_pdf_path") and self.original_pdf_path:
203
+ result.append(f"\tOriginal PDF: {self.original_pdf_path}")
204
+
205
+ if hasattr(self, "total_seconds") and self.total_seconds:
206
+ result.append(f"\tTotal time: {self.total_seconds:.2f} seconds")
207
+
208
+ if self.mono_pdf_path:
209
+ result.append(f"\tMonolingual PDF: {self.mono_pdf_path}")
210
+
211
+ if self.dual_pdf_path:
212
+ result.append(f"\tDual-language PDF: {self.dual_pdf_path}")
213
+
214
+ if (
215
+ hasattr(self, "no_watermark_mono_pdf_path")
216
+ and self.no_watermark_mono_pdf_path
217
+ and self.no_watermark_mono_pdf_path != self.mono_pdf_path
218
+ ):
219
+ result.append(
220
+ f"\tNo-watermark Monolingual PDF: {self.no_watermark_mono_pdf_path}"
221
+ )
222
+
223
+ if (
224
+ hasattr(self, "no_watermark_dual_pdf_path")
225
+ and self.no_watermark_dual_pdf_path
226
+ and self.no_watermark_dual_pdf_path != self.dual_pdf_path
227
+ ):
228
+ result.append(
229
+ f"\tNo-watermark Dual-language PDF: {self.no_watermark_dual_pdf_path}"
230
+ )
231
+
232
+ if result:
233
+ result.insert(0, "Translation results:")
234
+
235
+ return "\n".join(result) if result else "No translation results available"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "BabelDOC"
3
- version = "0.1.25"
3
+ version = "0.1.26"
4
4
  description = "Yet Another Document Translator"
5
5
  license = "AGPL-3.0"
6
6
  readme = "README.md"
@@ -133,7 +133,7 @@ pythonpath = [".", "src"]
133
133
  testpaths = ["tests"]
134
134
 
135
135
  [bumpver]
136
- current_version = "0.1.25"
136
+ current_version = "0.1.26"
137
137
  version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
138
138
 
139
139
  [bumpver.file_patterns]
@@ -48,7 +48,7 @@ wheels = [
48
48
 
49
49
  [[package]]
50
50
  name = "babeldoc"
51
- version = "0.1.25"
51
+ version = "0.1.26"
52
52
  source = { editable = "." }
53
53
  dependencies = [
54
54
  { name = "bitstring" },
@@ -1 +0,0 @@
1
- __version__ = "0.1.25"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes