Abs6187 commited on
Commit
98192b5
Β·
verified Β·
1 Parent(s): 7a259c7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +211 -31
app.py CHANGED
@@ -8,22 +8,57 @@ import base64
8
  import io
9
  import logging
10
  import time
11
- from typing import Optional, Tuple
12
  import warnings
13
  import requests
14
  import json
 
 
15
  warnings.filterwarnings("ignore")
16
 
17
- logging.basicConfig(level=logging.INFO)
 
 
 
 
 
 
 
18
  logger = logging.getLogger(__name__)
19
 
20
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
21
- ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
22
- FREEPIK_API_KEY = os.getenv("FREEPIK_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  MAX_IMAGE_SIZE = 1024
25
  RATE_LIMIT_DELAY = 3
26
- API_RETRY_COUNT = 3
 
 
27
 
28
  IMAGE_MODELS = {
29
  "Freepik Gemini 2.5 Flash": {
@@ -60,11 +95,90 @@ except ImportError:
60
  yolo_available = False
61
  logger.info("YOLO not available - optional feature")
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  class NanoBananaApp:
64
  def __init__(self):
65
  self.gemini_model = None
66
  self.yolo_model = None
 
67
  self._initialize_gemini()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  def _initialize_gemini(self):
70
  if not GEMINI_API_KEY:
@@ -103,40 +217,45 @@ class NanoBananaApp:
103
  else:
104
  return None, f"Unsupported API type: {api_type}"
105
 
 
106
  def _generate_with_freepik(self, image, prompt, style, editing_mode, api_key):
107
  if not api_key:
108
  return None, "Freepik API key not provided"
109
 
110
  try:
111
  buffered = io.BytesIO()
112
- image.save(buffered, format='PNG')
113
  image_b64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
114
 
115
  full_prompt = self._build_enhanced_prompt(prompt, style, editing_mode)
 
116
 
117
- # Note: This URL may need to be updated based on actual Freepik API documentation
118
  url = "https://api.freepik.com/v1/ai/text-to-image"
119
  payload = {
120
  "prompt": full_prompt,
121
  "num_images": 1,
122
  "image": {
123
  "size": "1024x1024"
124
- }
125
- }
126
- headers = {
127
- "x-freepik-api-key": api_key,
128
- "Content-Type": "application/json"
129
  }
130
 
131
- response = requests.post(url, json=payload, headers=headers, timeout=60)
 
 
132
 
133
- logger.info(f"Freepik API response: {response.status_code}")
 
134
 
135
  if response.status_code == 200:
136
  result = response.json()
137
  logger.info(f"Freepik response data keys: {list(result.keys())}")
138
 
139
- # Handle different possible response formats
 
 
 
140
  if 'data' in result and len(result['data']) > 0:
141
  data_item = result['data'][0]
142
 
@@ -183,17 +302,29 @@ class NanoBananaApp:
183
  return None, f"Freepik API: Unexpected response format - available keys: {list(result.keys())}"
184
 
185
  elif response.status_code == 401:
186
- return None, "Invalid or expired Freepik API key"
 
187
  elif response.status_code == 403:
188
- return None, "Freepik API access forbidden - check subscription plan"
 
189
  elif response.status_code == 429:
190
- return None, "Freepik API rate limit exceeded"
 
 
 
 
 
 
 
191
  else:
192
  try:
193
  error_data = response.json()
194
- return None, f"Freepik API error: {response.status_code} - {error_data}"
 
 
195
  except:
196
- return None, f"Freepik API error: {response.status_code} - {response.text[:200]}"
 
197
 
198
  except Exception as e:
199
  logger.error(f"Freepik generation failed: {e}")
@@ -202,21 +333,63 @@ class NanoBananaApp:
202
 
203
  def _build_enhanced_prompt(self, prompt, style, editing_mode):
204
  style_modifiers = {
205
- "realistic": "photorealistic, high-quality construction, professional architecture",
206
- "futuristic": "futuristic, high-tech, modern glass and steel, sci-fi architecture",
207
- "artistic": "artistic, creative design, unique architecture, colorful and innovative"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  }
209
 
210
  mode_descriptions = {
211
- "complete": "Complete this unfinished construction",
212
- "edit": "Edit and transform this construction image",
213
- "blend": "Blend and reimagine this construction"
 
 
 
 
 
 
 
 
 
 
 
 
214
  }
215
 
216
- base_desc = mode_descriptions.get(editing_mode, 'Transform')
217
- style_desc = style_modifiers.get(style, '')
 
 
 
 
 
 
 
 
218
 
219
- return f"{base_desc} {prompt}. Style: {style_desc}. Make it look professional and realistic. Architecture, construction, building."
 
 
 
 
 
 
 
220
 
221
  def load_yolo_optional(self):
222
  if not yolo_available:
@@ -422,8 +595,15 @@ def process_nano_banana_with_freepik(image, prompt, style, editing_mode, freepik
422
  if not prompt or not prompt.strip():
423
  return image, image, image, None, "πŸ’­ Please provide a transformation prompt", None
424
 
 
 
 
 
 
 
 
425
  user_api_keys = {
426
- "freepik": freepik_key or FREEPIK_API_KEY
427
  }
428
 
429
  try:
 
8
  import io
9
  import logging
10
  import time
11
+ from typing import Optional, Tuple, Dict, List
12
  import warnings
13
  import requests
14
  import json
15
+ import random
16
+ from functools import wraps
17
  warnings.filterwarnings("ignore")
18
 
19
+ logging.basicConfig(
20
+ level=logging.INFO,
21
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
22
+ handlers=[
23
+ logging.StreamHandler(),
24
+ logging.FileHandler('nano_banana.log')
25
+ ]
26
+ )
27
  logger = logging.getLogger(__name__)
28
 
29
+ def validate_api_key(api_key, api_name):
30
+ if not api_key:
31
+ return False, f"{api_name} API key not provided"
32
+
33
+ if len(api_key.strip()) < 10:
34
+ return False, f"{api_name} API key appears to be too short"
35
+
36
+ if api_key.startswith(' ') or api_key.endswith(' '):
37
+ return False, f"{api_name} API key has leading/trailing whitespace"
38
+
39
+ return True, f"{api_name} API key format validated"
40
+
41
+ def get_secure_api_key(env_var_name, api_name):
42
+ api_key = os.getenv(env_var_name)
43
+ if api_key:
44
+ is_valid, message = validate_api_key(api_key, api_name)
45
+ if is_valid:
46
+ logger.info(f"{api_name} API key loaded and validated")
47
+ return api_key.strip()
48
+ else:
49
+ logger.warning(f"{api_name} API key validation failed: {message}")
50
+ return None
51
+ return None
52
+
53
+ GEMINI_API_KEY = get_secure_api_key("GEMINI_API_KEY", "Gemini")
54
+ ELEVENLABS_API_KEY = get_secure_api_key("ELEVENLABS_API_KEY", "ElevenLabs")
55
+ FREEPIK_API_KEY = get_secure_api_key("FREEPIK_API_KEY", "Freepik")
56
 
57
  MAX_IMAGE_SIZE = 1024
58
  RATE_LIMIT_DELAY = 3
59
+ API_RETRY_COUNT = 5
60
+ MAX_BACKOFF_TIME = 60
61
+ BASE_BACKOFF = 1
62
 
63
  IMAGE_MODELS = {
64
  "Freepik Gemini 2.5 Flash": {
 
95
  yolo_available = False
96
  logger.info("YOLO not available - optional feature")
97
 
98
+ def retry_with_backoff(max_retries=API_RETRY_COUNT, base_delay=BASE_BACKOFF):
99
+ def decorator(func):
100
+ @wraps(func)
101
+ def wrapper(*args, **kwargs):
102
+ last_exception = None
103
+ for attempt in range(max_retries):
104
+ try:
105
+ return func(*args, **kwargs)
106
+ except requests.exceptions.RequestException as e:
107
+ last_exception = e
108
+ if hasattr(e, 'response') and e.response is not None:
109
+ status_code = e.response.status_code
110
+ if status_code == 429:
111
+ wait_time = min(base_delay * (2 ** attempt) + random.uniform(0, 1), MAX_BACKOFF_TIME)
112
+ logger.warning(f"Rate limit hit, retrying in {wait_time:.2f}s (attempt {attempt + 1}/{max_retries})")
113
+ time.sleep(wait_time)
114
+ continue
115
+ elif status_code in [500, 502, 503, 504]:
116
+ wait_time = min(base_delay * (2 ** attempt), MAX_BACKOFF_TIME)
117
+ logger.warning(f"Server error {status_code}, retrying in {wait_time:.2f}s (attempt {attempt + 1}/{max_retries})")
118
+ time.sleep(wait_time)
119
+ continue
120
+ else:
121
+ logger.error(f"Non-retryable error: {status_code}")
122
+ break
123
+ else:
124
+ wait_time = min(base_delay * (2 ** attempt), MAX_BACKOFF_TIME)
125
+ logger.warning(f"Network error, retrying in {wait_time:.2f}s (attempt {attempt + 1}/{max_retries})")
126
+ time.sleep(wait_time)
127
+ except Exception as e:
128
+ last_exception = e
129
+ logger.error(f"Unexpected error on attempt {attempt + 1}: {e}")
130
+ break
131
+
132
+ if last_exception:
133
+ raise last_exception
134
+ return None
135
+ return wrapper
136
+ return decorator
137
+
138
  class NanoBananaApp:
139
  def __init__(self):
140
  self.gemini_model = None
141
  self.yolo_model = None
142
+ self.session = self._create_session()
143
  self._initialize_gemini()
144
+
145
+ def _create_session(self):
146
+ session = requests.Session()
147
+ session.headers.update({
148
+ 'User-Agent': 'NanoBanana/1.0',
149
+ 'Accept': 'application/json',
150
+ 'Content-Type': 'application/json'
151
+ })
152
+
153
+ adapter = requests.adapters.HTTPAdapter(
154
+ max_retries=requests.adapters.Retry(
155
+ total=3,
156
+ backoff_factor=0.3,
157
+ status_forcelist=[500, 502, 503, 504]
158
+ )
159
+ )
160
+ session.mount('http://', adapter)
161
+ session.mount('https://', adapter)
162
+
163
+ return session
164
+
165
+ def _log_api_response(self, response, api_name="API"):
166
+ logger.info(f"{api_name} Response: {response.status_code}")
167
+ if response.status_code != 200:
168
+ logger.error(f"{api_name} Error: {response.status_code} - {response.text[:500]}")
169
+ else:
170
+ logger.info(f"{api_name} Success: Request completed")
171
+
172
+ def _validate_api_response(self, response, expected_keys=None):
173
+ if not response:
174
+ return False, "Empty response"
175
+
176
+ if expected_keys:
177
+ missing_keys = [key for key in expected_keys if key not in response]
178
+ if missing_keys:
179
+ return False, f"Missing keys: {missing_keys}"
180
+
181
+ return True, "Valid response"
182
 
183
  def _initialize_gemini(self):
184
  if not GEMINI_API_KEY:
 
217
  else:
218
  return None, f"Unsupported API type: {api_type}"
219
 
220
+ @retry_with_backoff(max_retries=API_RETRY_COUNT)
221
  def _generate_with_freepik(self, image, prompt, style, editing_mode, api_key):
222
  if not api_key:
223
  return None, "Freepik API key not provided"
224
 
225
  try:
226
  buffered = io.BytesIO()
227
+ image.save(buffered, format='PNG', optimize=True, quality=95)
228
  image_b64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
229
 
230
  full_prompt = self._build_enhanced_prompt(prompt, style, editing_mode)
231
+ logger.info(f"Enhanced prompt (first 200 chars): {full_prompt[:200]}...")
232
 
 
233
  url = "https://api.freepik.com/v1/ai/text-to-image"
234
  payload = {
235
  "prompt": full_prompt,
236
  "num_images": 1,
237
  "image": {
238
  "size": "1024x1024"
239
+ },
240
+ "style": style,
241
+ "mode": editing_mode
 
 
242
  }
243
 
244
+ self.session.headers.update({
245
+ "x-freepik-api-key": api_key
246
+ })
247
 
248
+ response = self.session.post(url, json=payload, timeout=60)
249
+ self._log_api_response(response, "Freepik")
250
 
251
  if response.status_code == 200:
252
  result = response.json()
253
  logger.info(f"Freepik response data keys: {list(result.keys())}")
254
 
255
+ is_valid, validation_msg = self._validate_api_response(result, ['data'])
256
+ if not is_valid:
257
+ logger.warning(f"Response validation failed: {validation_msg}")
258
+
259
  if 'data' in result and len(result['data']) > 0:
260
  data_item = result['data'][0]
261
 
 
302
  return None, f"Freepik API: Unexpected response format - available keys: {list(result.keys())}"
303
 
304
  elif response.status_code == 401:
305
+ logger.error("Freepik API authentication failed")
306
+ return None, "πŸ”‘ Invalid or expired Freepik API key. Please check your API key."
307
  elif response.status_code == 403:
308
+ logger.error("Freepik API access forbidden")
309
+ return None, "🚫 Freepik API access forbidden. Please check your subscription plan and permissions."
310
  elif response.status_code == 429:
311
+ logger.warning("Freepik API rate limit exceeded")
312
+ return None, "⏱️ Freepik API rate limit exceeded. Please wait before making another request."
313
+ elif response.status_code == 400:
314
+ logger.error("Freepik API bad request")
315
+ return None, "πŸ“ Invalid request parameters. Please check your prompt and settings."
316
+ elif response.status_code >= 500:
317
+ logger.error(f"Freepik API server error: {response.status_code}")
318
+ return None, f"πŸ› οΈ Freepik API server error ({response.status_code}). Please try again later."
319
  else:
320
  try:
321
  error_data = response.json()
322
+ error_message = error_data.get('message', 'Unknown error')
323
+ logger.error(f"Freepik API error {response.status_code}: {error_message}")
324
+ return None, f"❌ Freepik API error ({response.status_code}): {error_message}"
325
  except:
326
+ logger.error(f"Freepik API error {response.status_code}: {response.text[:200]}")
327
+ return None, f"❌ Freepik API error ({response.status_code}): {response.text[:200]}"
328
 
329
  except Exception as e:
330
  logger.error(f"Freepik generation failed: {e}")
 
333
 
334
  def _build_enhanced_prompt(self, prompt, style, editing_mode):
335
  style_modifiers = {
336
+ "realistic": {
337
+ "base": "photorealistic, high-quality construction, professional architecture",
338
+ "materials": "concrete, steel, glass, brick, modern building materials",
339
+ "lighting": "natural lighting, architectural photography, professional composition",
340
+ "details": "precise construction details, realistic textures, accurate proportions"
341
+ },
342
+ "futuristic": {
343
+ "base": "futuristic, high-tech, modern glass and steel, sci-fi architecture",
344
+ "materials": "smart glass, carbon fiber, LED lighting, metallic surfaces",
345
+ "lighting": "neon accents, ambient lighting, dynamic color schemes",
346
+ "details": "sleek lines, geometric patterns, technological integration"
347
+ },
348
+ "artistic": {
349
+ "base": "artistic, creative design, unique architecture, colorful and innovative",
350
+ "materials": "mixed media, colorful facades, creative textures, artistic elements",
351
+ "lighting": "dramatic lighting, vibrant colors, artistic composition",
352
+ "details": "creative patterns, artistic flourishes, unique design elements"
353
+ }
354
  }
355
 
356
  mode_descriptions = {
357
+ "complete": {
358
+ "action": "Complete and finish this unfinished construction project",
359
+ "focus": "seamless integration, structural completion, architectural consistency",
360
+ "outcome": "fully realized building with complete structure and details"
361
+ },
362
+ "edit": {
363
+ "action": "Edit and transform specific elements of this construction image",
364
+ "focus": "targeted modifications, enhanced features, improved design",
365
+ "outcome": "enhanced version with specific improvements and modifications"
366
+ },
367
+ "blend": {
368
+ "action": "Blend and reimagine this construction with new architectural elements",
369
+ "focus": "creative fusion, architectural harmony, innovative design integration",
370
+ "outcome": "seamlessly blended design combining existing and new elements"
371
+ }
372
  }
373
 
374
+ style_info = style_modifiers.get(style, style_modifiers["realistic"])
375
+ mode_info = mode_descriptions.get(editing_mode, mode_descriptions["edit"])
376
+
377
+ enhanced_prompt = f"""
378
+ {mode_info['action']}: {prompt}
379
+
380
+ Style Requirements: {style_info['base']}
381
+ Materials: {style_info['materials']}
382
+ Lighting: {style_info['lighting']}
383
+ Details: {style_info['details']}
384
 
385
+ Focus: {mode_info['focus']}
386
+ Desired Outcome: {mode_info['outcome']}
387
+
388
+ Quality: High-resolution, professional architecture rendering, detailed construction imagery
389
+ Context: Modern construction, building architecture, structural engineering
390
+ """
391
+
392
+ return enhanced_prompt.strip()
393
 
394
  def load_yolo_optional(self):
395
  if not yolo_available:
 
595
  if not prompt or not prompt.strip():
596
  return image, image, image, None, "πŸ’­ Please provide a transformation prompt", None
597
 
598
+ final_freepik_key = freepik_key or FREEPIK_API_KEY
599
+ if final_freepik_key:
600
+ is_valid, validation_msg = validate_api_key(final_freepik_key, "Freepik")
601
+ if not is_valid:
602
+ return image, image, image, None, f"πŸ”‘ API Key Error: {validation_msg}", None
603
+ logger.info("User-provided Freepik API key validated")
604
+
605
  user_api_keys = {
606
+ "freepik": final_freepik_key
607
  }
608
 
609
  try: