extract_manual_images.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. # -*- coding: utf-8 -*-
  2. """Extract screenshots from 8.28 manuals with captions."""
  3. import os, re, json
  4. from pathlib import Path
  5. import fitz
  6. SRC = Path(r"D:\产线\大洋\大洋操作手册汇总8.28")
  7. OUT = Path(r"D:\产线\大洋\大洋操作手册汇总9.19\images")
  8. MIN_W, MIN_H = 180, 80
  9. MIN_PIXELS = 25000
  10. FASCICLES = [
  11. "1、系统管理操作手册.pdf",
  12. "2、设备管理操作手册.pdf",
  13. "3、物料管理操作手册.pdf",
  14. "4、质量管理操作手册.pdf",
  15. "5、生产管理操作手册.pdf",
  16. "6、ERP对接操作手册.pdf",
  17. "7、生产线管理操作手册.pdf",
  18. "8、PDA操作说明操作手册.pdf",
  19. "9、MES系统客户端员工扫码操作手册.pdf",
  20. ]
  21. def caption_from_page(page, img_bbox):
  22. blocks = page.get_text("blocks")
  23. texts = []
  24. for b in blocks:
  25. x0, y0, x1, y1, text, *_ = b[:6]
  26. text = (text or "").strip()
  27. if not text:
  28. continue
  29. # caption usually below image, short
  30. texts.append((y0, y1, text.replace("\n", " ").strip()))
  31. # find nearest text below image
  32. below = [t for t in texts if t[0] >= img_bbox[3] - 8]
  33. below.sort(key=lambda t: t[0])
  34. for y0, y1, text in below[:3]:
  35. t = re.sub(r"\s+", " ", text)
  36. if 2 <= len(t) <= 40 and not t.startswith("第") and "PAGE" not in t:
  37. if not re.match(r"^\d+(\.\d+)*\.?\s*$", t):
  38. return t
  39. # fallback: last short line above
  40. above = [t for t in texts if t[1] <= img_bbox[1] + 8]
  41. if above:
  42. t = re.sub(r"\s+", " ", above[-1][2])
  43. if 2 <= len(t) <= 50:
  44. return t
  45. return ""
  46. def heading_from_page(page):
  47. blocks = page.get_text("blocks")
  48. heads = []
  49. for b in blocks:
  50. text = (b[4] or "").strip()
  51. if not text:
  52. continue
  53. first = text.split("\n")[0].strip()
  54. if re.match(r"^\d+(\.\d+)*\.?\s+\S+", first) or first.startswith("3."):
  55. heads.append(first)
  56. return heads[0] if heads else ""
  57. def safe_name(s):
  58. s = re.sub(r'[\\/:*?"<>|]', "", s)
  59. s = re.sub(r"\s+", "_", s.strip())
  60. return s[:40]
  61. def extract_one(pdf_name):
  62. doc = fitz.open(SRC / pdf_name)
  63. stem = pdf_name[:-4]
  64. dest = OUT / stem
  65. dest.mkdir(parents=True, exist_ok=True)
  66. meta = []
  67. seen_xref = {}
  68. idx = 0
  69. for pi, page in enumerate(doc, 1):
  70. heading = heading_from_page(page)
  71. images = page.get_images(full=True)
  72. # also get image bboxes via dict
  73. img_info = []
  74. d = page.get_text("dict")
  75. for block in d.get("blocks", []):
  76. if block.get("type") != 1:
  77. continue
  78. bbox = block.get("bbox")
  79. w = bbox[2] - bbox[0]
  80. h = bbox[3] - bbox[1]
  81. img_info.append((bbox, w, h))
  82. # extract by xref, map to bbox by order
  83. for ii, img in enumerate(images):
  84. xref = img[0]
  85. try:
  86. pix = fitz.Pixmap(doc, xref)
  87. except Exception:
  88. continue
  89. if pix.n - pix.alpha > 3:
  90. pix = fitz.Pixmap(fitz.csRGB, pix)
  91. if pix.width < MIN_W or pix.height < MIN_H or pix.width * pix.height < MIN_PIXELS:
  92. continue
  93. # skip near-duplicates of same xref on same page
  94. key = (pi, xref, pix.width, pix.height)
  95. if key in seen_xref:
  96. continue
  97. seen_xref[key] = True
  98. bbox = img_info[ii][0] if ii < len(img_info) else (0, 0, pix.width, pix.height)
  99. cap = caption_from_page(page, bbox)
  100. idx += 1
  101. fname = "%03d_p%02d_%s.png" % (idx, pi, safe_name(cap) or "img")
  102. path = dest / fname
  103. pix.save(str(path))
  104. meta.append({
  105. "file": str(path.relative_to(OUT.parent)).replace("\\", "/"),
  106. "page": pi,
  107. "caption": cap,
  108. "heading": heading,
  109. "w": pix.width,
  110. "h": pix.height,
  111. })
  112. doc.close()
  113. (dest / "manifest.json").write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")
  114. print(stem, "images", len(meta))
  115. return meta
  116. def main():
  117. OUT.mkdir(parents=True, exist_ok=True)
  118. allm = {}
  119. for name in FASCICLES:
  120. allm[name] = extract_one(name)
  121. (OUT / "all_manifest.json").write_text(json.dumps(allm, ensure_ascii=False, indent=2), encoding="utf-8")
  122. print("total", sum(len(v) for v in allm.values()))
  123. if __name__ == "__main__":
  124. main()