DeepLearning101 commited on
Commit
9d4caaa
·
verified ·
1 Parent(s): f6f1840

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +22 -22
app.py CHANGED
@@ -52,9 +52,8 @@ class NotebookLMTool:
52
  def _call_gemini_with_retry(self, model_name, contents, config=None, retries=5):
53
  """
54
  封裝 Gemini 呼叫,加入指數退避重試機制 (Exponential Backoff)
55
- 專門處理 429 Resource Exhausted 錯誤
56
  """
57
- delay = 10 # 初始等待秒數
58
 
59
  for attempt in range(retries):
60
  try:
@@ -65,22 +64,22 @@ class NotebookLMTool:
65
  )
66
  return response
67
  except Exception as e:
68
- # 檢查是否為 Rate Limit 相關錯誤 (包含 429 或 Service Unavailable)
69
  error_str = str(e)
 
70
  if "429" in error_str or "RESOURCE_EXHAUSTED" in error_str or "503" in error_str:
71
- wait_time = delay + random.uniform(0, 5) # 加入隨機抖動避免同時重試
72
- print(f"⚠️ 觸發 API 限制,暫停 {wait_time:.1f} 秒後重試 ({attempt+1}/{retries})...", flush=True)
73
  time.sleep(wait_time)
74
- delay *= 2 # 等待時間加倍 (10s -> 20s -> 40s...)
75
  else:
76
- raise e # 其他錯誤直接拋出
77
 
78
- raise Exception("API 重試次數過多,請稍後再試。")
79
 
80
  # --- 單頁處理邏輯 ---
81
  def process_single_page(self, page_index, img, img_output_dir):
82
  """處理單一頁面的:去字(背景) + 文字分析(Layout)"""
83
- print(f"🚀 [Page {page_index+1}] 開始處理...", flush=True)
84
 
