davidlee831117 commited on
Commit
4ca74ac
·
verified ·
1 Parent(s): 8d0e513

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -126
app.py CHANGED
@@ -1,145 +1,106 @@
1
- import json
2
  import os
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
- # Hugging Face Spaces 上從 Secrets 讀取服務帳戶金鑰
15
- try:
16
- google_sheets_creds_json = os.environ.get("GOOGLE_SHEETS_SERVICE_ACCOUNT")
17
- if not google_sheets_creds_json:
18
- # Fallback to local file for local testing
19
- SERVICE_ACCOUNT_FILE = "service_account.json"
20
- else:
21
- with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as temp_key_file:
22
- temp_key_file.write(google_sheets_creds_json)
23
- SERVICE_ACCOUNT_FILE = temp_key_file.name
24
- except Exception as e:
25
- print(f"Failed to load Google Sheets credentials from environment variable: {e}")
26
- SERVICE_ACCOUNT_FILE = "service_account.json"
27
 
28
- # --- Google Sheets 相關函式 ---
29
- def get_sheet_data(spreadsheet_url):
30
- """從 Google Sheet 讀取所有資料並返回 DataFrame 格式。"""
 
 
 
 
31
  try:
32
- scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
33
- creds = ServiceAccountCredentials.from_json_keyfile_name(SERVICE_ACCOUNT_FILE, scope)
34
- client = gspread.authorize(creds)
35
-
36
- spreadsheet = client.open_by_url(spreadsheet_url)
37
- worksheet = spreadsheet.worksheet("Sheet1")
38
-
39
- list_of_lists = worksheet.get_all_values()
40
-
41
- if not list_of_lists or len(list_of_lists) < 2:
42
- return [], "試算表為空或沒有資料。"
 
 
 
 
43
 
44
- headers = list_of_lists[0]
45
- data = []
46
- for i, row in enumerate(list_of_lists[1:], start=2):
47
- if not any(row):
48
- continue
49
- row_dict = dict(zip(headers, row))
50
- data.append({
51
- "Index": i,
52
- "白背圖URL": row_dict.get("White_Back_Image_URL", ""),
53
- "參考圖URL": row_dict.get("Ref_Image_URL", ""),
54
- "提示詞": row_dict.get("Prompt", "")
55
- })
56
-
57
- dataframe_output = [[row['Index'], row['白背圖URL'], row['參考圖URL'], row['提示詞']] for row in data]
58
-
59
- log_message = f"成功讀取 {len(data)} 筆數據。"
60
- return dataframe_output, log_message
61
-
62
- except FileNotFoundError:
63
- raise gr.Error(f"錯誤:找不到金鑰檔案 '{SERVICE_ACCOUNT_FILE}'。請確認已正確放置或設定 Hugging Face Secrets。", duration=10)
64
- except gspread.exceptions.SpreadsheetNotFound:
65
- raise gr.Error(f"錯誤:Google Sheet not found at URL: {spreadsheet_url}", duration=10)
66
  except Exception as e:
67
- raise gr.Error(f"讀取試算表時發生錯誤: {e}", duration=10)
68
 
69
- def get_row_data(spreadsheet_url, row_number):
70
  """從 Google Sheet 讀取指定列的資料。"""
71
  try:
72
- scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
73
- creds = ServiceAccountCredentials.from_json_keyfile_name(SERVICE_ACCOUNT_FILE, scope)
74
- client = gspread.authorize(creds)
75
-
76
- spreadsheet = client.open_by_url(spreadsheet_url)
77
- worksheet = spreadsheet.worksheet("Sheet1")
78
 
79
- headers = worksheet.row_values(1)
80
- row_data = worksheet.row_values(row_number)
81
-
82
- row_dict = dict(zip(headers, row_data))
83
-
84
- white_back_image_url = row_dict.get("White_Back_Image_URL", "")
85
- ref_image_url = row_dict.get("Ref_Image_URL", "")
86
- prompt_text = row_dict.get("Prompt", "")
87
 
88
  return white_back_image_url, ref_image_url, prompt_text
89
-
90
- except FileNotFoundError:
91
- raise gr.Error(f"錯誤:找不到金鑰檔案 '{SERVICE_ACCOUNT_FILE}'。", duration=10)
92
- except gspread.exceptions.SpreadsheetNotFound:
93
- raise gr.Error(f"錯誤:Google Sheet not found at URL: {spreadsheet_url}", duration=10)
94
- except IndexError:
95
- raise gr.Error(f"錯誤:指定的行數 {row_number} 不存在。", duration=10)
96
  except Exception as e:
97
- raise gr.Error(f"讀取指定行時發生錯誤: {e}", duration=10)
98
 
99
  # --- 下載圖片函式 ---
