davidlee831117 commited on
Commit
a0a6c5e
·
verified ·
1 Parent(s): 8a6dbe2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +221 -107
app.py CHANGED
@@ -3,138 +3,252 @@ import os
3
  import time
4
  import uuid
5
  import tempfile
6
- from PIL import Image, ImageDraw, ImageFont
7
  import gradio as gr
8
- import base64
9
- import mimetypes
10
  from io import BytesIO
11
  from google import genai
12
- from google.genai import types
 
 
13
 
14
- def generate(text, images, api_key, model="gemini-2.5-flash-image-preview"):
15
- # Initialize client using provided api_key (or fallback to env variable)
16
- client = genai.Client(api_key=(api_key.strip() if api_key and api_key.strip() != ""
17
- else os.environ.get("GEMINI_API_KEY")))
18
 
19
- # Prepare contents with images first, then text
20
- contents = images + [text]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- response = client.models.generate_content(
23
- model=model,
24
- contents=contents,
25
- )
26
- text_response = ""
27
- image_path = None
28
-
29
- for part in response.candidates[0].content.parts:
30
- if part.text is not None:
31
- text_response += part.text + "\n"
32
- elif part.inline_data is not None:
33
- # Create a temporary file to store the generated image
34
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
35
- temp_path = tmp.name
36
- generated_image = Image.open(BytesIO(part.inline_data.data))
37
- generated_image.save(temp_path)
38
- image_path = temp_path
39
- print(f"Generated image saved to: {temp_path} with prompt: {text}")
40
- return image_path, text_response
41
-
42
- def load_uploaded_images(uploaded_files):
43
- """Load and display uploaded images immediately"""
44
- uploaded_images = []
45
- if uploaded_files:
46
- for file in uploaded_files:
47
- if file.name.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
48
- img = Image.open(file.name)
49
- if img.mode == "RGBA":
50
- img = img.convert("RGBA")
51
- uploaded_images.append(img)
52
- return uploaded_images
53
-
54
- def process_image_and_prompt(uploaded_files, prompt, gemini_api_key):
55
  try:
56
- input_text = prompt
57
- model = "gemini-2.5-flash-image-preview"
58
- # Load images from uploaded files
59
- images = []
60
- uploaded_images = []
61
- if uploaded_files:
62
- for file in uploaded_files:
63
- if file.name.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
64
- img = Image.open(file.name)
65
- if img.mode == "RGBA":
66
- img = img.convert("RGBA")
67
- images.append(img)
68
- uploaded_images.append(img)
69
- if not images:
70
- raise gr.Error("Please upload at least one image", duration=5)
71
- # Format: [dress_image, model_image, text_input] or [image1, image2, ..., text_input]
72
- image_path, text_response = generate(text=input_text, images=images, api_key=gemini_api_key, model=model)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  if image_path:
75
- # Load and convert the image if needed.
76
- result_img = Image.open(image_path)
77
- if result_img.mode == "RGBA":
78
- result_img = result_img.convert("RGBA")
79
- return uploaded_images, [result_img], "" # Return uploaded images, generated image, and empty text output.
80
  else:
81
- # Return uploaded images, no generated image, and the text response.
82
- return uploaded_images, None, text_response
 
 
 
 
83
  except Exception as e:
84
- raise gr.Error(f"Error Getting {e}", duration=5)
85
 
86
- # Build a Blocks-based interface with a custom HTML header and CSS
87
  with gr.Blocks() as demo:
88
- # Custom HTML header with proper class for styling
89
-
90
-
91
 
92
  with gr.Row(elem_classes="main-content"):
93
  with gr.Column(elem_classes="input-column"):
94
- image_input = gr.File(
95
- file_types=["image"],
96
- file_count="multiple",
97
- label="Upload Images ",
98
- elem_id="image-input",
99
- elem_classes="upload-box"
100
- )
101
  gemini_api_key = gr.Textbox(
102
  lines=1,
103
- placeholder="Enter Gemini API Key (optional)",
104
- label="Gemini API Key (optional)",
105
  elem_classes="api-key-input"
106
  )
107
- prompt_input = gr.Textbox(
108
- lines=2,
109
- placeholder="Enter prompt here...",
110
- label="Prompt",
111
- elem_classes="prompt-input"
112
  )
113
- submit_btn = gr.Button("Generate", elem_classes="generate-btn")
114
-
 
 
 
 
115
  with gr.Column(elem_classes="output-column"):
