davidlee831117 commited on
Commit
c4d9900
·
verified ·
1 Parent(s): 86eb4e0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +188 -124
app.py CHANGED
@@ -29,6 +29,16 @@ except Exception as e:
29
  if not GEMINI_API_KEY:
30
  raise ValueError("ERROR: GEMINI_API_KEY environment variable is not set. Please set it correctly in your Hugging Face Space settings.")
31
 
 
 
 
 
 
 
 
 
 
 
32
  class NanoBananaImageGenerator:
33
  def __init__(self, api_key):
34
  self.api_key = api_key
@@ -41,13 +51,16 @@ class NanoBananaImageGenerator:
41
  img_bytes = img_byte_arr.getvalue()
42
  return {"inline_data": {"mime_type": "image/png", "data": base64.b64encode(img_bytes).decode('utf-8')}}
43
 
44
- def _load_image_from_url(self, url):
45
  try:
46
- response = requests.get(url)
 
47
  response.raise_for_status()
48
- return Image.open(BytesIO(response.content)).convert("RGB")
49
- except Exception as e:
50
- print(f"Error loading image from URL {url}: {e}")
 
 
51
  return None
52
 
53
  def build_prompt_for_operation(self, prompt, operation="generate", has_references=False, aspect_ratio="1:1", character_consistency=True):
@@ -103,7 +116,7 @@ class NanoBananaImageGenerator:
103
  for i in range(batch_count):
104
  try:
105
  print(f"Debug: Batch {i+1} - Attempting to call Gemini API.")
