pdf2zh.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/env python3
  2. """A command line tool for extracting text and images from PDF and
  3. output it to plain text, html, xml or tags.
  4. """
  5. import argparse
  6. import logging
  7. import os
  8. import sys
  9. from typing import Any, Container, Iterable, List, Optional
  10. import pymupdf
  11. import layoutparser as lp
  12. import tempfile
  13. import urllib.request
  14. import pdf2zh.high_level
  15. from pdf2zh.layout import LAParams
  16. from pdf2zh.pdfexceptions import PDFValueError
  17. from pdf2zh.utils import AnyIO
  18. logging.basicConfig()
  19. OUTPUT_TYPES = ((".htm", "html"), (".html", "html"), (".xml", "xml"), (".tag", "tag"))
  20. def float_or_disabled(x: str) -> Optional[float]:
  21. if x.lower().strip() == "disabled":
  22. return None
  23. try:
  24. return float(x)
  25. except ValueError:
  26. raise argparse.ArgumentTypeError(f"invalid float value: {x}")
  27. def extract_text(
  28. files: Iterable[str] = [],
  29. outfile: str = "-",
  30. laparams: Optional[LAParams] = None,
  31. output_type: str = "text",
  32. codec: str = "utf-8",
  33. strip_control: bool = False,
  34. maxpages: int = 0,
  35. pages: Optional[Container[int]] = None,
  36. password: str = "",
  37. scale: float = 1.0,
  38. rotation: int = 0,
  39. layoutmode: str = "normal",
  40. output_dir: Optional[str] = None,
  41. debug: bool = False,
  42. disable_caching: bool = False,
  43. vfont: str = "",
  44. vchar: str = "",
  45. thread: int = 0,
  46. lang_in: str = "",
  47. lang_out: str = "",
  48. **kwargs: Any,
  49. ) -> AnyIO:
  50. if not files:
  51. raise PDFValueError("Must provide files to work upon!")
  52. if output_type == "text" and outfile != "-":
  53. for override, alttype in OUTPUT_TYPES:
  54. if outfile.endswith(override):
  55. output_type = alttype
  56. outfp: AnyIO = sys.stdout
  57. pth = os.path.join(tempfile.gettempdir(), 'mfd-tf_efficientdet_d0.pth.tar')
  58. if not os.path.exists(pth):
  59. print('Downloading...')
  60. urllib.request.urlretrieve("https://www.dropbox.com/s/dkr22iux7thlhel/mfd-tf_efficientdet_d0.pth.tar?dl=1",pth)
  61. model = lp.EfficientDetLayoutModel("lp://efficientdet/MFD/tf_efficientdet_d0",pth)
  62. for file in files:
  63. filename = os.path.splitext(os.path.basename(file))[0]
  64. doc_en = pymupdf.open(file)
  65. page_count=doc_en.page_count
  66. font_list=['china-ss','tiro']
  67. font_id={}
  68. for page in doc_en:
  69. for font in font_list:
  70. font_id[font]=page.insert_font(font)
  71. xreflen = doc_en.xref_length()
  72. for xref in range(1, xreflen):
  73. font_res=doc_en.xref_get_key(xref,'Resources/Font')
  74. if font_res[0]=='dict':
  75. for font in font_list:
  76. font_exist=doc_en.xref_get_key(xref,f'Resources/Font/{font}')
  77. if font_exist[0]=='null':
  78. try:
  79. doc_en.xref_set_key(xref,f'Resources/Font/{font}',f'{font_id[font]} 0 R')
  80. except:
  81. pass
  82. doc_en.save(f'{filename}-en.pdf')
  83. with open(f'{filename}-en.pdf', "rb") as fp:
  84. obj_patch:dict=pdf2zh.high_level.extract_text_to_fp(fp, **locals())
  85. for obj_id,ops_full in obj_patch.items():
  86. doc_en.update_stream(obj_id,ops_full.encode())
  87. doc_zh = doc_en
  88. doc_dual = pymupdf.open(f'{filename}-en.pdf')
  89. doc_dual.insert_file(doc_zh)
  90. for id in range(page_count):
  91. doc_dual.move_page(page_count+id,id*2+1)
  92. doc_zh.save(f'{filename}-zh.pdf',deflate=1)
  93. doc_dual.save(f'{filename}-dual.pdf',deflate=1)
  94. doc_zh.close()
  95. doc_dual.close()
  96. os.remove(f'{filename}-en.pdf')
  97. return
  98. def create_parser() -> argparse.ArgumentParser:
  99. parser = argparse.ArgumentParser(description=__doc__, add_help=True)
  100. parser.add_argument(
  101. "files",
  102. type=str,
  103. default=None,
  104. nargs="+",
  105. help="One or more paths to PDF files.",
  106. )
  107. parser.add_argument(
  108. "--version",
  109. "-v",
  110. action="version",
  111. version=f"pdf2zh v{pdf2zh.__version__}",
  112. )
  113. parser.add_argument(
  114. "--debug",
  115. "-d",
  116. default=False,
  117. action="store_true",
  118. help="Use debug logging level.",
  119. )
  120. parse_params = parser.add_argument_group(
  121. "Parser",
  122. description="Used during PDF parsing",
  123. )
  124. parse_params.add_argument(
  125. "--pages",
  126. "-p",
  127. type=str,
  128. help="The list of page numbers to parse.",
  129. )
  130. parse_params.add_argument(
  131. "--password",
  132. "-P",
  133. type=str,
  134. default="",
  135. help="The password to use for decrypting PDF file.",
  136. )
  137. parse_params.add_argument(
  138. "--vfont",
  139. "-f",
  140. type=str,
  141. default="",
  142. help="The regex to math font name of formula.",
  143. )
  144. parse_params.add_argument(
  145. "--vchar",
  146. "-c",
  147. type=str,
  148. default="",
  149. help="The regex to math character of formula.",
  150. )
  151. parse_params.add_argument(
  152. "--lang-in",
  153. "-li",
  154. type=str,
  155. default="en",
  156. help="The code of source language.",
  157. )
  158. parse_params.add_argument(
  159. "--lang-out",
  160. "-lo",
  161. type=str,
  162. default="zh-CN",
  163. help="The code of target language.",
  164. )
  165. parse_params.add_argument(
  166. "--thread",
  167. "-t",
  168. type=int,
  169. default=4,
  170. help="The number of threads to execute translation.",
  171. )
  172. return parser
  173. def parse_args(args: Optional[List[str]]) -> argparse.Namespace:
  174. parsed_args = create_parser().parse_args(args=args)
  175. if parsed_args.pages:
  176. pages = []
  177. for p in parsed_args.pages.split(","):
  178. if "-" in p:
  179. start, end = p.split("-")
  180. pages.extend(range(int(start) - 1, int(end)))
  181. else:
  182. pages.append(int(p) - 1)
  183. parsed_args.pages = pages
  184. return parsed_args
  185. def main(args: Optional[List[str]] = None) -> int:
  186. parsed_args = parse_args(args)
  187. extract_text(**vars(parsed_args))
  188. return 0
  189. if __name__ == "__main__":
  190. sys.exit(main())