davidlee831117 commited on
Commit
af5aa6e
·
verified ·
1 Parent(s): f89aa16

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -160
app.py CHANGED
@@ -1,33 +1,11 @@
1
  import gradio as gr
2
  import pandas as pd
3
  import os
4
- import json
5
- import base64
6
  import requests
7
  from io import BytesIO
8
  from PIL import Image
9
  from urllib.parse import urlparse, parse_qs
10
- from google.cloud import aiplatform
11
- from google.api_core.client_options import ClientOptions
12
- from google.oauth2 import service_account
13
-
14
- # 由於您的環境可能沒有服務帳號金鑰,我們將直接使用 GEMINI_API_KEY
15
- # 如果在 Hugging Face Space 中,這通常會從環境變數中自動加載
16
-
17
- # 初始化 AI Platform 服務客戶端
18
- def initialize_ai_platform(api_key):
19
- # 請替換為您的 GCP 專案 ID 和區域
20
- PROJECT_ID = "YOUR_GCP_PROJECT_ID" # 這裡需要您提供真實的GCP專案ID
21
- LOCATION = "us-central1"
22
-
23
- # 這裡的認證方式取決於您的環境。
24
- # 創建一個不帶服務帳號憑證的客戶端,這會讓它嘗試從環境中自動尋找憑證
25
- try:
26
- aiplatform.init(project=PROJECT_ID, location=LOCATION)
27
- return True
28
- except Exception as e:
29
- print(f"Error initializing AI Platform: {e}")
30
- return False
31
 
32
  # 全局變數來儲存 API 金鑰
33
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
@@ -43,144 +21,106 @@ print(f"Debug: Top-level Loaded GEMINI_API_KEY (first 5 chars): {GEMINI_API_KEY[
43
  if not GEMINI_API_KEY:
44
  raise ValueError("ERROR: GEMINI_API_KEY environment variable is not set. Please set it correctly.")
45
 
46
- # 這裡我們將創建一個通用客戶端來處理 API 呼叫
47
- # 由於 'google-cloud-aiplatform' 函式庫主要設計用於 GCP 環境,我們將使用 `requests`
48
- # 來模擬直接呼叫 API,以適應您在 Hugging Face Space 的需求。
49
- class NanoBananaImageGenerator:
50
- def __init__(self, api_key):
51
- self.api_key = api_key
52
- self.endpoint_url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image-preview:generateContent"
53
-
54
- def _load_image_from_url(self, url):
55
- # 圖片下載邏輯與之前相同,以確保兼容性
56
- try:
57
- headers = {'User-Agent': 'Mozilla/5.0'}
58
- response = requests.get(url, timeout=20, headers=headers)
59
- response.raise_for_status()
60
- image = Image.open(BytesIO(response.content)).convert("RGB")
61
- return image
62
- except requests.exceptions.HTTPError as e:
63
- print(f"Error downloading image from {url}: HTTP Error {e.response.status_code}")
64
- return None
65
- except Exception as e:
66
- print(f"An unexpected error occurred: {e}")
67
- return None
68
 
69
- def _image_to_base64(self, pil_image):
70
- img_byte_arr = BytesIO()
71
- pil_image.save(img_byte_arr, format='PNG')
72
- img_bytes = img_byte_arr.getvalue()
73
- return base64.b64encode(img_bytes).decode('utf-8')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- def build_prompt_for_operation(self, prompt, has_references=False, aspect_ratio="1:1"):
76
- # 提示詞生成邏輯與之前相同
77
- aspect_instructions = {
78
- "1:1": "square format",
79
- "16:9": "widescreen landscape format",
80
- "9:16": "portrait format",
81
- "4:3": "standard landscape format",
82
- "3:4": "standard portrait format"
83
- }
84
- base_quality = "Generate a high-quality, photorealistic image"
85
- format_instruction = f"in {aspect_instructions.get(aspect_ratio, 'square format')}"
86
 
87
- final_prompt = f"{base_quality} inspired by the style and elements of the reference images. {prompt}. {format_instruction}."
88
- if not has_references:
89
- final_prompt = f"{base_quality} of: {prompt}. {format_instruction}."
90
 
91
- return final_prompt
 
 
 
 
 
 
92
 
93
- def call_nano_banana_api(self, prompt, images, temperature=0.7):
94
- operation_log = ""
95
- try:
96
- # 構建請求主體
97
- contents = [
98
- {"text": prompt}
99
- ]
100
- for img in images:
101
- contents.append({
102
- "inline_data": {
103
- "mime_type": "image/png",
104
- "data": self._image_to_base64(img)
105
- }
106
- })
107
-
108
- payload = {
109
- "contents": {"parts": contents},
110
- "generation_config": {
111
- "temperature": temperature,
112
- "response_modalities": ["Text", "Image"]
113
- }
114
- }
115
-
116
- headers = {
117
- "Content-Type": "application/json",
118
- "x-goog-api-key": self.api_key
119
- }
120
-
121
- print(f"Debug: Attempting to call Gemini API via HTTP request.")
122
- response = requests.post(self.endpoint_url, headers=headers, json=payload, timeout=60)
123
- response.raise_for_status()
124
-
125
- api_response = response.json()
126
- print(f"Debug: Full API Response: {json.dumps(api_response, indent=2)}")
127
-
128
- # 解析回應
129
- candidates = api_response.get('candidates', [])
130
- if not candidates:
131
- safety_ratings = api_response.get('prompt_feedback', {}).get('safety_ratings', [])
132
- if safety_ratings:
133
- operation_log += f"API 被安全政策阻止。原因:{safety_ratings}\n"
134
- else:
135
- operation_log += "API 回應中未找到候選者,可能的原因是內部錯誤或無效請求。\n"
136
- return [], operation_log
137
-
138
- image_parts = []
139
- for candidate in candidates:
140
- if 'content' in candidate and 'parts' in candidate['content']:
141
- for part in candidate['content']['parts']:
142
- if 'inline_data' in part and 'data' in part['inline_data']:
143
- image_parts.append(base64.b64decode(part['inline_data']['data']))
144
- elif 'text' in part:
145
- operation_log += f"API 回應文字:{part['text']}\n"
146
-
147
- if not image_parts:
148
- operation_log += "API 回應沒有包含任何圖像數據,請檢查輸入內容是否違反安全政策。\n"
149
- else:
150
- operation_log += f"成功生成 {len(image_parts)} 張圖片。\n"
151
-
152
  return image_parts, operation_log
153
-
154
- except requests.exceptions.HTTPError as e:
155
- operation_log = f"API 呼叫失敗,HTTP Error: {e.response.status_code} - {e.response.text}\n"
156
- return [], operation_log
157
- except Exception as e:
158
- operation_log = f"意外錯誤: {type(e).__name__} - {str(e)}\n"
159
- return [], operation_log
 
 
 
 
 
160
 
161
  def generate_image(white_background_url, reference_image_url, prompt):
162
- image_generator = NanoBananaImageGenerator(api_key=GEMINI_API_KEY)
163
-
164
  if not GEMINI_API_KEY:
165
  return None, "Error: GEMINI_API_KEY is not set."
166
 
167
- wb_image = image_generator._load_image_from_url(white_background_url)
168
- ref_image = image_generator._load_image_from_url(reference_image_url)
169
 
170
- if not wb_image:
171
- return None, "Error: Failed to load white background image from URL."
172
- if not ref_image:
173
- return None, "Error: Failed to load reference image from URL."
174
 
175
  images = [wb_image, ref_image]
176
- final_prompt = image_generator.build_prompt_for_operation(prompt, has_references=True)
177
 
178
- generated_images_binary, operation_log = image_generator.call_nano_banana_api(final_prompt, images)
179
 
180
  if generated_images_binary:
181
  output_dir = "generated_images"
182
  os.makedirs(output_dir, exist_ok=True)
183
- output_path = os.path.join(output_dir, f"generated_{len(os.listdir(output_dir)) + 1}.png")
 
 
184
  with open(output_path, "wb") as f:
185
  f.write(generated_images_binary[0])
186
  return output_path, operation_log
@@ -188,6 +128,7 @@ def generate_image(white_background_url, reference_image_url, prompt):
188
  return None, operation_log
189
 
190
  def read_google_sheet(sheet_url):
 
191
  try:
192
  def build_csv_url(url: str) -> str:
193
  parsed = urlparse(url)
@@ -214,6 +155,7 @@ def read_google_sheet(sheet_url):
214
  raise gr.Error(f"Error reading Google Sheet: {e}")
215
 
216
  def process_sheet_data(sheet_url):
 
217
  try:
218
  df = read_google_sheet(sheet_url)
219
 
@@ -221,33 +163,35 @@ def process_sheet_data(sheet_url):
221
  error_msg = f"Error: Google Sheet has only {df.shape[1]} columns, but 3 are expected (White Background URL, Reference Image URL, Prompt)."
222
  raise gr.Error(error_msg)
223
 
224
- white_background_urls = df.iloc[:, 0].tolist()
225
- reference_image_urls = df.iloc[:, 1].tolist()
226
- prompts = df.iloc[:, 2].tolist()
227
-
228
- data = []
229
- for i, (wb, ref, p) in enumerate(zip(white_background_urls, reference_image_urls, prompts)):
230
- if pd.notna(wb) and pd.notna(ref) and pd.notna(p):
231
- data.append([i, wb, ref, p])
232
 
233
- return data
234
  except Exception as e:
235
  raise gr.Error(f"Error processing sheet data: {e}")
236
 
237
  def generate_image_for_row(row_index, dataframe_data):
238
- if not (0 <= row_index < len(dataframe_data)):
 
 
 
 
239
  return None, "Error: Invalid row index."
240
 
241
- row = dataframe_data.iloc[row_index]
242
- white_background_url = row.iloc[1]
243
- reference_image_url = row.iloc[2]
244
- prompt = row.iloc[3]
245
 
246
  return generate_image(white_background_url, reference_image_url, prompt)
247
 
248
  if __name__ == "__main__":
249
  with gr.Blocks() as demo:
250
- gr.Markdown("# AutoLS Gradio Image Generator (Powered by AI Platform)")
251
  gr.Markdown("輸入 Google Sheet 網址來處理圖像生成請求。")
252
 
253
  sheet_url_input = gr.Textbox(label="Google Sheet URL", value="https://docs.google.com/spreadsheets/d/1G3olHxydDIbnyXdh5nnw5TG0akZFeMeYm-25JmCGDLg/edit?gid=0#gid=0")
@@ -273,14 +217,15 @@ if __name__ == "__main__":
273
  inputs=sheet_url_input,
274
  outputs=output_dataframe
275
  ).success(
276
- fn=lambda x: pd.DataFrame(x, columns=["Index", "白背圖URL", "參考圖URL", "提示詞"]),
277
  inputs=output_dataframe,
278
  outputs=processed_df_state
279
  )