106
- print(f"Debug: Batch {i+1} - Contents (first part): {content_parts[0]['parts'][0]['text'][:50]}...")
107
  response = GenerativeModel("gemini-2.5-flash-image-preview").generate_content(
108
  contents=content_parts,
109
  generation_config=generation_config
@@ -115,13 +128,11 @@ class NanoBananaImageGenerator:
115
 
116
  batch_images = []
117
 
118
- # 優先檢查提示詞或候選者是否因安全政策被拒絕
119
  if hasattr(response, 'prompt_feedback') and response.prompt_feedback.safety_ratings:
120
  operation_log += f"提示詞因安全政策被拒絕: {response.prompt_feedback.safety_ratings}\n"
121
  continue
122
 
123
  if not hasattr(response, 'candidates') or not response.candidates:
124
- # 如果沒有候選者,但有其他錯誤資訊,記錄下來
125
  operation_log += f"批次 {i+1}: 在回應中未找到任何候選者。完整回應: {str(response)}\n"
126
  continue
127
 
@@ -145,11 +156,13 @@ class NanoBananaImageGenerator:
145
  else:
146
  operation_log += f"批次 {i+1}: 未找到圖像。請檢視日誌了解詳細資訊。\n"
147
 
 
 
 
 
 
148
  except Exception as e:
149
- operation_log += f"批次 {i+1} 發生意外錯誤: {type(e).__name__} - {str(e)}\n"
150
- if 'response' in locals() and response is not None:
151
- operation_log += f"除錯: 錯誤時的完整回應 (嘗試轉換為字串): {str(response)}\n"
152
- operation_log += f"除錯: 回應物件類型: {type(response)}\n"
153
 
154
  return all_generated_images, operation_log
155
 
@@ -157,129 +170,180 @@ class NanoBananaImageGenerator:
157
  operation_log = f"API 呼叫錯誤: {type(e).__name__} - {str(e)}\n"
158
  return [], operation_log
159
 
160
- def generate_image(white_background_url, reference_image_url, prompt):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  image_generator = NanoBananaImageGenerator(api_key=GEMINI_API_KEY)
162
 
163
  if not GEMINI_API_KEY:
164
- return None, "錯誤: GEMINI_API_KEY 環境變數未設定。"
165
 
166
- encoded_images = []
167
- wb_image = image_generator._load_image_from_url(white_background_url)
168
- if wb_image:
169
- encoded_images.append(image_generator._image_to_base64(wb_image))
170
-
171
- ref_image = image_generator._load_image_from_url(reference_image_url)
172
- if ref_image:
173
- encoded_images.append(image_generator._image_to_base64(ref_image))
174
-
175
- if not encoded_images:
176
- return None, "錯誤: 無法從提供的 URL 載入任何圖片。"
177
-
178
- has_references = len(encoded_images) > 0
179
- final_prompt = image_generator.build_prompt_for_operation(
180
- prompt, operation="generate", has_references=has_references, aspect_ratio="1:1", character_consistency=True
181
- )
182
-
183
- generated_images_binary, operation_log = image_generator.call_nano_banana_api(
184
- final_prompt, encoded_images, batch_count=1
185
- )
186
-
187
- if generated_images_binary:
188
- output_dir = "generated_images"
189
- os.makedirs(output_dir, exist_ok=True)
190
- output_path = os.path.join(output_dir, f"generated_{len(os.listdir(output_dir)) + 1}.png")
191
- with open(output_path, "wb") as f:
192
- f.write(generated_images_binary[0])
193
- return output_path, operation_log
194
- else:
195
- return None, operation_log
196
-
197
- def read_google_sheet(sheet_url):
198
  try:
199
- def build_csv_url(url: str) -> str:
200
- parsed = urlparse(url)
201
- path_parts = parsed.path.strip("/").split("/")
202
- doc_id = None
203
- if len(path_parts) >= 3 and path_parts[0] == "spreadsheets" and path_parts[1] == "d":
204
- doc_id = path_parts[2]
205
- qs_gid = parse_qs(parsed.query).get("gid", [None])[0]
206
- frag_gid = None
207
- if parsed.fragment:
208
- frag_qs = parse_qs(parsed.fragment)
209
- frag_gid = frag_qs.get("gid", [None])[0]
210
- gid = qs_gid or frag_gid or "0"
211
- if doc_id:
212
- return f"https://docs.google.com/spreadsheets/d/{doc_id}/export?format=csv&gid={gid}"
213
- if "/export" in parsed.path and "format=csv" in parsed.query:
214
- return url
215
- return url.replace("/edit#gid=0", "/export?format=csv&gid=0")
216
- csv_url = build_csv_url(sheet_url)
217
- print(f"Attempting to read CSV from: {csv_url}")
218
- df = pd.read_csv(csv_url, engine='python', on_bad_lines='warn')
219
- print("Successfully read Google Sheet.")
220
- return df
221
- except Exception as e:
222
- print(f"Error reading Google Sheet: {e}")
223
- raise gr.Error(f"Error reading Google Sheet: {e}")
224
 
225
- def process_sheet_data(sheet_url):
226
- try:
227
- df = read_google_sheet(sheet_url)
228
- if df.shape[1] < 3:
229
- error_msg = f"Error: Google Sheet has only {df.shape[1]} columns, but 3 are expected (White Background URL, Reference Image URL, Prompt)."
230
- print(error_msg)
231
- raise gr.Error(error_msg)
232
- white_background_urls = df.iloc[:, 0].tolist()
233
- reference_image_urls = df.iloc[:, 1].tolist()
234
- prompts = df.iloc[:, 2].tolist()
235
- data = []
236
- for i, (wb, ref, p) in enumerate(zip(white_background_urls, reference_image_urls, prompts)):
237
- if pd.notna(wb) and pd.notna(ref) and pd.notna(p):
238
- data.append([i, wb, ref, p])
239
- print(f"Processed {len(data)} valid rows.")
240
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  except Exception as e:
242
- print(f"Error processing sheet data: {e}")
243
- raise gr.Error(f"Error processing sheet data: {e}")
244
-
245
- def generate_image_for_row(row_index, dataframe_data):
246
- if not isinstance(dataframe_data, pd.DataFrame) or not (0 <= row_index < len(dataframe_data)):
247
- return None, "Error: Invalid row index or dataframe data not loaded."
248
- row = dataframe_data.iloc[row_index]
249
- white_background_url = row.iloc[1]
250
- reference_image_url = row.iloc[2]
251
- prompt = row.iloc[3]
252
- return generate_image(white_background_url, reference_image_url, prompt)
253
 
254
  if __name__ == "__main__":
255
  with gr.Blocks() as demo:
256
  gr.Markdown("# AutoLS Gradio Image Generator")
257
- gr.Markdown("Enter the Google Sheet URL to process image generation requests.")
258
- sheet_url_input = gr.Textbox(label="Google Sheet URL", value="https://docs.google.com/spreadsheets/d/1G3olHxydDIbnyXdh5nnw5TG0akZFeMeYm-25JmCGDLg/edit?gid=0#gid=0")
259
- process_button = gr.Button("Process Sheet")
260
- processed_df_state = gr.State()
261
- output_dataframe = gr.DataFrame(
262
- headers=["Index", "白背圖URL", "參考圖URL", "提示詞"],
263
- col_count=(4, "fixed"),
264
- interactive=False
265
- )
266
- with gr.Row():
267
- row_index_input = gr.Number(label="Row Index to Generate", precision=0, value=0)
268
- generate_selected_button = gr.Button("Generate Image for Selected Row")
269
- generated_image_output = gr.Image(label="Generated Image")
270
- operation_log_output = gr.Textbox(label="Operation Log", lines=5)
271
- process_button.click(
272
- fn=process_sheet_data,
273
- inputs=sheet_url_input,
274
- outputs=output_dataframe
275
- ).success(
276
- fn=lambda x: x,
277
- inputs=output_dataframe,
278
- outputs=processed_df_state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  )
280
- generate_selected_button.click(
281
- fn=generate_image_for_row,
282
- inputs=[row_index_input, processed_df_state],
283
- outputs=[generated_image_output, operation_log_output]
284
  )
 
 
 
 
 
 
 
 
 
 
 
285
  demo.launch()
 
29
  if not GEMINI_API_KEY:
30
  raise ValueError("ERROR: GEMINI_API_KEY environment variable is not set. Please set it correctly in your Hugging Face Space settings.")
31
 
32
+ # NOTE: configure() is now imported and called inside NanoBananaImageGenerator.__init__
33
+
34
+ # Initialize Gemini API client (now done after placeholder check)
35
+ # from google.generativeai import configure # Removed as configure is now inside class
36
+ # configure(api_key=GEMINI_API_KEY) # Removed as configure is now inside class
37
+ # print(f"Debug: configure() called with API key...") # Removed as debug is now inside class
38
+
39
+ #
40
+ # Helper class and methods
41
+ #
42
  class NanoBananaImageGenerator:
43
  def __init__(self, api_key):
44
  self.api_key = api_key
 
51
  img_bytes = img_byte_arr.getvalue()
52
  return {"inline_data": {"mime_type": "image/png", "data": base64.b64encode(img_bytes).decode('utf-8')}}
53
 
54
+ def _download_image_from_url(self, url):
55
  try:
56
+ print(f"Debug: Attempting to download image from URL: {url}")
57
+ response = requests.get(url, timeout=10)
58
  response.raise_for_status()
59
+ image = Image.open(BytesIO(response.content))
60
+ print(f"Debug: Successfully downloaded image. Size: {image.size}")
61
+ return image
62
+ except requests.exceptions.RequestException as e:
63
+ print(f"Error downloading image from {url}: {e}")
64
  return None
65
 
66
  def build_prompt_for_operation(self, prompt, operation="generate", has_references=False, aspect_ratio="1:1", character_consistency=True):
 
116
  for i in range(batch_count):
117
  try:
118
  print(f"Debug: Batch {i+1} - Attempting to call Gemini API.")
119
+ # Using the gemini-2.5-flash-image-preview model as in the original code
120
  response = GenerativeModel("gemini-2.5-flash-image-preview").generate_content(
121
  contents=content_parts,
122
  generation_config=generation_config
 
128
 
129
  batch_images = []
130
 
 
131
  if hasattr(response, 'prompt_feedback') and response.prompt_feedback.safety_ratings:
132
  operation_log += f"提示詞因安全政策被拒絕: {response.prompt_feedback.safety_ratings}\n"
133
  continue
134
 
135
  if not hasattr(response, 'candidates') or not response.candidates:
 
136
  operation_log += f"批次 {i+1}: 在回應中未找到任何候選者。完整回應: {str(response)}\n"
137
  continue
138
 
 
156
  else:
157
  operation_log += f"批次 {i+1}: 未找到圖像。請檢視日誌了解詳細資訊。\n"
158
 
159
+ except KeyError as e:
160
+ if str(e) == "'Text'":
161
+ operation_log += f"批次 {i+1} 錯誤: Gemini API 返回了意外的回應結構。影像生成可能因安全政策、無效輸入或內部 API 問題而失敗。原始錯誤: {type(e).__name__} - {str(e)}\n"
162
+ else:
163
+ raise e
164
  except Exception as e:
165
+ operation_log += f"批次 {i+1} 意外錯誤: {type(e).__name__} - {str(e)}\n"
 
 
 
166
 
167
  return all_generated_images, operation_log
168
 
 
170
  operation_log = f"API 呼叫錯誤: {type(e).__name__} - {str(e)}\n"
171
  return [], operation_log
172
 
173
+ def get_google_sheet_csv_url(sheet_url):
174
+ try:
175
+ parsed_url = urlparse(sheet_url)
176
+ sheet_id = parsed_url.path.split('/')[3]
177
+ query_params = parse_qs(parsed_url.query)
178
+ gid = query_params.get('gid', ['0'])[0]
179
+ csv_url = f"https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=csv&gid={gid}"
180
+ return csv_url
181
+ except Exception as e:
182
+ print(f"Error parsing Google Sheet URL: {e}")
183
+ return None
184
+
185
+ def process_sheet_data(sheet_url):
186
+ print(f"Attempting to read CSV from: {sheet_url}")
187
+ try:
188
+ csv_url = get_google_sheet_csv_url(sheet_url)
189
+ df = pd.read_csv(csv_url)
190
+ df = df.fillna('')
191
+ df['Index'] = range(len(df))
192
+ df = df[['Index', '白背圖URL', '參考圖URL', '提示詞']]
193
+ print("Successfully read Google Sheet and created DataFrame.")
194
+ return df, "Google Sheet 載入成功,請選擇行數並點擊『生成圖片』。"
195
+ except Exception as e:
196
+ error_message = f"載入 Google Sheet 失敗: {e}"
197
+ print(error_message)
198
+ return pd.DataFrame(columns=['Index', '白背圖URL', '參考圖URL', '提示詞']), error_message
199
+
200
+ def generate_image_for_row(processed_df_state, row_index):
201
+ operation_log = "開始處理圖片生成...\n"
202
  image_generator = NanoBananaImageGenerator(api_key=GEMINI_API_KEY)
203
 
204
  if not GEMINI_API_KEY:
205
+ return None, operation_log + "錯誤: GEMINI_API_KEY 環境變數未設定。\n"
206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  try:
208
+ # Get the row from the DataFrame based on the selected index
209
+ row = processed_df_state.iloc[row_index]
210
+ white_background_url = row['白背圖URL']
211
+ reference_image_url = row['參考圖URL']
212
+ prompt = row['提示詞']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
+ operation_log += f"正在處理第 {row_index} 行: 白背圖URL: {white_background_url}, 參考圖URL: {reference_image_url}, 提示詞: {prompt}\n"
215
+
216
+ encoded_images = []
217
+ if white_background_url:
218
+ white_background_img = image_generator._download_image_from_url(white_background_url)
219
+ if white_background_img:
220
+ encoded_images.append(image_generator._image_to_base64(white_background_img))
221
+ else:
222
+ operation_log += f"錯誤: 無法下載白背圖: {white_background_url}\n"
223
+
224
+ if reference_image_url:
225
+ reference_img = image_generator._download_image_from_url(reference_image_url)
226
+ if reference_img:
227
+ encoded_images.append(image_generator._image_to_base64(reference_img))
228
+ else:
229
+ operation_log += f"錯誤: 無法下載參考圖: {reference_image_url}\n"
230
+
231
+ if not encoded_images:
232
+ return None, operation_log + "錯誤: 請確認 Google Sheet 上的圖片 URL 是否有效,並至少提供一張圖片。\n"
233
+
234
+ has_references = len(encoded_images) > 0
235
+ final_prompt = image_generator.build_prompt_for_operation(
236
+ prompt, operation="generate", has_references=has_references, aspect_ratio="1:1", character_consistency=True
237
+ )
238
+
239
+ generated_images_binary, api_log = image_generator.call_nano_banana_api(
240
+ final_prompt, encoded_images, batch_count=1
241
+ )
242
+ operation_log += api_log
243
+
244
+ if generated_images_binary:
245
+ output_dir = "generated_images"
246
+ os.makedirs(output_dir, exist_ok=True)
247
+ output_path = os.path.join(output_dir, f"generated_{len(os.listdir(output_dir)) + 1}.png")
248
+ with open(output_path, "wb") as f:
249
+ f.write(generated_images_binary[0])
250
+ operation_log += "圖片成功生成並儲存。\n"
251
+ return output_path, operation_log
252
+ else:
253
+ return None, operation_log + "圖片生成失敗。\n"
254
+
255
+ except IndexError:
256
+ return None, operation_log + f"錯誤: 找不到索引 {row_index} 的行。請檢查您輸入的行數是否正確。\n"
257
  except Exception as e:
258
+ return None, operation_log + f"處理第 {row_index} 行時發生意外錯誤: {type(e).__name__} - {str(e)}\n"
 
 
 
 
 
 
 
 
 
 
259
 
260
  if __name__ == "__main__":
261
  with gr.Blocks() as demo:
262
  gr.Markdown("# AutoLS Gradio Image Generator")
263
+ gr.Markdown("請輸入 Google Sheet URL 並點擊『處理表格』,或直接上傳圖片並輸入提示詞。")
264
+
265
+ # Google Sheet Interface
266
+ with gr.Tab("使用 Google Sheet"):
267
+ sheet_url_input = gr.Textbox(label="Google Sheet URL", value="https://docs.google.com/spreadsheets/d/1G3olHxydDIbnyXdh5nnw5TG0akZFeMeYm-25JmCGDLg/edit?gid=0#gid=0")
268
+ process_button = gr.Button("處理表格")
269
+ processed_df_state = gr.State()
270
+ output_dataframe = gr.DataFrame(
271
+ headers=["Index", "白背圖URL", "參考圖URL", "提示詞"],
272
+ col_count=(4, "fixed"),
273
+ interactive=False
274
+ )
275
+ with gr.Row():
276
+ row_index_input = gr.Number(label="要生成的行索引", precision=0, value=0)
277
+ generate_selected_button = gr.Button("為選定行生成圖片")
278
+
279
+ process_button.click(
280
+ fn=process_sheet_data,
281
+ inputs=sheet_url_input,
282
+ outputs=[output_dataframe, gr.Textbox(label="操作日誌", lines=5)]
283
+ ).success(
284
+ fn=lambda df, log: df,
285
+ inputs=[output_dataframe, gr.Textbox(label="操作日誌", lines=5)],
286
+ outputs=processed_df_state
287
+ )
288
+
289
+ generate_selected_button.click(
290
+ fn=generate_image_for_row,
291
+ inputs=[processed_df_state, row_index_input],
292
+ outputs=[gr.Image(label="生成的圖片"), gr.Textbox(label="操作日誌", lines=5)]
293
+ )
294
+
295
+ # Direct Image Upload Interface
296
+ with gr.Tab("直接上傳圖片"):
297
+ with gr.Row():
298
+ white_background_input = gr.Image(type="numpy", label="上傳白背圖")
299
+ reference_image_input = gr.Image(type="numpy", label="上傳參考圖")
300
+
301
+ prompt_input = gr.Textbox(label="提示詞", placeholder="例如:加上一個舒適的木製椅子。")
302
+ generate_upload_button = gr.Button("生成圖片")
303
+
304
+ generated_image_output_upload = gr.Image(label="生成的圖片")
305
+ operation_log_output_upload = gr.Textbox(label="操作日誌", lines=5)
306
+
307
+ generate_upload_button.click(
308
+ fn=generate_image_from_uploads,
309
+ inputs=[white_background_input, reference_image_input, prompt_input],
310
+ outputs=[generated_image_output_upload, operation_log_output_upload]
311
+ )
312
+
313
+ def generate_image_from_uploads(white_background_img, reference_img, prompt):
314
+ image_generator = NanoBananaImageGenerator(api_key=GEMINI_API_KEY)
315
+
316
+ if not GEMINI_API_KEY:
317
+ return None, "錯誤: GEMINI_API_KEY 環境變數未設定。"
318
+
319
+ encoded_images = []
320
+
321
+ if white_background_img is not None:
322
+ encoded_images.append(image_generator._image_to_base64(Image.fromarray(white_background_img).convert("RGB")))
323
+
324
+ if reference_img is not None:
325
+ encoded_images.append(image_generator._image_to_base64(Image.fromarray(reference_img).convert("RGB")))
326
+
327
+ if not encoded_images:
328
+ return None, "錯誤: 請上傳至少一張圖片。"
329
+
330
+ has_references = len(encoded_images) > 0
331
+ final_prompt = image_generator.build_prompt_for_operation(
332
+ prompt, operation="generate", has_references=has_references, aspect_ratio="1:1", character_consistency=True
333
  )
334
+
335
+ generated_images_binary, operation_log = image_generator.call_nano_banana_api(
336
+ final_prompt, encoded_images, batch_count=1
 
337
  )
338
+
339
+ if generated_images_binary:
340
+ output_dir = "generated_images"
341
+ os.makedirs(output_dir, exist_ok=True)
342
+ output_path = os.path.join(output_dir, f"generated_{len(os.listdir(output_dir)) + 1}.png")
343
+ with open(output_path, "wb") as f:
344
+ f.write(generated_images_binary[0])
345
+ return output_path, operation_log
346
+ else:
347
+ return None, operation_log
348
+
349
  demo.launch()