AkashKumarave commited on
Commit
cd42783
Β·
verified Β·
1 Parent(s): 5907f9e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +190 -100
app.py CHANGED
@@ -1,108 +1,198 @@
1
- import gradio as gr
2
- import requests
3
- from pathlib import Path
4
- import base64
5
  import io
 
 
 
 
 
6
 
7
- # Kling AI API configuration (keys hardcoded as requested)
8
- KLING_ACCESS_KEY_ID = "AGBGmadNd9hakFYfahytyQQJtN8CJmDJ"
9
- KLING_ACCESS_KEY_SECRET = "dp3pAe4PpdmnAHCAPgEd3PyLmBQrkMde"
10
- KLING_API_URL = "https://api-singapore.klingai.com/v1/image-to-video" # Assumed endpoint for image-to-image video generation
11
-
12
- def generate_video(image, prompt="", negative_prompt="", duration=5, aspect_ratio="16:9"):
13
- """
14
- Call Kling AI API to generate a video from an input image.
15
-
16
- Args:
17
- image: Uploaded image file (from Gradio)
18
- prompt (str): Optional text prompt to guide video generation
19
- negative_prompt (str): Optional negative prompt
20
- duration (int): Video duration in seconds (5 or 10)
21
- aspect_ratio (str): Aspect ratio (e.g., '16:9')
22
-
23
- Returns:
24
- str: Path to the downloaded video or error message
25
- """
26
- # Convert image to base64 for API submission
27
- try:
28
- with open(image, "rb") as img_file:
29
- image_base64 = base64.b64encode(img_file.read()).decode("utf-8")
30
- except Exception as e:
31
- return f"Error: Failed to process input image. Details: {str(e)}"
32
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  headers = {
34
- "Authorization": f"Bearer {KLING_ACCESS_KEY_SECRET}",
35
- "X-Kling-Access-Key-Id": KLING_ACCESS_KEY_ID,
36
- "Content-Type": "application/json"
37
  }
38
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  payload = {
40
- "image": image_base64,
41
- "prompt": prompt,
42
- "negative_prompt": negative_prompt,
43
- "duration": duration,
44
- "aspect_ratio": aspect_ratio
45
  }
46
-
 
 
 
 
 
 
 
 
 
 
 
 
47
  try:
48
- response = requests.post(KLING_API_URL, json=payload, headers=headers, timeout=60)
49
- response.raise_for_status()
50
-
51
- # Assuming API returns a JSON with a video URL
52
- data = response.json()
53
- video_url = data.get("video_url") # Adjust based on actual API response
54
-
55
- if not video_url:
56
- return "Error: No video URL returned by the API."
57
-
58
- # Download the video
59
- video_response = requests.get(video_url, timeout=30)
60
- video_response.raise_for_status()
61
-
62
- # Save video to temporary file
63
- output_path = Path("output_video.mp4")
64
- with open(output_path, "wb") as f:
65
- f.write(video_response.content)
66
-
67
- return str(output_path)
68
-
69
- except requests.exceptions.RequestException as e:
70
- return f"Error: Failed to generate video. Details: {str(e)}"
71
-
72
- def chatbot_interface(image, prompt, negative_prompt, duration, aspect_ratio):
73
- """
74
- Gradio interface for user inputs and video output.
75
-
76
- Args:
77
- image: Uploaded image file
78
- prompt (str): Optional text prompt
79
- negative_prompt (str): Negative prompt
80
- duration (int): Video duration
81
- aspect_ratio (str): Aspect ratio
82
-
83
- Returns:
84
- Video file path or error message
85
- """
86
- if not image:
87
- return "Error: Please upload an image."
88
-
89
- return generate_video(image, prompt, negative_prompt, duration, aspect_ratio)
90
-
91
- # Define Gradio interface
92
- iface = gr.Interface(
93
- fn=chatbot_interface,
94
- inputs=[
95
- gr.Image(type="filepath", label="Input Image"),
96
- gr.Textbox(lines=2, placeholder="Enter an optional text prompt...", label="Prompt"),
97
- gr.Textbox(lines=2, placeholder="Enter negative prompt (optional)", label="Negative Prompt"),
98
- gr.Slider(minimum=5, maximum=10, step=5, value=5, label="Video Duration (seconds)"),
99
- gr.Dropdown(choices=["16:9", "4:3", "1:1"], value="16:9", label="Aspect Ratio")
100
- ],
101
- outputs=gr.Video(label="Generated Video"),
102
- title="Kling AI Image-to-Video Generator",
103
- description="Generate videos from an input image using Kling AI API. Upload an image, optionally add a prompt, and set parameters."
104
- )
105
-
106
- # Launch the interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  if __name__ == "__main__":
108
- iface.launch(server_name="0.0.0.0", server_port=7860)
 
