STLooo commited on
Commit
5e6e8d3
·
verified ·
1 Parent(s): 034b79e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +88 -111
app.py CHANGED
@@ -1,150 +1,127 @@
1
- import fitz # PyMuPDF
2
- import cv2
3
- import numpy as np
4
  import os
 
5
  import gradio as gr
6
- import gc
7
  import zipfile
8
- from PIL import Image
9
- import tempfile
10
 
11
- # 常量設定
12
- MAX_PAGES = 250
13
- BG_PATH = "background.jpg"
14
  FG_DIR = "foregrounds"
15
  OUTPUT_DIR = "output"
16
- ZIP_PATH = "output/results.zip"
 
17
  TARGET_WIDTH, TARGET_HEIGHT = 2133, 1200
18
  POS1 = (1032, 0)
19
  POS2 = (4666, 0)
20
 
21
- # 初始化資料夾與權限檢查
22
- def check_write_permission(path):
23
  if not os.path.exists(path):
24
- try:
25
- os.makedirs(path)
26
- except Exception as e:
27
- raise RuntimeError(f"❌ 無法建立目錄 '{path}':{e}")
28
- elif not os.access(path, os.W_OK):
29
- raise RuntimeError(f"❌ 目錄 '{path}' 存在,但沒有寫入權限!")
30
 
31
- check_write_permission(FG_DIR)
32
- check_write_permission(OUTPUT_DIR)
33
 
34
- # PDF轉圖片(使用 PyMuPDF)
35
  def pdf_to_images(pdf_file):
36
- try:
37
- doc = fitz.open(pdf_file.name) # 直接使用暫存檔案路徑
38
- image_paths = []
39
-
40
- for i, page in enumerate(doc, start=1):
41
- pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))
42
- img_path = os.path.join(FG_DIR, f"page_{i:03d}.jpg")
43
- pix.save(img_path)
44
- image_paths.append(img_path)
45
-
46
- return image_paths
47
- except Exception as e:
48
- raise gr.Error(f"PDF轉換失敗: {str(e)}")
49
-
50
- # 合成圖片
51
  def overlay_image(bg, fg, x, y):
52
  h, w = fg.shape[:2]
53
- bh, bw = bg.shape[:2]
54
- if x + w > bw or y + h > bh:
55
- raise ValueError(f"前景圖超出背景邊界:({x},{y},{w},{h}) > 背景({bw},{bh})")
56
-
57
  if fg.shape[2] == 4:
58
  alpha = fg[:, :, 3:] / 255.0
59
  bg[y:y+h, x:x+w] = alpha * fg[:, :, :3] + (1 - alpha) * bg[y:y+h, x:x+w]
60
  else:
61
- bg[y:y+h, x:x+w] = fg[:, :, :3]
62
  return bg
63
 
64
- # 壓縮 zip
65
- def zip_output_files():
66
- with zipfile.ZipFile(ZIP_PATH, "w", zipfile.ZIP_DEFLATED) as zipf:
67
- for file in sorted(os.listdir(OUTPUT_DIR)):
68
- if file.endswith(".jpg"):
69
- zipf.write(os.path.join(OUTPUT_DIR, file), arcname=file)
70
  return ZIP_PATH
71
 
