import os, cv2, zipfile, gc, gradio as gr import numpy as np import fitz # PyMuPDF from PIL import Image # 設定 OUTPUT_DIR = "output" ZIP_PATH = os.path.join(OUTPUT_DIR, "results.zip") BG_PATH = "background.jpg" TARGET_WIDTH, TARGET_HEIGHT = 2133, 1200 POS1 = (1032, 0) POS2 = (4666, 0) def ensure_dir(path): os.makedirs(path, exist_ok=True) if not os.access(path, os.W_OK): raise RuntimeError(f"❌ 無法寫入:{path}") ensure_dir(OUTPUT_DIR) # 解析頁碼範圍字串(如 "1-3,5,7") def parse_page_selection(selection_text): if not selection_text.strip(): return None pages = set() for part in selection_text.split(','): part = part.strip() if '-' in part: start, end = map(int, part.split('-')) pages.update(range(start, end + 1)) elif part.isdigit(): pages.add(int(part)) return sorted(pages) # PDF轉圖片(np.array 格式,BGR) def pdf_to_images(pdf_file, page_list=None): images = [] with fitz.open(pdf_file.name) as doc: total_pages = len(doc) selected = page_list or list(range(1, total_pages + 1)) for page_num in selected: if 1 <= page_num <= total_pages: pix = doc[page_num - 1].get_pixmap(matrix=fitz.Matrix(2, 2), colorspace=fitz.csRGB) img = np.frombuffer(pix.samples, dtype=np.uint8).reshape((pix.height, pix.width, 3)) img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) images.append(img) return images # 合成圖片 def overlay_image(bg, fg_img, x, y): fg = cv2.resize(fg_img, (TARGET_WIDTH, TARGET_HEIGHT), interpolation=cv2.INTER_AREA) bg[y:y+TARGET_HEIGHT, x:x+TARGET_WIDTH] = fg return bg # 處理與合成流程 def process_and_combine(images, prefix): bg = cv2.imread(BG_PATH) if bg is None: raise gr.Error("❌ 無法讀取 background.jpg") for f in os.listdir(OUTPUT_DIR): if f.endswith(".jpg"): os.remove(os.path.join(OUTPUT_DIR, f)) results = [] for i, img in enumerate(images, 1): combined = overlay_image(bg.copy(), img, *POS1) combined = overlay_image(combined, img, *POS2) output_path = os.path.join(OUTPUT_DIR, f"{prefix}{i:03d}.jpg") cv2.imwrite(output_path, combined) results.append((output_path, f"{prefix}{i:03d}")) if i % 10 == 0: gc.collect() if not results: raise gr.Error("⚠️ 沒有產生圖片") with zipfile.ZipFile(ZIP_PATH, "w", zipfile.ZIP_DEFLATED) as zipf: for f in sorted(os.listdir(OUTPUT_DIR)): if f.endswith(".jpg"): zipf.write(os.path.join(OUTPUT_DIR, f), arcname=f) return results, ZIP_PATH, f"✅ 成功合成 {len(results)} 張圖,已打包 ZIP" # 主邏輯:上傳處理 def process_inputs(files, prefix, page_selection): prefix = prefix.strip() or "result_" selected_pages = parse_page_selection(page_selection) images = [] for file in files: name = file.name.lower() if name.endswith(".pdf"): images.extend(pdf_to_images(file, selected_pages)) elif name.endswith((".png", ".jpg", ".jpeg")): img = Image.open(file).convert("RGB") img = np.array(img) img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) images.append(img) else: raise gr.Error(f"不支援的檔案格式:{name}") if not images: raise gr.Error("⚠️ 沒有有效圖片可處理,請確認頁碼或檔案內容") return process_and_combine(images, prefix) # Gradio UI 元件定義 page_selection = gr.Textbox(label="選擇頁碼(例如:1,3,5-7)", placeholder="留空代表全部頁面") # 介面建構 demo = gr.Interface( fn=process_inputs, inputs=[ gr.Files(label="📎 上傳 PDF 或圖片", file_types=[".pdf", ".png", ".jpg", ".jpeg"]), gr.Textbox(label="檔名前綴(可選)", placeholder="例如:poster_"), page_selection ], outputs=[ gr.Gallery(label="🖼️ 預覽", columns=3, height="auto"), gr.File(label="⬇️ 下載 ZIP"), gr.Textbox(label="📄 處理日誌") ], title="PDF 圖片合成工具", description="📄🖼️ 將 PDF 或圖片套用固定背景合成為寬圖,背景為 7872x1200,支援 PDF 選擇頁數與多檔處理。" ) if __name__ == "__main__": demo.launch()