Switch to Gemini + update requirements
Browse files
app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import os
|
| 2 |
import time
|
| 3 |
from datetime import datetime
|
|
|
|
| 4 |
|
| 5 |
from fastapi import FastAPI, UploadFile, Form
|
| 6 |
from fastapi.responses import FileResponse, JSONResponse
|
|
@@ -13,9 +14,13 @@ import uvicorn
|
|
| 13 |
|
| 14 |
from dotenv import load_dotenv
|
| 15 |
|
| 16 |
-
# Load environment variables
|
| 17 |
load_dotenv()
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
# ---------------------------
|
| 21 |
# CONFIG
|
|
@@ -29,7 +34,7 @@ LIFETIME = 24 * 60 * 60 # 24 hours
|
|
| 29 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 30 |
os.makedirs(RESULTS_DIR, exist_ok=True)
|
| 31 |
|
| 32 |
-
# Gemini
|
| 33 |
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
|
| 34 |
model = genai.GenerativeModel("gemini-1.5-flash")
|
| 35 |
|
|
@@ -48,55 +53,84 @@ def check_size(filepath):
|
|
| 48 |
os.remove(filepath)
|
| 49 |
raise ValueError(f"File too large! Max {MAX_SIZE_MB}MB allowed.")
|
| 50 |
|
| 51 |
-
def
|
| 52 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
if input_img is None:
|
| 54 |
return []
|
| 55 |
|
| 56 |
temp_path = os.path.join(UPLOAD_DIR, f"upload_{int(time.time())}.png")
|
| 57 |
input_img.save(temp_path)
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
if bg_choice and bg_choice in os.listdir(BG_DIR):
|
| 63 |
-
bg = Image.open(os.path.join(BG_DIR, bg_choice)).convert("RGBA").resize(fg.size)
|
| 64 |
-
elif bg_choice == "Custom Upload" and bg_upload is not None:
|
| 65 |
-
bg = bg_upload.convert("RGBA").resize(fg.size)
|
| 66 |
-
elif bg_choice == "Solid Brand Color" and brand_color:
|
| 67 |
-
bg = Image.new("RGBA", fg.size, brand_color)
|
| 68 |
else:
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
|
|
|
| 71 |
result = Image.alpha_composite(bg, fg)
|
| 72 |
|
| 73 |
-
#
|
| 74 |
if logo_upload is not None:
|
| 75 |
logo = logo_upload.convert("RGBA")
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
| 79 |
logo.putalpha(alpha)
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
| 94 |
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
| 95 |
result_path = os.path.join(RESULTS_DIR, f"result_{timestamp}.png")
|
| 96 |
result.save(result_path)
|
| 97 |
cleanup_old_files(RESULTS_DIR)
|
|
|
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
return [result_path]
|
|
|
|
| 100 |
|
| 101 |
def generate_caption(prompt="Promote my product"):
|
| 102 |
try:
|
|
@@ -115,9 +149,10 @@ def generate_caption(prompt="Promote my product"):
|
|
| 115 |
# ---------------------------
|
| 116 |
app = FastAPI(title="SnapLift API")
|
| 117 |
|
|
|
|
| 118 |
app.add_middleware(
|
| 119 |
CORSMiddleware,
|
| 120 |
-
allow_origins=["*"],
|
| 121 |
allow_credentials=True,
|
| 122 |
allow_methods=["*"],
|
| 123 |
allow_headers=["*"],
|
|
@@ -129,8 +164,9 @@ async def process_image_api(file: UploadFile, bg_choice: str = Form(...)):
|
|
| 129 |
input_path = os.path.join(UPLOAD_DIR, file.filename)
|
| 130 |
with open(input_path, "wb") as f:
|
| 131 |
f.write(await file.read())
|
| 132 |
-
|
| 133 |
-
|
|
|
|
| 134 |
except Exception as e:
|
| 135 |
return JSONResponse(content={"error": str(e)}, status_code=400)
|
| 136 |
|
|
@@ -146,50 +182,74 @@ with gr.Blocks(css="footer {display:none !important}") as demo:
|
|
| 146 |
gr.Markdown("# ✨ SnapLift – AI Social Media Booster")
|
| 147 |
gr.Markdown("Upload your product photo, replace background, and auto-generate marketing captions + hashtags!")
|
| 148 |
|
| 149 |
-
|
|
|
|
| 150 |
with gr.Tab("📸 Image Editor"):
|
| 151 |
with gr.Row():
|
| 152 |
input_img = gr.Image(type="pil", label="Upload Main Photo")
|
| 153 |
|
| 154 |
with gr.Column():
|
| 155 |
bg_choices = gr.Dropdown(
|
| 156 |
-
choices=os.listdir(BG_DIR)
|
| 157 |
value=os.listdir(BG_DIR)[0] if os.listdir(BG_DIR) else None,
|
| 158 |
label="Choose Background"
|
| 159 |
)
|
| 160 |
-
|
| 161 |
-
bg_upload = gr.Image(type="pil", label="Upload Custom Background")
|
| 162 |
-
brand_color = gr.ColorPicker(label="Pick Brand Colour", value="#FFFFFF")
|
| 163 |
-
|
| 164 |
-
logo_upload = gr.Image(type="pil", label="Upload Brand Logo (Optional)")
|
| 165 |
-
logo_opacity = gr.Slider(minimum=0, maximum=100, value=80, step=5, label="Logo Transparency (%)")
|
| 166 |
-
logo_position = gr.Radio(
|
| 167 |
-
choices=["top-left", "top-right", "bottom-left", "bottom-right", "center"],
|
| 168 |
-
value="bottom-right",
|
| 169 |
-
label="Logo Position"
|
| 170 |
-
)
|
| 171 |
-
|
| 172 |
-
def update_preview(bg_choice):
|
| 173 |
-
return os.path.join(BG_DIR, bg_choice) if bg_choice in os.listdir(BG_DIR) else None
|
| 174 |
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
btn = gr.Button("✨ Generate New Photo")
|
| 178 |
output_imgs = gr.Gallery(label="Generated Image", elem_id="gallery", columns=1, rows=1)
|
|
|
|
| 179 |
btn.click(
|
| 180 |
fn=process_image,
|
| 181 |
-
inputs=[input_img, bg_choices, bg_upload,
|
| 182 |
outputs=output_imgs
|
| 183 |
)
|
| 184 |
|
| 185 |
-
# --- Caption Generator ---
|
| 186 |
-
with gr.Tab("✍️ Caption Generator"):
|
| 187 |
-
prompt = gr.Textbox(label="Enter product/promotion text", value="Promote my skincare product")
|
| 188 |
-
btn2 = gr.Button("💡 Suggest Captions + Hashtags")
|
| 189 |
-
caption_box = gr.Textbox(label="Suggested Posts (multi-platform)", lines=12)
|
| 190 |
-
btn2.click(fn=generate_caption, inputs=[prompt], outputs=[caption_box])
|
| 191 |
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
# START SERVER
|
| 194 |
# ---------------------------
|
| 195 |
if __name__ == "__main__":
|
|
@@ -200,3 +260,7 @@ if __name__ == "__main__":
|
|
| 200 |
|
| 201 |
threading.Thread(target=run_gradio).start()
|
| 202 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import time
|
| 3 |
from datetime import datetime
|
| 4 |
+
from typing import List
|
| 5 |
|
| 6 |
from fastapi import FastAPI, UploadFile, Form
|
| 7 |
from fastapi.responses import FileResponse, JSONResponse
|
|
|
|
| 14 |
|
| 15 |
from dotenv import load_dotenv
|
| 16 |
|
| 17 |
+
# Load environment variables from .env
|
| 18 |
load_dotenv()
|
| 19 |
+
|
| 20 |
+
# Confirm API key is loaded
|
| 21 |
+
print("Gemini API Key Loaded:", os.getenv("GOOGLE_API_KEY") is not None)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
|
| 25 |
# ---------------------------
|
| 26 |
# CONFIG
|
|
|
|
| 34 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 35 |
os.makedirs(RESULTS_DIR, exist_ok=True)
|
| 36 |
|
| 37 |
+
# Gemini API key
|
| 38 |
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
|
| 39 |
model = genai.GenerativeModel("gemini-1.5-flash")
|
| 40 |
|
|
|
|
| 53 |
os.remove(filepath)
|
| 54 |
raise ValueError(f"File too large! Max {MAX_SIZE_MB}MB allowed.")
|
| 55 |
|
| 56 |
+
def replace_background(input_path, bg_choice):
|
| 57 |
+
"""Replace background with selected file"""
|
| 58 |
+
check_size(input_path)
|
| 59 |
+
input_img = Image.open(input_path).convert("RGBA")
|
| 60 |
+
fg = remove(input_img)
|
| 61 |
+
|
| 62 |
+
bg_path = os.path.join(BG_DIR, bg_choice)
|
| 63 |
+
bg = Image.open(bg_path).convert("RGBA").resize(fg.size)
|
| 64 |
+
|
| 65 |
+
result = Image.alpha_composite(bg, fg)
|
| 66 |
+
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
| 67 |
+
result_path = os.path.join(RESULTS_DIR, f"result_{timestamp}.png")
|
| 68 |
+
result.save(result_path)
|
| 69 |
+
cleanup_old_files(RESULTS_DIR)
|
| 70 |
+
return result_path
|
| 71 |
+
|
| 72 |
+
def process_image(input_img, bg_choice, bg_upload, logo_upload, logo_transparency, logo_position, brand_color):
|
| 73 |
if input_img is None:
|
| 74 |
return []
|
| 75 |
|
| 76 |
temp_path = os.path.join(UPLOAD_DIR, f"upload_{int(time.time())}.png")
|
| 77 |
input_img.save(temp_path)
|
| 78 |
|
| 79 |
+
# Background selection
|
| 80 |
+
if bg_upload is not None:
|
| 81 |
+
bg = bg_upload.convert("RGBA").resize(input_img.size)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
else:
|
| 83 |
+
bg_path = os.path.join(BG_DIR, bg_choice)
|
| 84 |
+
bg = Image.open(bg_path).convert("RGBA").resize(input_img.size)
|
| 85 |
+
|
| 86 |
+
# Foreground (removed background)
|
| 87 |
+
fg = remove(input_img.convert("RGBA"))
|
| 88 |
|
| 89 |
+
# Merge main photo with background
|
| 90 |
result = Image.alpha_composite(bg, fg)
|
| 91 |
|
| 92 |
+
# If logo uploaded
|
| 93 |
if logo_upload is not None:
|
| 94 |
logo = logo_upload.convert("RGBA")
|
| 95 |
+
# Resize logo (20% of image width)
|
| 96 |
+
scale = result.width // 5
|
| 97 |
+
logo.thumbnail((scale, scale))
|
| 98 |
+
# Apply transparency
|
| 99 |
+
alpha = logo.split()[3].point(lambda p: p * (logo_transparency / 100))
|
| 100 |
logo.putalpha(alpha)
|
| 101 |
|
| 102 |
+
# Position logo
|
| 103 |
+
pos_map = {
|
| 104 |
+
"Top-Left": (10, 10),
|
| 105 |
+
"Top-Right": (result.width - logo.width - 10, 10),
|
| 106 |
+
"Bottom-Left": (10, result.height - logo.height - 10),
|
| 107 |
+
"Bottom-Right": (result.width - logo.width - 10, result.height - logo.height - 10),
|
| 108 |
+
"Center": ((result.width - logo.width) // 2, (result.height - logo.height) // 2),
|
| 109 |
+
}
|
| 110 |
+
result.paste(logo, pos_map[logo_position], logo)
|
| 111 |
+
|
| 112 |
+
# Apply brand colour tint
|
| 113 |
+
if brand_color:
|
| 114 |
+
tint = Image.new("RGBA", result.size, brand_color + "20") # ~12% opacity overlay
|
| 115 |
+
result = Image.alpha_composite(result, tint)
|
| 116 |
+
|
| 117 |
+
# Save result
|
| 118 |
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
| 119 |
result_path = os.path.join(RESULTS_DIR, f"result_{timestamp}.png")
|
| 120 |
result.save(result_path)
|
| 121 |
cleanup_old_files(RESULTS_DIR)
|
| 122 |
+
return [result_path]
|
| 123 |
|
| 124 |
+
|
| 125 |
+
'''
|
| 126 |
+
def process_image(input_img, bg_choice):
|
| 127 |
+
if input_img is None:
|
| 128 |
+
return []
|
| 129 |
+
temp_path = os.path.join(UPLOAD_DIR, f"upload_{int(time.time())}.png")
|
| 130 |
+
input_img.save(temp_path)
|
| 131 |
+
result_path = replace_background(temp_path, bg_choice)
|
| 132 |
return [result_path]
|
| 133 |
+
'''
|
| 134 |
|
| 135 |
def generate_caption(prompt="Promote my product"):
|
| 136 |
try:
|
|
|
|
| 149 |
# ---------------------------
|
| 150 |
app = FastAPI(title="SnapLift API")
|
| 151 |
|
| 152 |
+
# Allow CORS for mobile app access
|
| 153 |
app.add_middleware(
|
| 154 |
CORSMiddleware,
|
| 155 |
+
allow_origins=["*"], # Change later for security
|
| 156 |
allow_credentials=True,
|
| 157 |
allow_methods=["*"],
|
| 158 |
allow_headers=["*"],
|
|
|
|
| 164 |
input_path = os.path.join(UPLOAD_DIR, file.filename)
|
| 165 |
with open(input_path, "wb") as f:
|
| 166 |
f.write(await file.read())
|
| 167 |
+
|
| 168 |
+
result_path = replace_background(input_path, bg_choice)
|
| 169 |
+
return FileResponse(result_path)
|
| 170 |
except Exception as e:
|
| 171 |
return JSONResponse(content={"error": str(e)}, status_code=400)
|
| 172 |
|
|
|
|
| 182 |
gr.Markdown("# ✨ SnapLift – AI Social Media Booster")
|
| 183 |
gr.Markdown("Upload your product photo, replace background, and auto-generate marketing captions + hashtags!")
|
| 184 |
|
| 185 |
+
|
| 186 |
+
# Image Editor Tab
|
| 187 |
with gr.Tab("📸 Image Editor"):
|
| 188 |
with gr.Row():
|
| 189 |
input_img = gr.Image(type="pil", label="Upload Main Photo")
|
| 190 |
|
| 191 |
with gr.Column():
|
| 192 |
bg_choices = gr.Dropdown(
|
| 193 |
+
choices=os.listdir(BG_DIR),
|
| 194 |
value=os.listdir(BG_DIR)[0] if os.listdir(BG_DIR) else None,
|
| 195 |
label="Choose Background"
|
| 196 |
)
|
| 197 |
+
bg_upload = gr.Image(type="pil", label="Or Upload Your Background (Optional)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
|
| 199 |
+
# Logo + Branding Controls
|
| 200 |
+
with gr.Row():
|
| 201 |
+
logo_upload = gr.Image(type="pil", label="Upload Logo (Optional)")
|
| 202 |
+
logo_transparency = gr.Slider(0, 100, value=70, label="Logo Transparency (%)")
|
| 203 |
+
logo_position = gr.Dropdown(
|
| 204 |
+
["Top-Left", "Top-Right", "Bottom-Left", "Bottom-Right", "Center"],
|
| 205 |
+
value="Bottom-Right",
|
| 206 |
+
label="Logo Position"
|
| 207 |
+
)
|
| 208 |
+
brand_color = gr.ColorPicker(label="Brand Colour")
|
| 209 |
|
| 210 |
btn = gr.Button("✨ Generate New Photo")
|
| 211 |
output_imgs = gr.Gallery(label="Generated Image", elem_id="gallery", columns=1, rows=1)
|
| 212 |
+
|
| 213 |
btn.click(
|
| 214 |
fn=process_image,
|
| 215 |
+
inputs=[input_img, bg_choices, bg_upload, logo_upload, logo_transparency, logo_position, brand_color],
|
| 216 |
outputs=output_imgs
|
| 217 |
)
|
| 218 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
|
| 220 |
+
|
| 221 |
+
'''
|
| 222 |
+
# Image Editor Tab
|
| 223 |
+
with gr.Tab("📸 Image Editor"):
|
| 224 |
+
with gr.Row():
|
| 225 |
+
input_img = gr.Image(type="pil", label="Upload Photo")
|
| 226 |
+
|
| 227 |
+
with gr.Column():
|
| 228 |
+
bg_choices = gr.Dropdown(
|
| 229 |
+
choices=os.listdir(BG_DIR),
|
| 230 |
+
value=os.listdir(BG_DIR)[0] if os.listdir(BG_DIR) else None,
|
| 231 |
+
label="Choose Background"
|
| 232 |
+
)
|
| 233 |
+
bg_preview = gr.Image(
|
| 234 |
+
label="Background Preview",
|
| 235 |
+
type="filepath"
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
# Update preview whenever background changes
|
| 239 |
+
def update_preview(bg_choice):
|
| 240 |
+
return os.path.join(BG_DIR, bg_choice) if bg_choice else None
|
| 241 |
+
|
| 242 |
+
bg_choices.change(fn=update_preview, inputs=bg_choices, outputs=bg_preview)
|
| 243 |
+
|
| 244 |
+
btn = gr.Button("✨ Generate New Photo")
|
| 245 |
+
output_imgs = gr.Gallery(label="Generated Image", elem_id="gallery", columns=1, rows=1)
|
| 246 |
+
btn.click(fn=process_image, inputs=[input_img, bg_choices], outputs=output_imgs)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
'''
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
|
| 253 |
# START SERVER
|
| 254 |
# ---------------------------
|
| 255 |
if __name__ == "__main__":
|
|
|
|
| 260 |
|
| 261 |
threading.Thread(target=run_gradio).start()
|
| 262 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|
| 263 |
+
|
| 264 |
+
threading.Thread(target=start_fastapi, daemon=True).start()
|
| 265 |
+
|
| 266 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|