72
- # 流程
73
- def process_pdf(pdf_file, prefix="result_"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  if not prefix.strip():
75
  prefix = "result_"
76
- try:
77
- progress = gr.Progress()
78
- progress(0, desc="準備中...")
79
-
80
- # 轉換PDF為圖片
81
- progress(0.1, "正在轉換PDF頁面為圖片...")
82
- image_paths = pdf_to_images(pdf_file)
83
-
84
- if len(image_paths) == 0:
85
- return [], None, "PDF轉換失敗,未產生任何圖片"
86
- if len(image_paths) > MAX_PAGES:
87
- raise gr.Error(f"PDF頁數過多,最多支援 {MAX_PAGES} 頁")
88
-
89
- # 讀取背景圖
90
- bg = cv2.imread(BG_PATH)
91
- if bg is None:
92
- raise gr.Error("無法讀取背景圖(請確認 background.jpg 是否存在)")
93
-
94
- # 清除舊結果
95
- for f in os.listdir(OUTPUT_DIR):
96
- if f.endswith(".jpg"):
97
- os.remove(os.path.join(OUTPUT_DIR, f))
98
-
99
- results = []
100
- for i, img_path in enumerate(image_paths, 1):
101
- progress(i / len(image_paths), f"合成第 {i} 頁...")
102
-
103
- try:
104
- fg = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
105
- if fg is None:
106
- continue
107
 
108
- fg = cv2.resize(fg, (TARGET_WIDTH, TARGET_HEIGHT), interpolation=cv2.INTER_AREA)
109
- result = overlay_image(bg.copy(), fg, *POS1)
110
- result = overlay_image(result, fg, *POS2)
 
 
 
 
 
 
111
 
112
- output_path = f"{OUTPUT_DIR}/{prefix}{i:03d}.jpg"
113
- cv2.imwrite(output_path, result)
114
- results.append((output_path, f"Page {i:03d}"))
115
-
116
- if i % 10 == 0:
117
- gc.collect()
118
-
119
- except Exception as e:
120
- print(f"處理第 {i} 頁失敗: {str(e)}")
121
- continue
122
-
123
- progress(1, "完成!")
124
- zip_file = zip_output_files()
125
- return results, zip_file, f"成功處理 {len(results)}/{len(image_paths)} 頁,已打包為 ZIP 可下載"
126
-
127
- except Exception as e:
128
- raise gr.Error(f"處理出錯:{str(e)}")
129
 
130
  # Gradio 介面
131
  with gr.Blocks() as demo:
132
- gr.Markdown("## 📄 PDF頁面合成工具(背景固定為 7872x1200)")
133
 
134
- pdf_upload = gr.File(label="上傳PDF文件", file_types=[".pdf"])
135
- prefix_input = gr.Textbox(label="輸出檔案前綴 (選填)", placeholder="例如:invoice_")
136
- btn_run = gr.Button("開始合成", variant="primary")
 
137
 
138
  with gr.Row():
139
- gallery = gr.Gallery(label="合成結果預覽", columns=3, preview=True)
140
- zip_download = gr.File(label="下載結果 ZIP")
141
- log_output = gr.Textbox(label="日誌", interactive=False)
142
 
143
  btn_run.click(
144
- fn=process_pdf,
145
- inputs=[pdf_upload, prefix_input],
146
- outputs=[gallery, zip_download, log_output],
147
- concurrency_limit=4
148
  )
149
 
150
  if __name__ == "__main__":
 
 
 
 
1
  import os
2
+ import cv2
3
  import gradio as gr
4
+ import numpy as np
5
  import zipfile
6
+ import fitz # PyMuPDF
7
+ import gc
8
 
9
+ # 參數設定
 
 
10
  FG_DIR = "foregrounds"
11
  OUTPUT_DIR = "output"
12
+ ZIP_PATH = os.path.join(OUTPUT_DIR, "results.zip")
13
+ BG_PATH = "background.jpg"
14
  TARGET_WIDTH, TARGET_HEIGHT = 2133, 1200
15
  POS1 = (1032, 0)
16
  POS2 = (4666, 0)
17
 
18
+ # 建立資料夾
19
+ def ensure_dir(path):
20
  if not os.path.exists(path):
21
+ os.makedirs(path)
22
+ if not os.access(path, os.W_OK):
23
+ raise RuntimeError(f"❌ 沒有寫入權限:{path}")
 
 
 
24
 
25
+ ensure_dir(FG_DIR)
26
+ ensure_dir(OUTPUT_DIR)
27
 
28
+ # PDF 每頁
29
  def pdf_to_images(pdf_file):
30
+ doc = fitz.open(pdf_file.name)
31
+ paths = []
32
+ for i, page in enumerate(doc, start=1):
33
+ pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))
34
+ img_path = os.path.join(FG_DIR, f"page_{i:03d}.jpg")
35
+ pix.save(img_path)
36
+ paths.append(img_path)
37
+ return paths
38
+
39
+ # 圖像合成功能
 
 
 
 
 