116
- uploaded_gallery = gr.Gallery(label="Uploaded Images", elem_classes="uploaded-gallery")
117
- output_gallery = gr.Gallery(label="Generated Outputs", elem_classes="output-gallery")
118
- output_text = gr.Textbox(
119
- label="Gemini Output",
120
- placeholder="Text response will appear here if no image is generated.",
121
- elem_classes="output-text"
 
 
 
 
 
122
  )
123
 
124
- # Set up the interaction with three outputs.
125
- submit_btn.click(
126
- fn=process_image_and_prompt,
127
- inputs=[image_input, prompt_input, gemini_api_key],
128
- outputs=[uploaded_gallery, output_gallery, output_text],
129
  )
130
 
131
- # Update uploaded gallery immediately when files are uploaded
132
- image_input.upload(
133
- fn=load_uploaded_images,
134
- inputs=[image_input],
135
- outputs=[uploaded_gallery],
136
  )
137
-
138
 
139
-
140
- demo.queue(max_size=50).launch(mcp_server=True, share=True)
 
3
  import time
4
  import uuid
5
  import tempfile
6
+ from PIL import Image
7
  import gradio as gr
 
 
8
  from io import BytesIO
9
  from google import genai
10
+ import gspread
11
+ from oauth2client.service_account import ServiceAccountCredentials
12
+ import requests
13
 
14
+ # 請將你的 Google Sheets 服務帳戶金鑰檔案命名為 "service_account.json" 並放在同一目錄
15
+ SERVICE_ACCOUNT_FILE = "service_account.json"
 
 
16
 
17
+ # --- Google Sheets 相關函式 ---
18
+ def get_sheet_data(spreadsheet_url):
19
+ """從 Google Sheet 讀取所有資料並返回 DataFrame 格式。"""
20
+ try:
21
+ scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
22
+ creds = ServiceAccountCredentials.from_json_keyfile_name(SERVICE_ACCOUNT_FILE, scope)
23
+ client = gspread.authorize(creds)
24
+
25
+ spreadsheet = client.open_by_url(spreadsheet_url)
26
+ worksheet = spreadsheet.worksheet("Sheet1")
27
+
28
+ list_of_lists = worksheet.get_all_values()
29
+
30
+ if not list_of_lists or len(list_of_lists) < 2:
31
+ return [], "試算表為空或沒有資料。"
32
+
33
+ headers = list_of_lists[0]
34
+ data = []
35
+ for i, row in enumerate(list_of_lists[1:], start=2):
36
+ if not any(row): # 跳過空行
37
+ continue
38
+ row_dict = dict(zip(headers, row))
39
+ data.append({
40
+ "Index": i,
41
+ "白背圖URL": row_dict.get("White_Back_Image_URL", ""),
42
+ "參考圖URL": row_dict.get("Ref_Image_URL", ""),
43
+ "提示詞": row_dict.get("Prompt", "")
44
+ })
45
+
46
+ dataframe_output = [[row['Index'], row['白背圖URL'], row['參考圖URL'], row['提示詞']] for row in data]
47
+
48
+ log_message = f"成功讀取 {len(data)} 筆數據。"
49
+ return dataframe_output, log_message
50
+
51
+ except FileNotFoundError:
52
+ raise gr.Error(f"錯誤:找不到金鑰檔案 '{SERVICE_ACCOUNT_FILE}'。請確認已正確放置。", duration=10)
53
+ except gspread.exceptions.SpreadsheetNotFound:
54
+ raise gr.Error(f"錯誤:Google Sheet not found at URL: {spreadsheet_url}", duration=10)
55
+ except Exception as e:
56
+ raise gr.Error(f"讀取試算表時發生錯誤: {e}", duration=10)
57
+
58
+ def get_row_data(spreadsheet_url, row_number):
59
+ """從 Google Sheet 讀取指定列的資料。"""
60
+ try:
61
+ scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
62
+ creds = ServiceAccountCredentials.from_json_keyfile_name(SERVICE_ACCOUNT_FILE, scope)
63
+ client = gspread.authorize(creds)
64
+
65
+ spreadsheet = client.open_by_url(spreadsheet_url)
66
+ worksheet = spreadsheet.worksheet("Sheet1")
67
+
68
+ headers = worksheet.row_values(1)
69
+ row_data = worksheet.row_values(row_number)
70
+
71
+ row_dict = dict(zip(headers, row_data))
72
+
73
+ white_back_image_url = row_dict.get("White_Back_Image_URL", "")
74
+ ref_image_url = row_dict.get("Ref_Image_URL", "")
75
+ prompt_text = row_dict.get("Prompt", "")
76
+
77
+ return white_back_image_url, ref_image_url, prompt_text
78
+
79
+ except FileNotFoundError:
80
+ raise gr.Error(f"錯誤:找不到金鑰檔案 '{SERVICE_ACCOUNT_FILE}'。", duration=10)
81
+ except gspread.exceptions.SpreadsheetNotFound:
82
+ raise gr.Error(f"錯誤:Google Sheet not found at URL: {spreadsheet_url}", duration=10)
83
+ except IndexError:
84
+ raise gr.Error(f"錯誤:指定的行數 {row_number} 不存在。", duration=10)
85
+ except Exception as e:
86
+ raise gr.Error(f"讀取指定行時發生錯誤: {e}", duration=10)
87
+
88
+ # --- 下載圖片函式 (已更新) ---
89
+ def load_image_from_url(url: str):
90
+ """
91
+ 從 URL 下載圖片並以 PIL Image 格式回傳,使用更穩健的請求標頭。
92
+ """
93
+ if not url:
94
+ return None
95
+ try:
96
+ # 使用更完整的標頭來模擬瀏覽器請求
97
+ headers = {
98
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
99
+ 'Referer': 'https://www.google.com/', # 模擬從 Google 搜尋點擊過來的
100
+ 'Accept-Encoding': 'gzip, deflate, br',
101
+ 'Accept-Language': 'en-US,en;q=0.9',
102
+ 'Connection': 'keep-alive',
103
+ }
104
+
105
+ response = requests.get(url, timeout=20, headers=headers)
106
+ response.raise_for_status() # 如果狀態碼是 4xx 或 5xx,將引發異常
107
+
108
+ image = Image.open(BytesIO(response.content)).convert("RGB")
109
+ print(f"Debug: Successfully loaded image from URL: {url}")
110
+ return image
111
+ except requests.exceptions.HTTPError as e:
112
+ print(f"Error downloading image from {url}: HTTP Error {e.response.status_code}")
113
+ gr.Warning(f"從 URL '{url}' 下載圖片失敗:HTTP 錯誤 {e.response.status_code}", duration=10)
114
+ return None
115
+ except Exception as e:
116
+ print(f"An unexpected error occurred: {e}")
117
+ gr.Warning(f"從 URL '{url}' 下載圖片時發生意外錯誤:{e}", duration=10)
118
+ return None
119
+
120
+ # --- Gemini 核心函式 ---
121
+ def generate_image(text, images, api_key, model="gemini-2.5-flash-image-preview"):
122
+ """使用 Gemini 模型生成圖片。"""
123
+ if not api_key or api_key.strip() == "":
124
+ raise gr.Error("錯誤:請輸入有效的 Gemini API 金鑰。", duration=10)
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  try:
127
+ client = genai.Client(api_key=api_key.strip())
128
+ contents = images + [text]
129
+ response = client.models.generate_content(
130
+ model=model,
131
+ contents=contents,
132
+ )
133
+ text_response = ""
134
+ image_path = None
135
+ for part in response.candidates[0].content.parts:
136
+ if part.text is not None:
137
+ text_response += part.text + "\n"
138
+ elif part.inline_data is not None:
139
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
140
+ temp_path = tmp.name
141
+ generated_image = Image.open(BytesIO(part.inline_data.data))
142
+ generated_image.save(temp_path)
143
+ image_path = temp_path
144
+ return image_path, text_response
145
+ except Exception as e:
146
+ raise gr.Error(f"Gemini API 呼叫失敗: {e}", duration=10)
147
+
148
+ # --- Gradio 互動函式 ---
149
+ def process_sheet_data(sheet_url):
150
+ """處理試算表,讀取所有數據並更新 DataFrame。"""
151
+ if not sheet_url:
152
+ raise gr.Error("請輸入 Google Sheet URL。", duration=5)
153
+
154
+ data, log_message = get_sheet_data(sheet_url)
155
+ return data, log_message
156
+
157
+ def generate_image_from_row(sheet_url, row_number, gemini_api_key):
158
+ """根據指定的行數,生成圖片。"""
159
+ if not sheet_url:
160
+ raise gr.Error("請先輸入 Google Sheet URL。", duration=5)
161
+ if not row_number or row_number <= 1:
162
+ raise gr.Error("請輸入有效的行數 (大於 1)。", duration=5)
163
+
164
+ try:
165
+ row_num = int(row_number)
166
+ white_back_url, ref_image_url, prompt_text = get_row_data(sheet_url, row_num)
167
+
168
+ log_message = f"開始處理第 {row_num} 行...\n"
169
+ log_message += f"白背圖URL: {white_back_url}\n"
170
+ log_message += f"參考圖URL: {ref_image_url}\n"
171
+ log_message += f"提示詞: {prompt_text}\n"
172
+
173
+ images_for_gemini = []
174
+
175
+ # 使用新的函式
176
+ if white_back_url:
177
+ wb_img = load_image_from_url(white_back_url)
178
+ if wb_img:
179
+ images_for_gemini.append(wb_img)
180
+ if ref_image_url:
181
+ ref_img = load_image_from_url(ref_image_url)
182
+ if ref_img:
183
+ images_for_gemini.append(ref_img)
184
+
185
+ if not images_for_gemini:
186
+ return None, "警告:無效的圖片 URL,無法生成圖片。"
187
+
188
+ log_message += "圖片已下載,開始呼叫 Gemini 模型...\n"
189
+
190
+ image_path, text_response = generate_image(text=prompt_text, images=images_for_gemini, api_key=gemini_api_key)
191
 