1
+ import os
 
 
 
2
  import io
3
+ import time
4
+ import base64
5
+ import requests
6
+ from PIL import Image
7
+ import gradio as gr
8
 
9
+ # -------------------------------------------------------------------
10
+ # CONFIG (via Secrets in your HF Space)
11
+ # -------------------------------------------------------------------
12
+ # Required: your Kling secret key. In HF: Settings β†’ Secrets β†’ add KLING_SECRET_KEY
13
+ KLING_SECRET_KEY = os.getenv("KLING_SECRET_KEY", "").strip()
14
+
15
+ # One of these should be set:
16
+ # 1) If you have a proxy (e.g., your Supabase Edge Function):
17
+ # e.g. https://rzbyjtariqsnhgwfxdxa.supabase.co/functions/v1/klingai-proxy
18
+ KLING_PROXY_URL = os.getenv("KLING_PROXY_URL", "").strip()
19
+
20
+ # 2) Or, if you want to call Kling API directly, set this to the base URL they provide.
21
+ # Example placeholder (replace with the *real* one if you have it):
22
+ KLING_API_URL = os.getenv("KLING_API_URL", "").strip()
23
+
24
+ # Optional tuning defaults
25
+ DEFAULT_SIZE = "1024x1024"
26
+ TIMEOUT_SEC = 120 # request timeout
27
+ POLL_DELAY = 2 # for any status polling, if your proxy supports it
28
+
29
+ # -------------------------------------------------------------------
30
+ # Helpers
31
+ # -------------------------------------------------------------------
32
+ def _b64_image_from_pil(img: Image.Image) -> str:
33
+ buf = io.BytesIO()
34
+ img.save(buf, format="PNG")
35
+ return base64.b64encode(buf.getvalue()).decode("utf-8")
36
+
37
+ def _pil_from_b64(b64_str: str) -> Image.Image:
38
+ raw = base64.b64decode(b64_str)
39
+ return Image.open(io.BytesIO(raw)).convert("RGBA").convert("RGB")
40
+
41
+ def _ensure_ready():
42
+ if not KLING_SECRET_KEY:
43
+ raise RuntimeError(
44
+ "Missing KLING_SECRET_KEY. In your Hugging Face Space, go to Settings β†’ Secrets and add it."
45
+ )
46
+ if not (KLING_PROXY_URL or KLING_API_URL):
47
+ raise RuntimeError(
48
+ "Set either KLING_PROXY_URL (your proxy) OR KLING_API_URL (direct API) in Settings β†’ Secrets."
49
+ )
50
+
51
+ def _post_json(url: str, json_body: dict):
52
  headers = {
53
+ "Authorization": f"Bearer {KLING_SECRET_KEY}",
54
+ "Content-Type": "application/json",
 
55
  }
56
+ resp = requests.post(url, json=json_body, headers=headers, timeout=TIMEOUT_SEC)
57
+ # Try to provide helpful error info
58
+ if resp.status_code // 100 != 2:
59
+ try:
60
+ msg = resp.json()
61
+ except Exception:
62
+ msg = resp.text
63
+ raise RuntimeError(f"HTTP {resp.status_code}: {msg}")
64
+ return resp.json()
65
+
66
+ # -------------------------------------------------------------------
67
+ # Unified call: works with your proxy or direct API
68
+ # Expected proxy/ API contract:
69
+ # Request JSON:
70
+ # {
71
+ # "mode": "txt2img" | "img2img",
72
+ # "prompt": "...",
73
+ # "size": "1024x1024",
74
+ # "seed": 0,
75
+ # "strength": 0.8, # only for img2img
76
+ # "image_base64": "<PNG b64>" # only for img2img
77
+ # }
78
+ # Response JSON (synchronous):
79
+ # { "image_base64": "<PNG b64>" }
80
+ # OR (async job pattern):
81
+ # { "job_id": "..." } then a GET to /result?job_id=... returns same {image_base64: ...}
82
+ # Adjust if your proxy differs.
83
+ # -------------------------------------------------------------------
84
+ def kling_generate(prompt: str,
85
+ size: str,
86
+ seed: int,
87
+ strength: float,
88
+ init_image: Image.Image | None):
89
+ _ensure_ready()
90
+ mode = "img2img" if init_image is not None else "txt2img"
91
+
92
  payload = {
93
+ "mode": mode,
94
+ "prompt": (prompt or "").strip(),
95
+ "size": size or DEFAULT_SIZE,
96
+ "seed": int(seed) if seed is not None else 0,
 
97
  }
