AkashKumarave commited on
Commit
01117dd
·
verified ·
1 Parent(s): de086e0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +200 -82
app.py CHANGED
@@ -1,91 +1,209 @@
1
- # app.py
2
- from flask import Flask, request, jsonify
3
- import razorpay
 
 
 
4
  import os
5
- import hmac
6
- import hashlib
7
- import json
8
-
9
- app = Flask(__name__)
10
-
11
- # Get keys from Hugging Face Secrets (Settings -> Variables)
12
- RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID", "")
13
- RAZORPAY_KEY_SECRET = os.getenv("RAZORPAY_KEY_SECRET", "")
14
-
15
- if not RAZORPAY_KEY_ID or not RAZORPAY_KEY_SECRET:
16
- print("WARNING: Razorpay keys not found in env. Set RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET in Space secrets.")
17
-
18
- client = razorpay.Client(auth=(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET))
19
-
20
- def cors_json(resp):
21
- # Helper to add CORS headers to Flask response dict
22
- response = jsonify(resp)
23
- response.headers.add("Access-Control-Allow-Origin", "*")
24
- response.headers.add("Access-Control-Allow-Headers", "Content-Type")
25
- return response
26
-
27
- @app.route("/create-order", methods=["POST", "OPTIONS"])
28
- def create_order():
29
- # Preflight
30
- if request.method == "OPTIONS":
31
- response = app.response_class(status=204)
32
- response.headers.add("Access-Control-Allow-Origin", "*")
33
- response.headers.add("Access-Control-Allow-Headers", "Content-Type")
34
- return response
35
-
36
- data = request.get_json(force=True) or {}
37
- amount = int(data.get("amount", 0)) # amount in rupees from frontend
38
- if amount <= 0:
39
- return cors_json({"error": "Invalid amount"}), 400
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  try:
42
- options = {
43
- "amount": amount * 100, # paise
44
- "currency": "INR",
45
- "receipt": f"receipt_{os.urandom(4).hex()}",
46
- "payment_capture": 1
47
- }
48
- order = client.order.create(options)
49
- return cors_json(order)
50
  except Exception as e:
51
- return cors_json({"error": str(e)}), 500
52
-
53
- @app.route("/verify-payment", methods=["POST", "OPTIONS"])
54
- def verify_payment():
55
- # Verifies signature from Razorpay checkout
56
- if request.method == "OPTIONS":
57
- response = app.response_class(status=204)
58
- response.headers.add("Access-Control-Allow-Origin", "*")
59
- response.headers.add("Access-Control-Allow-Headers", "Content-Type")
60
- return response
61
-
62
- data = request.get_json(force=True) or {}
63
  try:
64
- razorpay_order_id = data.get("razorpay_order_id")
65
- razorpay_payment_id = data.get("razorpay_payment_id")
66
- razorpay_signature = data.get("razorpay_signature")
67
-
68
- if not (razorpay_order_id and razorpay_payment_id and razorpay_signature):
69
- return cors_json({"success": False, "message": "Missing fields"}), 400
70
-
71
- payload = f"{razorpay_order_id}|{razorpay_payment_id}"
72
- generated_signature = hmac.new(
73
- RAZORPAY_KEY_SECRET.encode("utf-8"),
74
- payload.encode("utf-8"),
75
- hashlib.sha256
76
- ).hexdigest()
77
-
78
- if generated_signature == razorpay_signature:
79
- return cors_json({"success": True})
80
- else:
81
- return cors_json({"success": False, "message": "Invalid signature"}), 400
82
  except Exception as e:
