Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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
|
| 45 |
try:
|
| 46 |
-
|
|
|
|
| 47 |
response.raise_for_status()
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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}
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 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 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
if
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
if
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
except Exception as e:
|
| 242 |
-
|
| 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("
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
)
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 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()
|