85
  result = {
86
  "index": page_index,
@@ -96,7 +95,9 @@ class NotebookLMTool:
96
  final_bg_path = os.path.join(img_output_dir, save_name)
97
  bg_success = False
98
 
 
99
  # 1. 背景去字 (Image Cleaning)
 
100
  try:
101
  clean_prompt = """
102
  Strictly remove all text, titles, text-boxes, and bullet points from this slide image.
@@ -106,9 +107,9 @@ class NotebookLMTool:
106
  3. Output ONLY the image.
107
  """
108
 
109
- # 使用帶重試機制的呼叫
110
  resp_img = self._call_gemini_with_retry(
111
- model_name="gemini-2.0-flash-exp",
112
  contents=[clean_prompt, img],
113
  config=types.GenerateContentConfig(response_modalities=["IMAGE"])
114
  )
@@ -133,28 +134,30 @@ class NotebookLMTool:
133
  result["bg_path"] = final_bg_path
134
  result["preview"] = (final_bg_path, f"Page {page_index+1} Cleaned")
135
  else:
136
- print(f"⚠️ [Page {page_index+1}] 去字失敗: 未回傳圖片", flush=True)
137
 
138
  except Exception as e:
139
  print(f"❌ [Page {page_index+1}] Clean Error: {e}", flush=True)
140
 
141
- # 失敗回退原圖
142
  if not bg_success:
143
  img.save(final_bg_path)
144
  result["bg_path"] = final_bg_path
145
  result["preview"] = (final_bg_path, f"Page {page_index+1} (Original)")
146
- result["log"] += f"[P{page_index+1}] Warning: Background cleaning failed (Rate Limit or Error).\n"
147
 
 
148
  # 2. 文字與佈局分析 (Layout Analysis)
 
149
  try:
150
  layout_prompt = """
151
  Analyze this slide. Return a JSON list of all text blocks.
152
  Each item: {"text": string, "box_2d": [ymin, xmin, ymax, xmax] (0-1000), "font_size": int, "color": hex, "is_bold": bool}
153
  """
154
 
155
- # 使用帶重試機制的呼叫
156
  resp_layout = self._call_gemini_with_retry(
157
- model_name="gemini-2.0-flash",
158
  contents=[layout_prompt, img],
159
  config=types.GenerateContentConfig(response_mime_type="application/json")
160
  )
@@ -211,14 +214,12 @@ class NotebookLMTool:
211
  progress(0.2, desc="🚀 AI 處理中 (已啟用速率保護)...")
212
 
213
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
214
- # 提交任務,但加入微小延遲避免瞬間併發過高
215
  future_to_page = {}
216
  for i, img in enumerate(images):
217
- time.sleep(1) # 讓請求稍微錯開
218
  future = executor.submit(self.process_single_page, i, img, img_output_dir)
219
  future_to_page[future] = i
220
 
221
- # 等待完成
222
  for future in concurrent.futures.as_completed(future_to_page):
223
  try:
224
  res = future.result()
@@ -234,8 +235,7 @@ class NotebookLMTool:
234
  cleaned_images_paths = []
235
 
236
  for i in range(len(images)):
237
- if i not in results_map:
238
- continue
239
  res = results_map[i]
240
 
241
  full_text_log += res["log"]
@@ -331,7 +331,7 @@ with gr.Blocks(title="NotebookLM Slide Restorer,PPT.404", theme=gr.themes.Soft
331
 
332
  gr.Markdown("---")
333
  pdf_input = gr.File(label="上傳 PDF")
334
- btn_process = gr.Button("🚀 開始還原 PPTX (平行加速版)", variant="primary")
335
 
336
  with gr.Column():
337
  out_zip = gr.File(label="📦 下載完整包")
 
52
  def _call_gemini_with_retry(self, model_name, contents, config=None, retries=5):
53
  """
54
  封裝 Gemini 呼叫,加入指數退避重試機制 (Exponential Backoff)
 
55
  """
56
+ delay = 5 # 初始等待秒數
57
 
58
  for attempt in range(retries):
59
  try:
 
64
  )
65
  return response
66
  except Exception as e:
 
67
  error_str = str(e)
68
+ # 檢查是否為 Rate Limit 相關錯誤 (429, 503, Resource Exhausted)
69
  if "429" in error_str or "RESOURCE_EXHAUSTED" in error_str or "503" in error_str:
70
+ wait_time = delay + random.uniform(0, 3)
71
+ print(f"⚠️ API 忙碌 (Attempt {attempt+1}/{retries}),休息 {wait_time:.1f} 秒...", flush=True)
72
  time.sleep(wait_time)
73
+ delay *= 1.5 # 遞增等待時間
74
  else:
75
+ raise e # 其他錯誤 (如 400) 直接拋出
76
 
77
+ raise Exception("API 重試多次失敗,請檢查配額。")
78
 
79
  # --- 單頁處理邏輯 ---
80
  def process_single_page(self, page_index, img, img_output_dir):
81
  """處理單一頁面的:去字(背景) + 文字分析(Layout)"""
82
+ print(f"🚀 [Page {page_index+1}] 啟動處理...", flush=True)
83
 
84
  result = {
85
  "index": page_index,
 
95
  final_bg_path = os.path.join(img_output_dir, save_name)
96
  bg_success = False
97
 
98
+ # ==========================================
99
  # 1. 背景去字 (Image Cleaning)
100
+ # ==========================================
101
  try:
102
  clean_prompt = """
103
  Strictly remove all text, titles, text-boxes, and bullet points from this slide image.
 
107
  3. Output ONLY the image.
108
  """
109
 
110
+ # ✅ 修正點 2: 使用 _call_gemini_with_retry 確保 429 時會重試
111
  resp_img = self._call_gemini_with_retry(
112
+ model_name="gemini-2.5-flash-image",
113
  contents=[clean_prompt, img],
114
  config=types.GenerateContentConfig(response_modalities=["IMAGE"])
115
  )
 
134
  result["bg_path"] = final_bg_path
135
  result["preview"] = (final_bg_path, f"Page {page_index+1} Cleaned")
136
  else:
137
+ print(f"⚠️ [Page {page_index+1}] 去字失敗: 模型未回傳圖片", flush=True)
138
 
139
  except Exception as e:
140
  print(f"❌ [Page {page_index+1}] Clean Error: {e}", flush=True)
141
 
142
+ # 失敗回退原圖 (但標記為失敗)
143
  if not bg_success:
144
  img.save(final_bg_path)
145
  result["bg_path"] = final_bg_path
146
  result["preview"] = (final_bg_path, f"Page {page_index+1} (Original)")
147
+ result["log"] += f"[P{page_index+1}] Warning: Background cleaning failed. Used original image.\n"
148
 
149
+ # ==========================================
150
  # 2. 文字與佈局分析 (Layout Analysis)
151
+ # ==========================================
152
  try:
153
  layout_prompt = """
154
  Analyze this slide. Return a JSON list of all text blocks.
155
  Each item: {"text": string, "box_2d": [ymin, xmin, ymax, xmax] (0-1000), "font_size": int, "color": hex, "is_bold": bool}
156
  """
157
 
158
+ # ✅ 修正點 3: 使用 _call_gemini_with_retry
159
  resp_layout = self._call_gemini_with_retry(
160
+ model_name="gemini-2.5-flash",
161
  contents=[layout_prompt, img],
162
  config=types.GenerateContentConfig(response_mime_type="application/json")
163
  )
 
214
  progress(0.2, desc="🚀 AI 處理中 (已啟用速率保護)...")
215
 
216
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
 
217
  future_to_page = {}
218
  for i, img in enumerate(images):
219
+ time.sleep(1.5) # 稍微加大間隔,避免同時撞牆
220
  future = executor.submit(self.process_single_page, i, img, img_output_dir)
221
  future_to_page[future] = i
222
 
 
223
  for future in concurrent.futures.as_completed(future_to_page):
224
  try:
225
  res = future.result()
 
235
  cleaned_images_paths = []
236
 
237
  for i in range(len(images)):
238
+ if i not in results_map: continue
 
239
  res = results_map[i]
240
 
241
  full_text_log += res["log"]
 
331
 
332
  gr.Markdown("---")
333
  pdf_input = gr.File(label="上傳 PDF")
334
+ btn_process = gr.Button("🚀 開始還原 PPTX (穩定修復版)", variant="primary")
335
 
336
  with gr.Column():
337
  out_zip = gr.File(label="📦 下載完整包")