98
+
99
+ if mode == "img2img":
100
+ payload["strength"] = float(strength)
101
+ payload["image_base64"] = _b64_image_from_pil(init_image)
102
+
103
+ # Decide endpoint
104
+ base = KLING_PROXY_URL or KLING_API_URL
105
+ # Two possible styles:
106
+ # - single endpoint that handles both modes
107
+ # - separate endpoints per mode
108
+ # Here we use a single generic /generate endpoint for simplicity:
109
+ gen_url = base.rstrip("/") + "/generate"
110
+
111
  try:
112
+ data = _post_json(gen_url, payload)
113
+ except Exception as e:
114
+ # If your proxy uses job polling, try a second path:
115
+ # On non-2xx, we just bubble up error. You can customize below.
116
+ raise
117
+
118
+ # Handle both sync and async contract
119
+ if "image_base64" in data:
120
+ return _pil_from_b64(data["image_base64"])
121
+
122
+ # Async job fallback
123
+ job_id = data.get("job_id")
124
+ if not job_id:
125
+ raise RuntimeError("Unexpected response. Neither 'image_base64' nor 'job_id' found.")
126
+
127
+ # Polling
128
+ result_url = base.rstrip("/") + "/result"
129
+ started = time.time()
130
+ while True:
131
+ if time.time() - started > TIMEOUT_SEC:
132
+ raise RuntimeError("Generation timeout. Try smaller size or check your proxy/API.")
133
+ try:
134
+ r = requests.get(result_url, params={"job_id": job_id}, timeout=TIMEOUT_SEC)
135
+ if r.status_code // 100 != 2:
136
+ time.sleep(POLL_DELAY)
137
+ continue
138
+ jd = r.json()
139
+ if "status" in jd and jd["status"] in ("queued", "running"):
140
+ time.sleep(POLL_DELAY)
141
+ continue
142
+ if "image_base64" in jd:
143
+ return _pil_from_b64(jd["image_base64"])
144
+ # If API returns error shape
145
+ if "error" in jd:
146
+ raise RuntimeError(str(jd["error"]))
147
+ except Exception:
148
+ time.sleep(POLL_DELAY)
149
+
150
+ # -------------------------------------------------------------------
151
+ # Gradio UI
152
+ # -------------------------------------------------------------------
153
+ with gr.Blocks(title="Kling AI β€” Image Generator") as demo:
154
+ gr.Markdown(
155
+ """
156
+ # Kling AI β€” Image & Image-to-Image
157
+ - Add your **KLING_SECRET_KEY** in Space **Settings β†’ Secrets**.
158
+ - Set either **KLING_PROXY_URL** (recommended) or **KLING_API_URL**.
159
+ """
160
+ )
161
+
162
+ with gr.Row():
163
+ with gr.Column():
164
+ prompt = gr.Textbox(
165
+ label="Prompt",
166
+ placeholder="Describe the image you want...",
167
+ lines=4
168
+ )
169
+ init_image = gr.Image(label="Init Image (optional for img2img)", type="pil")
170
+ strength = gr.Slider(0.0, 1.0, value=0.8, step=0.05, label="Strength (img2img only)")
171
+ size = gr.Dropdown(
172
+ ["512x512", "768x768", "1024x1024", "1024x1536", "1536x1024"],
173
+ value=DEFAULT_SIZE, label="Size"
174
+ )
175
+ seed = gr.Number(value=0, precision=0, label="Seed (0 = random)")
176
+
177
+ btn = gr.Button("Generate", variant="primary")
178
+
179
+ with gr.Column():
180
+ out = gr.Image(label="Output", type="pil")
181
+
182
+ def _on_click(prompt, size, seed, strength, init_image):
183
+ if not prompt and init_image is None:
184
+ raise gr.Error("Please enter a prompt or provide an init image.")
185
+ try:
186
+ img = kling_generate(prompt, size, int(seed), float(strength), init_image)
187
+ return img
188
+ except Exception as e:
189
+ raise gr.Error(str(e))
190
+
191
+ btn.click(
192
+ _on_click,
193
+ inputs=[prompt, size, seed, strength, init_image],
194
+ outputs=[out]
195
+ )
196
+
197
  if __name__ == "__main__":
198
+ demo.launch()