83
- return cors_json({"success": False, "message": str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- @app.route("/health", methods=["GET"])
86
- def health():
87
- return cors_json({"ok": True, "app": "razorpay-backend"})
 
88
 
89
  if __name__ == "__main__":
90
- # Hugging Face Spaces runs on port 7860
91
- app.run(host="0.0.0.0", port=7860)
 
 
1
+ import logging
2
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Form
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.responses import FileResponse
5
+ import requests
6
+ import base64
7
  import os
8
+ import time
9
+ import jwt
10
+ from pathlib import Path
11
+ from typing import List, Optional
12
+ from pydantic import BaseModel
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ # Configure logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # ===== API CONFIGURATION =====
19
+ # Load sensitive data from environment variables
20
+ ACCESS_KEY_ID = os.getenv("ACCESS_KEY_ID", "AFyHfnQATghFdCMyAG3gRPbNY4TNKFGB")
21
+ ACCESS_KEY_SECRET = os.getenv("ACCESS_KEY_SECRET", "TTepeLyBterLNM3brYPGmdndBnnyKJBA")
22
+ API_BASE_URL = "https://api-singapore.klingai.com"
23
+ CREATE_TASK_ENDPOINT = f"{API_BASE_URL}/v1/images/multi-image2image"
24
+
25
+ # Initialize FastAPI app
26
+ app = FastAPI(title="Kling AI Multi-Image Generator API")
27
+
28
+ # Enable CORS for the frontend
29
+ app.add_middleware(
30
+ CORSMiddleware,
31
+ allow_origins=["https://hivili.web.app"], # Allow your frontend
32
+ allow_credentials=True,
33
+ allow_methods=["*"],
34
+ allow_headers=["*"],
35
+ )
36
+
37
+ # ===== AUTHENTICATION =====
38
+ def generate_jwt_token():
39
+ """Generate JWT token for API authentication"""
40
+ payload = {
41
+ "iss": ACCESS_KEY_ID,
42
+ "exp": int(time.time()) + 1800, # 30 minutes expiration
43
+ "nbf": int(time.time()) - 5 # Not before 5 seconds ago
44
+ }
45
+ return jwt.encode(payload, ACCESS_KEY_SECRET, algorithm="HS256")
46
+
47
+ # ===== IMAGE PROCESSING =====
48
+ def prepare_image_base64(image_content: bytes):
49
+ """Convert image bytes to base64 without prefix"""
50
  try:
51
+ return base64.b64encode(image_content).decode('utf-8')
 
 
 
 
 
 
 
52
  except Exception as e:
53
+ logger.error(f"Image processing failed: {str(e)}")
54
+ return None
55
+
56
+ def validate_image(image_content: bytes):
57
+ """Validate image meets API requirements"""
 
 
 
 
 
 
 
58
  try:
59
+ # Check file size
60
+ size_mb = len(image_content) / (1024 * 1024)
61
+ if size_mb > 10:
62
+ return False, "Image too large (max 10MB)"
63
+ # Basic validation (add PIL-based dimension checks if needed)
64
+ return True, ""
 
 
 
 
 
 
 
 
 
 
 
 
65
  except Exception as e:
66
+ return False, f"Image validation error: {str(e)}"
67
+
68
+ # ===== API FUNCTIONS =====
69
+ def create_multi_image_task(subject_images: List[bytes], prompt: str):
70
+ """Create multi-image generation task"""
71
+ headers = {
72
+ "Authorization": f"Bearer {generate_jwt_token()}",
73
+ "Content-Type": "application/json"
74
+ }
75
+
76
+ # Prepare subject images list
77
+ subject_image_list = []
78
+ for img_content in subject_images:
79
+ if img_content:
80
+ base64_img = prepare_image_base64(img_content)
81
+ if base64_img:
82
+ subject_image_list.append({"subject_image": base64_img})
83
+
84
+ if len(subject_image_list) < 2:
85
+ return None, "At least 2 subject images required"
86
+
87
+ payload = {
88
+ "model_name": "kling-v2",
89
+ "prompt": prompt,
90
+ "subject_image_list": subject_image_list,
91
+ "n": 1,
92
+ "aspect_ratio": "1:1"
93
+ }
94
+
95
+ try:
96
+ response = requests.post(CREATE_TASK_ENDPOINT, json=payload, headers=headers)
97
+ response.raise_for_status()
98
+ return response.json(), None
99
+ except requests.exceptions.RequestException as e:
100
+ logger.error(f"API request failed: {str(e)}")
101
+ if hasattr(e, 'response') and e.response:
102
+ logger.error(f"API response: {e.response.text}")
103
+ return None, f"API Error: {str(e)}"
104
+
105
+ def check_task_status(task_id: str):
106
+ """Check task completion status"""
107
+ headers = {"Authorization": f"Bearer {generate_jwt_token()}"}
108
+ status_url = f"{API_BASE_URL}/v1/images/multi-image2image/{task_id}"
109
+
110
+ try:
111
+ response = requests.get(status_url, headers=headers)
112
+ response.raise_for_status()
113
+ return response.json(), None
114
+ except requests.exceptions.RequestException as e:
115
+ return None, f"Status check failed: {str(e)}"
116
+
117
+ # ===== MAIN PROCESSING =====
118
+ async def generate_image(subject_images: List[bytes], prompt: str):
119
+ """Handle complete image generation workflow"""
120
+ # Validate images
121
+ for img_content in subject_images:
122
+ if img_content:
123
+ is_valid, error_msg = validate_image(img_content)
124
+ if not is_valid:
125
+ return None, error_msg
126
+
127
+ # Create task
128
+ task_response, error = create_multi_image_task(subject_images, prompt)
129
+ if error:
130
+ return None, error
131
+
132
+ if task_response.get("code") != 0:
133
+ return None, f"API error: {task_response.get('message', 'Unknown error')}"
134
+
135
+ task_id = task_response["data"]["task_id"]
136
+ logger.info(f"Task created: {task_id}")
137
+
138
+ # Poll for results (max 10 minutes)
139
+ for _ in range(60):
140
+ task_data, error = check_task_status(task_id)
141
+ if error:
142
+ return None, error
143
+
144
+ status = task_data["data"]["task_status"]
145
+
146
+ if status == "succeed":
147
+ image_url = task_data["data"]["task_result"]["images"][0]["url"]
148
+ try:
149
+ response = requests.get(image_url)
150
+ response.raise_for_status()
151
+ output_dir = Path("/tmp") # Hugging Face Spaces uses /tmp for ephemeral storage
152
+ output_dir.mkdir(exist_ok=True)
153
+ output_path = output_dir / f"kling_output_{task_id}.png"
154
+ with open(output_path, "wb") as f:
155
+ f.write(response.content)
156
+ return str(output_path), None
157
+ except Exception as e:
158
+ return None, f"Failed to download result: {str(e)}"
159
+
160
+ elif status in ("failed", "canceled"):
161
+ error_msg = task_data["data"].get("task_status_msg", "Unknown error")
162
+ return None, f"Task failed: {error_msg}"
163
+
164
+ time.sleep(10)
165
+
166
+ return None, "Task timed out after 10 minutes"
167
+
168
+ # ===== API ENDPOINTS =====
169
+ class GenerateImageRequest(BaseModel):
170
+ prompt: str
171
+
172
+ @app.post("/generate")
173
+ async def generate_image_endpoint(
174
+ prompt: str = Form(...),
175
+ images: List[UploadFile] = File(...)
176
+ ):
177
+ """Endpoint to generate an image from multiple input images and a prompt"""
178
+ if len(images) < 2:
179
+ raise HTTPException(status_code=400, detail="At least 2 images are required")
180
+ if len(images) > 4:
181
+ raise HTTPException(status_code=400, detail="Maximum 4 images allowed")
182
+
183
+ # Read image contents
184
+ image_contents = []
185
+ for image in images:
186
+ content = await image.read()
187
+ image_contents.append(content)
188
+
189
+ # Generate image
190
+ output_path, error = await generate_image(image_contents, prompt)
191
+ if error:
192
+ raise HTTPException(status_code=500, detail=error)
193
+
194
+ # Return the generated image file
195
+ return FileResponse(
196
+ path=output_path,
197
+ media_type="image/png",
198
+ filename=f"kling_output_{Path(output_path).stem}.png"
199
+ )
200
 
201
+ # Health check endpoint
202
+ @app.get("/health")
203
+ async def health_check():
204
+ return {"status": "healthy"}
205
 
206
  if __name__ == "__main__":
207
+ import uvicorn
208
+ # Hugging Face Spaces expects port 7860
209
+ uvicorn.run(app, host="0.0.0.0", port=7860)