40
  def overlay_image(bg, fg, x, y):
41
  h, w = fg.shape[:2]
 
 
 
 
42
  if fg.shape[2] == 4:
43
  alpha = fg[:, :, 3:] / 255.0
44
  bg[y:y+h, x:x+w] = alpha * fg[:, :, :3] + (1 - alpha) * bg[y:y+h, x:x+w]
45
  else:
46
+ bg[y:y+h, x:x+w] = fg
47
  return bg
48
 
49
+ # 壓縮 ZIP
50
+ def zip_results():
51
+ with zipfile.ZipFile(ZIP_PATH, "w", zipfile.ZIP_DEFLATED) as z:
52
+ for f in sorted(os.listdir(OUTPUT_DIR)):
53
+ if f.endswith(".jpg"):
54
+ z.write(os.path.join(OUTPUT_DIR, f), arcname=f)
55
  return ZIP_PATH
56
 
57
+ # 合成流程
58
+ def process_and_combine(fg_paths, prefix):
59
+ bg = cv2.imread(BG_PATH)
60
+ if bg is None:
61
+ raise gr.Error("❌ 無法讀取 background.jpg")
62
+
63
+ # 清空舊檔
64
+ for f in os.listdir(OUTPUT_DIR):
65
+ if f.endswith(".jpg"):
66
+ os.remove(os.path.join(OUTPUT_DIR, f))
67
+
68
+ results = []
69
+ for i, path in enumerate(fg_paths, 1):
70
+ fg = cv2.imread(path, cv2.IMREAD_UNCHANGED)
71
+ if fg is None:
72
+ continue
73
+ fg = cv2.resize(fg, (TARGET_WIDTH, TARGET_HEIGHT))
74
+ result = overlay_image(bg.copy(), fg, *POS1)
75
+ result = overlay_image(result, fg, *POS2)
76
+
77
+ out_path = os.path.join(OUTPUT_DIR, f"{prefix}{i:03d}.jpg")
78
+ cv2.imwrite(out_path, result)
79
+ results.append((out_path, f"{prefix}{i:03d}"))
80
+
81
+ if i % 10 == 0:
82
+ gc.collect()
83
+
84
+ if not results:
85
+ raise gr.Error("⚠️ 沒有生成任何圖片")
86
+
87
+ zip_file = zip_results()
88
+ return results, zip_file, f"✅ 成功合成 {len(results)} 張圖片"
89
+
90
+ # 主入口:支援圖片或 PDF 混合上傳
91
+ def process_inputs(files, prefix):
92
  if not prefix.strip():
93
  prefix = "result_"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ fg_paths = []
96
+ for f in files:
97
+ name = f.name.lower()
98
+ if name.endswith(".pdf"):
99
+ fg_paths.extend(pdf_to_images(f))
100
+ elif name.endswith((".png", ".jpg", ".jpeg")):
101
+ fg_paths.append(f.name)
102
+ else:
103
+ raise gr.Error(f"不支援的檔案格式:{name}")
104
 
105
+ return process_and_combine(fg_paths, prefix)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
  # Gradio 介面
108
  with gr.Blocks() as demo:
109
+ gr.Markdown("## 📄🖼️ PDF 或圖片合成工具(背景為 7872x1200)")
110
 
111
+ with gr.Row():
112
+ upload_files = gr.Files(label="上傳 PDF 或圖片", file_types=[".pdf", ".png", ".jpg", ".jpeg"])
113
+ prefix_input = gr.Textbox(label="輸出檔案前綴 (選填)", placeholder="例如:project_")
114
+ btn_run = gr.Button("開始合成", variant="primary")
115
 
116
  with gr.Row():
117
+ gallery = gr.Gallery(label="預覽", columns=3)
118
+ zip_file = gr.File(label="下載 ZIP")
119
+ log_output = gr.Textbox(label="處理日誌", interactive=False)
120
 
121
  btn_run.click(
122
+ fn=process_inputs,
123
+ inputs=[upload_files, prefix_input],
124
+ outputs=[gallery, zip_file, log_output]
 
125
  )
126
 
127
  if __name__ == "__main__":