openfree commited on
Commit
43f666f
·
verified ·
1 Parent(s): c63ac7e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +428 -0
app.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import replicate
3
+ import os
4
+ from PIL import Image
5
+ import requests
6
+ from io import BytesIO
7
+ import time
8
+ import tempfile
9
+ import base64
10
+
11
+ # Set up Replicate API key from environment variable
12
+ os.environ['REPLICATE_API_TOKEN'] = os.getenv('REPLICATE_API_TOKEN')
13
+
14
+ def upload_image_to_hosting(image):
15
+ """
16
+ Upload image to multiple hosting services with fallback
17
+ """
18
+ # Method 1: Try imgbb.com (most reliable)
19
+ try:
20
+ buffered = BytesIO()
21
+ image.save(buffered, format="PNG")
22
+ buffered.seek(0)
23
+ img_base64 = base64.b64encode(buffered.getvalue()).decode()
24
+
25
+ response = requests.post(
26
+ "https://api.imgbb.com/1/upload",
27
+ data={
28
+ 'key': '6d207e02198a847aa98d0a2a901485a5',
29
+ 'image': img_base64,
30
+ }
31
+ )
32
+
33
+ if response.status_code == 200:
34
+ data = response.json()
35
+ if data.get('success'):
36
+ return data['data']['url']
37
+ except:
38
+ pass
39
+
40
+ # Method 2: Try 0x0.st (simple and reliable)
41
+ try:
42
+ buffered = BytesIO()
43
+ image.save(buffered, format="PNG")
44
+ buffered.seek(0)
45
+
46
+ files = {'file': ('image.png', buffered, 'image/png')}
47
+ response = requests.post("https://0x0.st", files=files)
48
+
49
+ if response.status_code == 200:
50
+ return response.text.strip()
51
+ except:
52
+ pass
53
+
54
+ # Method 3: Fallback to base64
55
+ buffered = BytesIO()
56
+ image.save(buffered, format="PNG")
57
+ buffered.seek(0)
58
+ img_base64 = base64.b64encode(buffered.getvalue()).decode()
59
+ return f"data:image/png;base64,{img_base64}"
60
+
61
+ def process_images(prompt, image1, image2=None):
62
+ """
63
+ Process uploaded images with Replicate API or generate from text
64
+ """
65
+ if not os.getenv('REPLICATE_API_TOKEN'):
66
+ return None, "Please set REPLICATE_API_TOKEN"
67
+
68
+ try:
69
+ # Check if any images are uploaded
70
+ if not image1 and not image2:
71
+ # Text-to-Image mode (no images uploaded)
72
+ return generate_from_text(prompt)
73
+
74
+ # Image-to-Image mode (at least one image uploaded)
75
+ image_urls = []
76
+
77
+ # Upload images
78
+ if image1:
79
+ url1 = upload_image_to_hosting(image1)
80
+ image_urls.append(url1)
81
+
82
+ if image2:
83
+ url2 = upload_image_to_hosting(image2)
84
+ image_urls.append(url2)
85
+
86
+ # Run the model with images
87
+ output = replicate.run(
88
+ "google/nano-banana",
89
+ input={
90
+ "prompt": prompt,
91
+ "image_input": image_urls
92
+ }
93
+ )
94
+
95
+ return process_output(output)
96
+
97
+ except Exception as e:
98
+ return None, f"Error: {str(e)[:100]}"
99
+
100
+ def generate_from_text(prompt):
101
+ """
102
+ Generate image from text only using a text-to-image model
103
+ """
104
+ try:
105
+ # Use a text-to-image model when no images are provided
106
+ # Using SDXL-Lightning for fast generation
107
+ output = replicate.run(
108
+ "bytedance/sdxl-lightning-4step:5599ed30703defd1d160a25a63321b4dec97101d98b4674bcc56e41f62f35637",
109
+ input={
110
+ "prompt": prompt,
111
+ "negative_prompt": "worst quality, low quality, blurry, nsfw",
112
+ "width": 1024,
113
+ "height": 1024,
114
+ "num_inference_steps": 4,
115
+ "guidance_scale": 0,
116
+ "scheduler": "K_EULER"
117
+ }
118
+ )
119
+
120
+ return process_output(output, "✨ Generated from text successfully!")
121
+
122
+ except Exception as e:
123
+ # Fallback to another model if the first one fails
124
+ try:
125
+ output = replicate.run(
126
+ "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b",
127
+ input={
128
+ "prompt": prompt,
129
+ "negative_prompt": "worst quality, low quality",
130
+ "width": 1024,
131
+ "height": 1024,
132
+ "num_inference_steps": 25,
133
+ "refine": "expert_ensemble_refiner",
134
+ "apply_watermark": False
135
+ }
136
+ )
137
+ return process_output(output, "✨ Generated from text successfully!")
138
+ except Exception as e2:
139
+ return None, f"Text generation error: {str(e2)[:100]}"
140
+
141
+ def process_output(output, success_message="✨ Generated successfully!"):
142
+ """
143
+ Process the output from Replicate API
144
+ """
145
+ if output is None:
146
+ return None, "No output received"
147
+
148
+ # Get the generated image
149
+ try:
150
+ if hasattr(output, 'read'):
151
+ img_data = output.read()
152
+ img = Image.open(BytesIO(img_data))
153
+ return img, success_message
154
+ except:
155
+ pass
156
+
157
+ try:
158
+ if hasattr(output, 'url'):
159
+ output_url = output.url()
160
+ response = requests.get(output_url, timeout=30)
161
+ if response.status_code == 200:
162
+ img = Image.open(BytesIO(response.content))
163
+ return img, success_message
164
+ except:
165
+ pass
166
+
167
+ output_url = None
168
+ if isinstance(output, str):
169
+ output_url = output
170
+ elif isinstance(output, list) and len(output) > 0:
171
+ output_url = output[0]
172
+
173
+ if output_url:
174
+ response = requests.get(output_url, timeout=30)
175
+ if response.status_code == 200:
176
+ img = Image.open(BytesIO(response.content))
177
+ return img, success_message
178
+
179
+ return None, "Could not process output"
180
+
181
+ # Enhanced CSS with modern, minimal design
182
+ css = """
183
+ .gradio-container {
184
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
185
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
186
+ min-height: 100vh;
187
+ }
188
+ .header-container {
189
+ background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%);
190
+ padding: 2.5rem;
191
+ border-radius: 24px;
192
+ margin-bottom: 2.5rem;
193
+ box-shadow: 0 20px 60px rgba(255, 179, 71, 0.25);
194
+ }
195
+ .logo-text {
196
+ font-size: 3.5rem;
197
+ font-weight: 900;
198
+ color: #2d3436;
199
+ text-align: center;
200
+ margin: 0;
201
+ letter-spacing: -2px;
202
+ }
203
+ .subtitle {
204
+ color: #2d3436;
205
+ text-align: center;
206
+ font-size: 1rem;
207
+ margin-top: 0.5rem;
208
+ opacity: 0.8;
209
+ }
210
+ .mode-indicator {
211
+ background: rgba(255, 255, 255, 0.3);
212
+ backdrop-filter: blur(10px);
213
+ border-radius: 12px;
214
+ padding: 0.5rem 1rem;
215
+ margin-top: 1rem;
216
+ text-align: center;
217
+ font-weight: 600;
218
+ color: #2d3436;
219
+ }
220
+ .main-content {
221
+ background: rgba(255, 255, 255, 0.95);
222
+ backdrop-filter: blur(20px);
223
+ border-radius: 24px;
224
+ padding: 2.5rem;
225
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
226
+ }
227
+ .gr-button-primary {
228
+ background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%) !important;
229
+ border: none !important;
230
+ color: #2d3436 !important;
231
+ font-weight: 700 !important;
232
+ font-size: 1.1rem !important;
233
+ padding: 1.2rem 2rem !important;
234
+ border-radius: 14px !important;
235
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
236
+ text-transform: uppercase;
237
+ letter-spacing: 1px;
238
+ width: 100%;
239
+ margin-top: 1rem !important;
240
+ }
241
+ .gr-button-primary:hover {
242
+ transform: translateY(-3px) !important;
243
+ box-shadow: 0 15px 40px rgba(255, 179, 71, 0.35) !important;
244
+ }
245
+ .gr-input, .gr-textarea {
246
+ background: #ffffff !important;
247
+ border: 2px solid #e1e8ed !important;
248
+ border-radius: 14px !important;
249
+ color: #2d3436 !important;
250
+ font-size: 1rem !important;
251
+ padding: 0.8rem 1rem !important;
252
+ }
253
+ .gr-input:focus, .gr-textarea:focus {
254
+ border-color: #ffd93d !important;
255
+ box-shadow: 0 0 0 4px rgba(255, 217, 61, 0.15) !important;
256
+ }
257
+ .gr-form {
258
+ background: transparent !important;
259
+ border: none !important;
260
+ }
261
+ .gr-panel {
262
+ background: #ffffff !important;
263
+ border: 2px solid #e1e8ed !important;
264
+ border-radius: 16px !important;
265
+ padding: 1.5rem !important;
266
+ }
267
+ .gr-box {
268
+ border-radius: 14px !important;
269
+ border-color: #e1e8ed !important;
270
+ }
271
+ label {
272
+ color: #636e72 !important;
273
+ font-weight: 600 !important;
274
+ font-size: 0.85rem !important;
275
+ text-transform: uppercase;
276
+ letter-spacing: 0.5px;
277
+ margin-bottom: 0.5rem !important;
278
+ }
279
+ .status-text {
280
+ font-family: 'SF Mono', 'Monaco', monospace;
281
+ color: #00b894;
282
+ font-size: 0.9rem;
283
+ }
284
+ .image-container {
285
+ border-radius: 14px !important;
286
+ overflow: hidden;
287
+ border: 2px solid #e1e8ed !important;
288
+ background: #fafbfc !important;
289
+ }
290
+ footer {
291
+ display: none !important;
292
+ }
293
+ /* Equal sizing for all image containers */
294
+ .image-upload {
295
+ min-height: 200px !important;
296
+ max-height: 200px !important;
297
+ }
298
+ .output-image {
299
+ min-height: 420px !important;
300
+ max-height: 420px !important;
301
+ }
302
+ /* Ensure consistent spacing */
303
+ .gr-row {
304
+ gap: 1rem !important;
305
+ }
306
+ .gr-column {
307
+ gap: 1rem !important;
308
+ }
309
+ .info-box {
310
+ background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
311
+ border-radius: 12px;
312
+ padding: 1rem;
313
+ margin-bottom: 1rem;
314
+ border-left: 4px solid #2196f3;
315
+ }
316
+ """
317
+
318
+ with gr.Blocks(css=css, theme=gr.themes.Base()) as demo:
319
+ with gr.Column(elem_classes="header-container"):
320
+ gr.HTML("""
321
+ <h1 class="logo-text">🍌 Free Nano Banana</h1>
322
+ <p class="subtitle">AI-Powered Image Generation & Style Transfer</p>
323
+ <div class="mode-indicator">
324
+ 💡 Works in two modes: Text-to-Image (no upload) or Style Transfer (with images)
325
+ </div>
326
+ <div style="display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 20px;">
327
+ <a href="https://huggingface.co/spaces/ginigen/Nano-Banana-PRO" target="_blank">
328
+ <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=PRO&color=%230000ff&labelColor=%23800080&logo=HUGGINGFACE&logoColor=white&style=for-the-badge" alt="badge">
329
+ </a>
330
+ <a href="https://huggingface.co/spaces/openfree/Nano-Banana-Upscale" target="_blank">
331
+ <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=UPSCALE&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Nano Banana Upscale">
332
+ </a>
333
+ <a href="https://huggingface.co/spaces/aiqtech/Nano-Banana-API" target="_blank">
334
+ <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=API&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Nano Banana API">
335
+ </a>
336
+ <a href="https://huggingface.co/spaces/ginigen/Nano-Banana-Video" target="_blank">
337
+ <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=VIDEO&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Nano Banana VIDEO">
338
+ </a>
339
+ <a href="https://discord.gg/openfreeai" target="_blank">
340
+ <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="Discord Openfree AI">
341
+ </a>
342
+ </div>
343
+ """)
344
+
345
+ with gr.Column(elem_classes="main-content"):
346
+ # Info box
347
+ gr.HTML("""
348
+ <div class="info-box">
349
+ <strong>How to use:</strong><br>
350
+ • <b>Text-to-Image:</b> Enter a prompt without uploading images<br>
351
+ • <b>Style Transfer:</b> Upload 1-2 images and describe the style you want
352
+ </div>
353
+ """)
354
+
355
+ with gr.Row(equal_height=True):
356
+ # Left Column - Inputs
357
+ with gr.Column(scale=1):
358
+ prompt = gr.Textbox(
359
+ label="Prompt / Style Description",
360
+ placeholder="Describe what you want to generate or the style to apply...",
361
+ lines=3,
362
+ value="A beautiful sunset over mountains with golden clouds",
363
+ elem_classes="prompt-input"
364
+ )
365
+
366
+ with gr.Row(equal_height=True):
367
+ image1 = gr.Image(
368
+ label="Primary Image (Optional)",
369
+ type="pil",
370
+ height=200,
371
+ elem_classes="image-container image-upload"
372
+ )
373
+ image2 = gr.Image(
374
+ label="Secondary Image (Optional)",
375
+ type="pil",
376
+ height=200,
377
+ elem_classes="image-container image-upload"
378
+ )
379
+
380
+ generate_btn = gr.Button(
381
+ "Generate Magic ✨",
382
+ variant="primary",
383
+ size="lg"
384
+ )
385
+
386
+ # Right Column - Output
387
+ with gr.Column(scale=1):
388
+ output_image = gr.Image(
389
+ label="Generated Result",
390
+ type="pil",
391
+ height=420,
392
+ elem_classes="image-container output-image"
393
+ )
394
+
395
+ status = gr.Textbox(
396
+ label="Status",
397
+ interactive=False,
398
+ lines=1,
399
+ elem_classes="status-text",
400
+ value="Ready to generate..."
401
+ )
402
+
403
+ # Event handler
404
+ generate_btn.click(
405
+ fn=process_images,
406
+ inputs=[prompt, image1, image2],
407
+ outputs=[output_image, status]
408
+ )
409
+
410
+ # Examples
411
+ gr.Examples(
412
+ examples=[
413
+ ["A cyberpunk city at night with neon lights and flying cars", None, None],
414
+ ["A magical forest with glowing mushrooms and fireflies", None, None],
415
+ ["Portrait of a robot in renaissance painting style", None, None],
416
+ ["Underwater palace made of coral and pearls", None, None],
417
+ ],
418
+ inputs=[prompt, image1, image2],
419
+ label="Text-to-Image Examples"
420
+ )
421
+
422
+ # Launch
423
+ if __name__ == "__main__":
424
+ demo.launch(
425
+ share=True,
426
+ server_name="0.0.0.0",
427
+ server_port=7860
428
+ )