100
  def load_image_from_url(url: str):
101
- """
102
- URL 下載圖片並以 PIL Image 格式回傳,使用更穩健的請求標頭。
103
- """
104
- if not url:
105
  return None
106
  try:
107
  headers = {
108
  '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',
109
- 'Referer': 'https://www.google.com/',
110
- 'Accept-Encoding': 'gzip, deflate, br',
111
- 'Accept-Language': 'en-US,en;q=0.9',
112
- 'Connection': 'keep-alive',
113
  }
114
-
115
  response = requests.get(url, timeout=20, headers=headers)
116
  response.raise_for_status()
117
-
118
  image = Image.open(BytesIO(response.content)).convert("RGB")
119
- print(f"Debug: Successfully loaded image from URL: {url}")
120
  return image
121
  except requests.exceptions.HTTPError as e:
122
- print(f"Error downloading image from {url}: HTTP Error {e.response.status_code}")
123
- gr.Warning(f"從 URL '{url}' 下載圖片失敗:HTTP 錯誤 {e.response.status_code}", duration=10)
124
  return None
125
  except Exception as e:
126
- print(f"An unexpected error occurred: {e}")
127
- gr.Warning(f"從 URL '{url}' 下載圖片時發生意外錯誤:{e}", duration=10)
128
  return None
129
 
130
  # --- Gemini 核心函式 ---
131
  def generate_image(text, images, api_key, model="gemini-2.5-flash-image-preview"):
132
  """使用 Gemini 模型生成圖片。"""
133
  if not api_key or api_key.strip() == "":
134
- raise gr.Error("錯誤:請輸入有效的 Gemini API 金鑰。", duration=10)
135
-
136
  try:
137
  client = genai.Client(api_key=api_key.strip())
138
  contents = images + [text]
139
- response = client.models.generate_content(
140
- model=model,
141
- contents=contents,
142
- )
143
  text_response = ""
144
  image_path = None
145
  for part in response.candidates[0].content.parts:
