# -*- coding: utf-8 -*- """Extract screenshots from 8.28 manuals with captions.""" import os, re, json from pathlib import Path import fitz SRC = Path(r"D:\产线\大洋\大洋操作手册汇总8.28") OUT = Path(r"D:\产线\大洋\大洋操作手册汇总9.19\images") MIN_W, MIN_H = 180, 80 MIN_PIXELS = 25000 FASCICLES = [ "1、系统管理操作手册.pdf", "2、设备管理操作手册.pdf", "3、物料管理操作手册.pdf", "4、质量管理操作手册.pdf", "5、生产管理操作手册.pdf", "6、ERP对接操作手册.pdf", "7、生产线管理操作手册.pdf", "8、PDA操作说明操作手册.pdf", "9、MES系统客户端员工扫码操作手册.pdf", ] def caption_from_page(page, img_bbox): blocks = page.get_text("blocks") texts = [] for b in blocks: x0, y0, x1, y1, text, *_ = b[:6] text = (text or "").strip() if not text: continue # caption usually below image, short texts.append((y0, y1, text.replace("\n", " ").strip())) # find nearest text below image below = [t for t in texts if t[0] >= img_bbox[3] - 8] below.sort(key=lambda t: t[0]) for y0, y1, text in below[:3]: t = re.sub(r"\s+", " ", text) if 2 <= len(t) <= 40 and not t.startswith("第") and "PAGE" not in t: if not re.match(r"^\d+(\.\d+)*\.?\s*$", t): return t # fallback: last short line above above = [t for t in texts if t[1] <= img_bbox[1] + 8] if above: t = re.sub(r"\s+", " ", above[-1][2]) if 2 <= len(t) <= 50: return t return "" def heading_from_page(page): blocks = page.get_text("blocks") heads = [] for b in blocks: text = (b[4] or "").strip() if not text: continue first = text.split("\n")[0].strip() if re.match(r"^\d+(\.\d+)*\.?\s+\S+", first) or first.startswith("3."): heads.append(first) return heads[0] if heads else "" def safe_name(s): s = re.sub(r'[\\/:*?"<>|]', "", s) s = re.sub(r"\s+", "_", s.strip()) return s[:40] def extract_one(pdf_name): doc = fitz.open(SRC / pdf_name) stem = pdf_name[:-4] dest = OUT / stem dest.mkdir(parents=True, exist_ok=True) meta = [] seen_xref = {} idx = 0 for pi, page in enumerate(doc, 1): heading = heading_from_page(page) images = page.get_images(full=True) # also get image bboxes via dict img_info = [] d = page.get_text("dict") for block in d.get("blocks", []): if block.get("type") != 1: continue bbox = block.get("bbox") w = bbox[2] - bbox[0] h = bbox[3] - bbox[1] img_info.append((bbox, w, h)) # extract by xref, map to bbox by order for ii, img in enumerate(images): xref = img[0] try: pix = fitz.Pixmap(doc, xref) except Exception: continue if pix.n - pix.alpha > 3: pix = fitz.Pixmap(fitz.csRGB, pix) if pix.width < MIN_W or pix.height < MIN_H or pix.width * pix.height < MIN_PIXELS: continue # skip near-duplicates of same xref on same page key = (pi, xref, pix.width, pix.height) if key in seen_xref: continue seen_xref[key] = True bbox = img_info[ii][0] if ii < len(img_info) else (0, 0, pix.width, pix.height) cap = caption_from_page(page, bbox) idx += 1 fname = "%03d_p%02d_%s.png" % (idx, pi, safe_name(cap) or "img") path = dest / fname pix.save(str(path)) meta.append({ "file": str(path.relative_to(OUT.parent)).replace("\\", "/"), "page": pi, "caption": cap, "heading": heading, "w": pix.width, "h": pix.height, }) doc.close() (dest / "manifest.json").write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8") print(stem, "images", len(meta)) return meta def main(): OUT.mkdir(parents=True, exist_ok=True) allm = {} for name in FASCICLES: allm[name] = extract_one(name) (OUT / "all_manifest.json").write_text(json.dumps(allm, ensure_ascii=False, indent=2), encoding="utf-8") print("total", sum(len(v) for v in allm.values())) if __name__ == "__main__": main()