280
-
 
281
  generate_selected_button.click(
282
  fn=generate_image_for_row,
283
- inputs=[row_index_input, processed_df_state],
284
  outputs=[generated_image_output, operation_log_output]
285
  )
286
 
 
1
  import gradio as gr
2
  import pandas as pd
3
  import os
 
 
4
  import requests
5
  from io import BytesIO
6
  from PIL import Image
7
  from urllib.parse import urlparse, parse_qs
8
+ import google.generativeai as genai
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  # 全局變數來儲存 API 金鑰
11
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
 
21
  if not GEMINI_API_KEY:
22
  raise ValueError("ERROR: GEMINI_API_KEY environment variable is not set. Please set it correctly.")
23
 
24
+ # 配置 Gemini API
25
+ genai.configure(api_key=GEMINI_API_KEY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ def load_image_from_url(url: str):
28
+ """從 URL 下載圖片並以 PIL Image 格式回傳。"""
29
+ try:
30
+ headers = {'User-Agent': 'Mozilla/5.0'}
31
+ response = requests.get(url, timeout=20, headers=headers)
32
+ response.raise_for_status() # 檢查請求是否成功
33
+ image = Image.open(BytesIO(response.content)).convert("RGB")
34
+ print(f"Debug: Successfully loaded image from URL: {url}")
35
+ return image
36
+ except requests.exceptions.HTTPError as e:
37
+ print(f"Error downloading image from {url}: HTTP Error {e.response.status_code}")
38
+ return None
39
+ except Exception as e:
40
+ print(f"An unexpected error occurred: {e}")
41
+ return None
42
+
43
+ def build_prompt_for_operation(prompt, has_references=False, aspect_ratio="1:1"):
44
+ """根據輸入構建完整的提示詞。"""
45
+ aspect_instructions = {
46
+ "1:1": "square format",
47
+ "16:9": "widescreen landscape format",
48
+ "9:16": "portrait format",
49
+ "4:3": "standard landscape format",
50
+ "3:4": "standard portrait format"
51
+ }
52
+ base_quality = "Generate a high-quality, photorealistic image"
53
+ format_instruction = f"in {aspect_instructions.get(aspect_ratio, 'square format')}"
54
+
55
+ final_prompt = f"{base_quality} inspired by the style and elements of the reference images. {prompt}. {format_instruction}."
56
+ if not has_references:
57
+ final_prompt = f"{base_quality} of: {prompt}. {format_instruction}."
58
+
59
+ return final_prompt
60
 
61
+ def call_gemini_api(prompt, images):
62
+ """使用官方函式庫呼叫 Gemini API。"""
63
+ operation_log = ""
64
+ try:
65
+ # 使用官方 genai.generate_content 函式
66
+ response = genai.generate_content(
67
+ contents=[prompt] + images, # 圖片列表直接作為輸入
68
+ model="gemini-1.5-pro-latest" # 使用官方推薦的多模態模型
69
+ )
 
 
70
 
71
+ print(f"Debug: Full API Response: {response.text}")
 
 
72
 
73
+ # 檢查是否有安全政策問題
74
+ if 'prompt_feedback' in response:
75
+ if 'safety_ratings' in response['prompt_feedback']:
76
+ for rating in response['prompt_feedback']['safety_ratings']:
77
+ if rating['block_reason'] != 'NONE':
78
+ operation_log += f"API 被安全政策阻止。原因:{rating['block_reason']}\n"
79
+ return None, operation_log
80
 
81
+ # 處理回應
82
+ if response.text is not None and "data:image" in response.text:
83
+ # 這是內嵌的圖片 Base64 字串,需要解碼
84
+ base64_string = response.text.split(',')[1]
85
+ image_data = base64.b64decode(base64_string)
86
+ image_parts = [image_data]
87
+ operation_log += f"成功生成 {len(image_parts)} 張圖片。\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  return image_parts, operation_log
89
+ elif response.text is not None:
90
+ # 如果回傳的是文字
91
+ operation_log += f"API 回應文字:{response.text}\n"
92
+ return None, operation_log
93
+ else:
94
+ # 沒有任何回傳
95
+ operation_log += "API 回應沒有包含任何圖像或文字數據。\n"
96
+ return None, operation_log
97
+
98
+ except Exception as e:
99
+ operation_log = f"意外錯誤: {type(e).__name__} - {str(e)}\n"
100
+ return None, operation_log
101
 
102
  def generate_image(white_background_url, reference_image_url, prompt):
103
+ """Gradio 介面呼叫的主函式。"""
 
104
  if not GEMINI_API_KEY:
105
  return None, "Error: GEMINI_API_KEY is not set."
106
 
107
+ wb_image = load_image_from_url(white_background_url)
108
+ ref_image = load_image_from_url(reference_image_url)
109
 
110
+ if wb_image is None or ref_image is None:
111
+ return None, "Error: One or more images failed to load from URL."
 
 
112
 
113
  images = [wb_image, ref_image]
114
+ final_prompt = build_prompt_for_operation(prompt, has_references=True)
115
 
116
+ generated_images_binary, operation_log = call_gemini_api(final_prompt, images)
117
 
118
  if generated_images_binary:
119
  output_dir = "generated_images"
120
  os.makedirs(output_dir, exist_ok=True)
121
+ # 使用時間戳或唯一ID來確保檔名唯一
122
+ import time
123
+ output_path = os.path.join(output_dir, f"generated_{int(time.time())}.png")
124
  with open(output_path, "wb") as f:
125
  f.write(generated_images_binary[0])
126
  return output_path, operation_log
 
128
  return None, operation_log
129
 
130
  def read_google_sheet(sheet_url):
131
+ """從 Google Sheet 讀取資料。"""
132
  try:
133
  def build_csv_url(url: str) -> str:
134
  parsed = urlparse(url)
 
155
  raise gr.Error(f"Error reading Google Sheet: {e}")
156
 
157
  def process_sheet_data(sheet_url):
158
+ """處理試算表資料,為 Gradio DataFrame 準備。"""
159
  try:
160
  df = read_google_sheet(sheet_url)
161
 
 
163
  error_msg = f"Error: Google Sheet has only {df.shape[1]} columns, but 3 are expected (White Background URL, Reference Image URL, Prompt)."
164
  raise gr.Error(error_msg)
165
 
166
+ # 這裡使用 to_dict('records') 來處理,以確保後續 Pandas 處理時的兼容性
167
+ data = df.to_dict('records')
168
+ data_list = []
169
+ for i, row in enumerate(data):
170
+ if pd.notna(row.iloc[0]) and pd.notna(row.iloc[1]) and pd.notna(row.iloc[2]):
171
+ data_list.append([i, row.iloc[0], row.iloc[1], row.iloc[2]])
 
 
172
 
173
+ return data_list
174
  except Exception as e:
175
  raise gr.Error(f"Error processing sheet data: {e}")
176
 
177
  def generate_image_for_row(row_index, dataframe_data):
178
+ """根據 Gradio DataFrame 的行索引生成圖片。"""
179
+ # 將 Gradio 的 dataframe_data 轉換回 pandas DataFrame
180
+ df = pd.DataFrame(dataframe_data, columns=["Index", "白背圖URL", "參考圖URL", "提示詞"])
181
+
182
+ if not (0 <= row_index < len(df)):
183
  return None, "Error: Invalid row index."
184
 
185
+ row = df.iloc[int(row_index)]
186
+ white_background_url = row['白背圖URL']
187
+ reference_image_url = row['參考圖URL']
188
+ prompt = row['提示詞']
189
 
190
  return generate_image(white_background_url, reference_image_url, prompt)
191
 
192
  if __name__ == "__main__":
193
  with gr.Blocks() as demo:
194
+ gr.Markdown("# AutoLS Gradio Image Generator (Powered by Gemini API)")
195
  gr.Markdown("輸入 Google Sheet 網址來處理圖像生成請求。")
196
 
197
  sheet_url_input = gr.Textbox(label="Google Sheet URL", value="https://docs.google.com/spreadsheets/d/1G3olHxydDIbnyXdh5nnw5TG0akZFeMeYm-25JmCGDLg/edit?gid=0#gid=0")
 
217
  inputs=sheet_url_input,
218
  outputs=output_dataframe
219
  ).success(
220
+ fn=lambda x: x,
221
  inputs=output_dataframe,
222
  outputs=processed_df_state
223
  )
224
+
225
+ # 修正 click 觸發器,確保傳入正確的參數
226
  generate_selected_button.click(
227
  fn=generate_image_for_row,
228
+ inputs=[row_index_input, output_dataframe],
229
  outputs=[generated_image_output, operation_log_output]
230
  )
231