@@ -156,15 +117,7 @@ def generate_image(text, images, api_key, model="gemini-2.5-flash-image-preview"
156
  raise gr.Error(f"Gemini API 呼叫失敗: {e}", duration=10)
157
 
158
  # --- Gradio 互動函式 ---
159
- def process_sheet_data(sheet_url):
160
- """處理試算表,讀取所有數據並更新 DataFrame。"""
161
- if not sheet_url:
162
- raise gr.Error("請輸入 Google Sheet URL。", duration=5)
163
-
164
- data, log_message = get_sheet_data(sheet_url)
165
- return data, log_message
166
-
167
- def generate_image_from_row(sheet_url, row_number):
168
  """根據指定的行數,生成圖片。"""
169
  if not sheet_url:
170
  raise gr.Error("請先輸入 Google Sheet URL。", duration=5)
@@ -172,16 +125,14 @@ def generate_image_from_row(sheet_url, row_number):
172
  raise gr.Error("請輸入有效的行數 (大於 1)。", duration=5)
173
 
174
  try:
175
- row_num = int(row_number)
176
- white_back_url, ref_image_url, prompt_text = get_row_data(sheet_url, row_num)
177
 
178
- log_message = f"開始處理第 {row_num} 行...\n"
179
  log_message += f"白背圖URL: {white_back_url}\n"
180
  log_message += f"參考圖URL: {ref_image_url}\n"
181
  log_message += f"提示詞: {prompt_text}\n"
182
 
183
  images_for_gemini = []
184
-
185
  if white_back_url:
186
  wb_img = load_image_from_url(white_back_url)
187
  if wb_img:
@@ -196,11 +147,6 @@ def generate_image_from_row(sheet_url, row_number):
196
 
197
  log_message += "圖片已下載,開始呼叫 Gemini 模型...\n"
198
 
199
- # 優先從環境變數讀取 Gemini API 金鑰
200
- gemini_api_key = os.environ.get("GEMINI_API_KEY")
201
- if not gemini_api_key:
202
- raise gr.Error("錯誤:找不到 Gemini API 金鑰。請確認已設定 Hugging Face Secrets。", duration=10)
203
-
204
  image_path, text_response = generate_image(text=prompt_text, images=images_for_gemini, api_key=gemini_api_key)
205
 
206
  if image_path:
@@ -210,7 +156,6 @@ def generate_image_from_row(sheet_url, row_number):
210
  log_message += "圖片生成失敗。\n"
211
  log_message += f"Gemini 文字回應: {text_response}"
212
  return None, log_message
213
-
214
  except ValueError:
215
  raise gr.Error("行數必須為數字。", duration=5)
216
  except Exception as e:
@@ -222,11 +167,12 @@ with gr.Blocks() as demo:
222
 
223
  with gr.Row(elem_classes="main-content"):
224
  with gr.Column(elem_classes="input-column"):
225
- # 由於金鑰將從 Secrets 讀取,這個輸入框可以移除或僅用於顯示
226
- gr.Markdown("### ⚠️ 請在 Hugging Face Spaces 專案設定中設定 Secrets")
227
- gr.Markdown("金鑰名稱:`GOOGLE_SHEETS_SERVICE_ACCOUNT`")
228
- gr.Markdown("金鑰名稱:`GEMINI_API_KEY`")
229
-
 
230
  sheet_url_input = gr.Textbox(
231
  label="Google Sheet URL",
232
  value="https://docs.google.com/spreadsheets/d/1G3olHxydDIbnyXdh5nnw5TG0akZFeMeYm-25JmCGDLg/edit?gid=0#gid=0"
@@ -260,7 +206,7 @@ with gr.Blocks() as demo:
260
 
261
  generate_selected_button.click(
262
  fn=generate_image_from_row,
263
- inputs=[sheet_url_input, row_index_input],
264
  outputs=[generated_image_output, operation_log_output]
265
  )
266
 
 
 
1
  import os
 
 
2
  import tempfile
 
3
  import gradio as gr
4
+ import pandas as pd
5
+ from urllib.parse import urlparse, parse_qs
6
+ from PIL import Image
7
  from io import BytesIO
 
 
 
8
  import requests
9
+ from google import genai
10
 
11
+ # 請確保你的 requirements.txt 包含 pandas
12
+ # pip install pandas
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ # --- Google Sheets 相關函式 (已更新) ---
15
+ def read_google_sheet(sheet_url: str):
16
+ """
17
+ 從 Google Sheet 的 URL 讀取資料。
18
+ """
19
+ if not sheet_url:
20
+ raise gr.Error("請提供 Google Sheet URL。")
21
  try:
22
+ def build_csv_url(url: str) -> str:
23
+ parsed = urlparse(url)
24
+ doc_id = parsed.path.strip("/").split("/")[2] if len(parsed.path.strip("/").split("/")) >= 3 and parsed.path.strip("/").split("/")[1] == "d" else None
25
+ gid = parse_qs(parsed.query).get("gid", [None])[0] or parse_qs(parsed.fragment).get("gid", [None])[0] or "0"
26
+ if doc_id:
27
+ return f"https://docs.google.com/spreadsheets/d/{doc_id}/export?format=csv&gid={gid}"
28
+ if "/export" in parsed.path and "format=csv" in parsed.query:
29
+ return url
30
+ return url.replace("/edit#gid=0", "/export?format=csv&gid=0")
31
+
32
+ csv_url = build_csv_url(sheet_url)
33
+ df = pd.read_csv(csv_url, engine='python', on_bad_lines='warn', encoding='utf-8')
34
+ return df
35
+ except Exception as e:
36
+ raise gr.Error(f"讀取 Google Sheet 時發生錯誤: {e}")
37
 
38
+ def process_sheet_data(sheet_url):
39
+ """處理試算表資料,為 Gradio DataFrame 準備。"""
40
+ try:
41
+ df = read_google_sheet(sheet_url)
42
+ if df.shape[1] < 4: # 檢查是否至少有 4 列 (索引, 白背圖URL, 參考圖URL, 提示詞)
43
+ error_msg = f"錯誤:Google Sheet 至少需要 4 列 (索引, 白背圖URL, 參考圖URL, 提示詞)。目前只有 {df.shape[1]} 列。"
44
+ raise gr.Error(error_msg)
45
+
46
+ # 假設列順序為: White_Back_Image_URL, Ref_Image_URL, Prompt
47
+ headers = ["Index", "白背圖URL", "參考圖URL", "提示詞"]
48
+ data_list = []
49
+ for i, row in df.iterrows():
50
+ if pd.notna(row.iloc[0]): # 檢查第一列 (白背圖URL) 是否有資料
51
+ data_list.append([i + 2, row.iloc[0], row.iloc[1], row.iloc[2]])
52
+
53
+ log_message = f"成功讀取 {len(data_list)} 筆數據。"
54
+ return data_list, log_message
 
 
 
 
 
55
  except Exception as e:
56
+ raise gr.Error(f"處理試算表資料時發生錯誤: {e}")
57
 
58
+ def get_row_data(sheet_url, row_number):
59
  """從 Google Sheet 讀取指定列的資料。"""
60
  try:
61
+ df = read_google_sheet(sheet_url)
62
+ row_index = int(row_number) - 2 # 由於DataFrame索引從0開始,而Gradio顯示的行數從2開始
63
+ if row_index < 0 or row_index >= df.shape[0]:
64
+ raise gr.Error(f"指定的行數 {row_number} 不存在。")
 
 
65
 
66
+ row_data = df.iloc[row_index]
67
+ white_back_image_url = row_data.iloc[0] if pd.notna(row_data.iloc[0]) else ""
68
+ ref_image_url = row_data.iloc[1] if pd.notna(row_data.iloc[1]) else ""
69
+ prompt_text = row_data.iloc[2] if pd.notna(row_data.iloc[2]) else ""
 
 
 
 
70
 
71
  return white_back_image_url, ref_image_url, prompt_text
 
 
 
 
 
 
 
72
  except Exception as e:
73
+ raise gr.Error(f"讀取指定行時發生錯誤: {e}")
74
 
75
  # --- 下載圖片函式 ---
76
  def load_image_from_url(url: str):
77
+ """從 URL 下載圖片並以 PIL Image 格式回傳。"""
78
+ if not url or not isinstance(url, str):
 
 
79
  return None
80
  try:
81
  headers = {
82
  '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',
 
 
 
 
83
  }
 
84
  response = requests.get(url, timeout=20, headers=headers)
85
  response.raise_for_status()
 
86
  image = Image.open(BytesIO(response.content)).convert("RGB")
 
87
  return image
88
  except requests.exceptions.HTTPError as e:
89
+ gr.Warning(f"下載圖片失敗:HTTP 錯誤 {e.response.status_code}")
 
90
  return None
91
  except Exception as e:
92
+ gr.Warning(f"下載圖片時發生意外錯誤:{e}")
 
93
  return None
94
 
95
  # --- Gemini 核心函式 ---
96
  def generate_image(text, images, api_key, model="gemini-2.5-flash-image-preview"):
97
  """使用 Gemini 模型生成圖片。"""
98
  if not api_key or api_key.strip() == "":
99
+ raise gr.Error("請輸入有效的 Gemini API 金鑰。", duration=10)
 
100
  try:
101
  client = genai.Client(api_key=api_key.strip())
102
  contents = images + [text]
103
+ response = client.models.generate_content(model=model, contents=contents)
 
 
 
104
  text_response = ""
105
  image_path = None
106
  for part in response.candidates[0].content.parts:
 
117
  raise gr.Error(f"Gemini API 呼叫失敗: {e}", duration=10)
118
 
119
  # --- Gradio 互動函式 ---
120
+ def generate_image_from_row(sheet_url, row_number, gemini_api_key):
 
 
 
 
 
 
 
 
121
  """根據指定的行數,生成圖片。"""
122
  if not sheet_url:
123
  raise gr.Error("請先輸入 Google Sheet URL。", duration=5)
 
125
  raise gr.Error("請輸入有效的行數 (大於 1)。", duration=5)
126
 
127
  try:
128
+ white_back_url, ref_image_url, prompt_text = get_row_data(sheet_url, row_number)
 
129
 
130
+ log_message = f"開始處理第 {row_number} 行...\n"
131
  log_message += f"白背圖URL: {white_back_url}\n"
132
  log_message += f"參考圖URL: {ref_image_url}\n"
133
  log_message += f"提示詞: {prompt_text}\n"
134
 
135
  images_for_gemini = []
 
136
  if white_back_url:
137
  wb_img = load_image_from_url(white_back_url)
138
  if wb_img:
 
147
 
148
  log_message += "圖片已下載,開始呼叫 Gemini 模型...\n"
149
 
 
 
 
 
 
150
  image_path, text_response = generate_image(text=prompt_text, images=images_for_gemini, api_key=gemini_api_key)
151
 
152
  if image_path:
 
156
  log_message += "圖片生成失敗。\n"
157
  log_message += f"Gemini 文字回應: {text_response}"
158
  return None, log_message
 
159
  except ValueError:
160
  raise gr.Error("行數必須為數字。", duration=5)
161
  except Exception as e:
 
167
 
168
  with gr.Row(elem_classes="main-content"):
169
  with gr.Column(elem_classes="input-column"):
170
+ gemini_api_key = gr.Textbox(
171
+ lines=1,
172
+ placeholder="請在此輸入你的 Gemini API 金鑰",
173
+ label="Gemini API 金鑰",
174
+ elem_classes="api-key-input"
175
+ )
176
  sheet_url_input = gr.Textbox(
177
  label="Google Sheet URL",
178
  value="https://docs.google.com/spreadsheets/d/1G3olHxydDIbnyXdh5nnw5TG0akZFeMeYm-25JmCGDLg/edit?gid=0#gid=0"
 
206
 
207
  generate_selected_button.click(
208
  fn=generate_image_from_row,
209
+ inputs=[sheet_url_input, row_index_input, gemini_api_key],
210
  outputs=[generated_image_output, operation_log_output]
211
  )
212