192
  if image_path:
193
+ log_message += "圖片生成成功!"
194
+ return image_path, log_message
 
 
 
195
  else:
196
+ log_message += "圖片生成失敗。\n"
197
+ log_message += f"Gemini 文字回應: {text_response}"
198
+ return None, log_message
199
+
200
+ except ValueError:
201
+ raise gr.Error("行數必須為數字。", duration=5)
202
  except Exception as e:
203
+ return None, f"生成圖片時發生錯誤: {e}"
204
 
205
+ # --- Gradio 介面設定 ---
206
  with gr.Blocks() as demo:
207
+ gr.Markdown("## Google Sheets 圖片生成器")
 
 
208
 
209
  with gr.Row(elem_classes="main-content"):
210
  with gr.Column(elem_classes="input-column"):
 
 
 
 
 
 
 
211
  gemini_api_key = gr.Textbox(
212
  lines=1,
213
+ placeholder="請在此輸入你的 Gemini API 金鑰",
214
+ label="Gemini API 金鑰",
215
  elem_classes="api-key-input"
216
  )
217
+ sheet_url_input = gr.Textbox(
218
+ label="Google Sheet URL",
219
+ value="https://docs.google.com/spreadsheets/d/1G3olHxydDIbnyXdh5nnw5TG0akZFeMeYm-25JmCGDLg/edit?gid=0#gid=0"
 
 
220
  )
221
+ process_button = gr.Button("處理試算表", elem_classes="generate-btn")
222
+
223
+ with gr.Row():
224
+ row_index_input = gr.Number(label="要生成的行數", precision=0, value=2)
225
+ generate_selected_button = gr.Button("生成所選行的圖片", elem_classes="generate-btn")
226
+
227
  with gr.Column(elem_classes="output-column"):
228
+ output_dataframe = gr.DataFrame(
229
+ headers=["Index", "白背圖URL", "參考圖URL", "提示詞"],
230
+ col_count=(4, "fixed"),
231
+ interactive=False,
232
+ label="已處理的試算表數據"
233
+ )
234
+ generated_image_output = gr.Image(label="生成的圖片", elem_classes="output-gallery")
235
+ operation_log_output = gr.Textbox(
236
+ label="操作日誌",
237
+ lines=10,
238
+ placeholder="文字回應和日誌會顯示在這裡。"
239
  )
240
 
241
+ # 按鈕的事件綁定
242
+ process_button.click(
243
+ fn=process_sheet_data,
244
+ inputs=[sheet_url_input],
245
+ outputs=[output_dataframe, operation_log_output]
246
  )
247
 
248
+ generate_selected_button.click(
249
+ fn=generate_image_from_row,
250
+ inputs=[sheet_url_input, row_index_input, gemini_api_key],
251
+ outputs=[generated_image_output, operation_log_output]
 
252
  )
 
253
 
254
+ demo.queue().launch(mcp_server=True, share=True)