DranikKartofelniy commited on
Commit
4978487
·
verified ·
1 Parent(s): abd66fb

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +133 -73
  2. requirements.txt +2 -1
app.py CHANGED
@@ -7,6 +7,7 @@ import jwt
7
  import io
8
  from PIL import Image
9
 
 
10
  try:
11
  from google import genai
12
  from google.genai import types
@@ -16,26 +17,65 @@ except ImportError as e:
16
  HAS_GENAI = False
17
  GENAI_ERR = str(e)
18
 
 
 
 
 
 
 
 
19
  # --- SECRETS AND CONFIGURATION ---
20
  GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
21
  KLING_ACCESS_KEY = os.getenv("KLING_ACCESS_KEY")
22
  KLING_SECRET_KEY = os.getenv("KLING_SECRET_KEY")
 
23
 
24
- # --- ОБНОВЛЕННЫЙ ПРОМПТ ДЛЯ ФОТО (Banana/GenAI) ---
25
  SYSTEM_PROMPT = "Hyper-realistic Instagram influencer lifestyle photo, shot on iPhone 15 Pro. The exact person [refer to subject images] is looking directly at the camera with a warm, highly attractive, and confident smile showing natural bright white teeth. Healthy, clear, highly realistic skin texture (natural, not overly airbrushed, but free of blemishes or rough spots, with a soft flattering glow). They are casually holding the product [refer to product images] close to the camera, showing it to the viewers. Flattering soft daylight from a window combined with a subtle ambient glow on the face. The background is a stylish, slightly eclectic modern loft with unique decor, lush indoor plants, and rich textures, giving a lived-in, creative, and non-generic vibe. Authentic social media blogger aesthetic, exact facial likeness, highly detailed, and visually pleasing."
26
 
27
  def generate_kling_token(ak, sk):
28
- headers = {
29
- "alg": "HS256",
30
- "typ": "JWT"
31
- }
32
- payload = {
33
- "iss": ak,
34
- "exp": int(time.time()) + 1800,
35
- "nbf": int(time.time()) - 5
36
- }
37
  return jwt.encode(payload, sk, algorithm="HS256", headers=headers)
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  def create_photo(blogger_images, product_images):
40
  logs = ""
41
  if not blogger_images or not product_images:
@@ -65,7 +105,7 @@ def create_photo(blogger_images, product_images):
65
  yield logs, None
66
 
67
  client = genai.Client(api_key=GOOGLE_API_KEY)
68
- contents = [SYSTEM_PROMPT]
69
 
70
  for img in blogger_images:
71
  path = img if isinstance(img, str) else img.name
@@ -112,53 +152,51 @@ def create_photo(blogger_images, product_images):
112
 
113
  def create_video(generated_photo_path):
114
  logs = ""
115
- # Защита от случайного нажатия до генерации фото
 
116
  if not generated_photo_path:
117
  raise gr.Error("Please click 'Create Photo' first to generate an image for the video!")
118
 
119
- logs += "[System] Initializing Kling AI video generation...\n"
120
- yield logs, None
121
- time.sleep(1)
 
122
 
123
- # DEMO FALLBACK
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  if not KLING_ACCESS_KEY or not KLING_SECRET_KEY:
125
  logs += "[Warning] Kling API Keys are not set! Running in Demo Fallback mode...\n"
126
- yield logs, None
127
- time.sleep(1)
128
- logs += "[Kling API] POST /v1/videos/image2video -> Sending data...\n"
129
- yield logs, None
130
- time.sleep(1.5)
131
- logs += "[Kling API] Task ID: kl_demo_98765. Status: SUBMITTED...\n"
132
- yield logs, None
133
  time.sleep(2)
134
- logs += "[Kling API] Status: PROCESSING...\n"
135
- yield logs, None
136
- time.sleep(2.5)
137
  logs += "[Success] Status: SUCCEED. Video is ready!\n"
138
- yield logs, "https://www.w3schools.com/html/mov_bbb.mp4"
139
  return
140
 
141
- # REAL API CALL
142
  try:
143
- logs += "[Security] Generating 30-min JWT Token using Access & Secret Keys...\n"
144
- yield logs, None
145
  api_token = generate_kling_token(KLING_ACCESS_KEY, KLING_SECRET_KEY)
146
 
147
- # --- БЕРЕМ СГЕНЕРИРОВАННОЕ ФОТО ИЗ UI ---
148
- logs += "[Storage] Loading generated photo from UI...\n"
149
- yield logs, None
150
-
151
- img_obj = Image.open(generated_photo_path)
152
- if img_obj.mode != 'RGB':
153
- img_obj = img_obj.convert('RGB')
154
-
155
- buffered = io.BytesIO()
156
- img_obj.save(buffered, format="JPEG", quality=95)
157
- base64_image = base64.b64encode(buffered.getvalue()).decode('utf-8')
158
-
159
- logs += "[Storage] Generated image prepared for Kling AI.\n"
160
- yield logs, None
161
-
162
  headers = {
163
  "Authorization": f"Bearer {api_token}",
164
  "Content-Type": "application/json"
@@ -166,16 +204,16 @@ def create_video(generated_photo_path):
166
 
167
  payload = {
168
  "model_name": "kling-v2-6",
169
- "image": base64_image,
170
- "prompt": "Natural handheld UGC-style video of a young man holding a small WD-40 spray can in his hand, speaking casually to camera like filming a story at home. The camera is front-facing smartphone style, slight natural hand movement, subtle micro-shakes. He slightly tilts the can toward the camera to show the label, rotates it a bit, then looks back at the lens and smiles while talking. Soft daylight from window, cozy apartment background, shallow depth of field. Realistic facial expressions, natural blinking, small head movements, authentic body language. The product remains clearly visible in frame. Casual authentic vibe, not overacted, not commercial studio style.",
171
- "negative_prompt": "blur, distort, low quality, bad anatomy",
172
  "duration": "5",
173
  "mode": "pro",
174
  "sound": "off"
175
  }
176
 
177
  logs += f"[Kling API] POST /v1/videos/image2video -> Starting task...\n"
178
- yield logs, None
179
 
180
  post_url = "https://api-singapore.klingai.com/v1/videos/image2video"
181
  response = requests.post(post_url, headers=headers, json=payload)
@@ -184,12 +222,12 @@ def create_video(generated_photo_path):
184
  if response.status_code != 200 or resp_json.get("code") != 0:
185
  error_msg = resp_json.get('message', 'Unknown error')
186
  logs += f"[Error] API Creation Failed: {error_msg}\n"
187
- yield logs, None
188
  return
189
 
190
  task_id = resp_json.get("data", {}).get("task_id")
191
  logs += f"[Kling API] Task ID: {task_id}. Status: SUBMITTED...\n"
192
- yield logs, None
193
 
194
  get_url = f"https://api-singapore.klingai.com/v1/videos/image2video/{task_id}"
195
 
@@ -200,7 +238,7 @@ def create_video(generated_photo_path):
200
 
201
  if poll_json.get("code") != 0:
202
  logs += f"[Error] API Polling Failed: {poll_json.get('message')}\n"
203
- yield logs, None
204
  break
205
 
206
  data = poll_json.get("data", {})
@@ -208,73 +246,94 @@ def create_video(generated_photo_path):
208
 
209
  if task_status == "succeed":
210
  task_result = data.get("task_result", {})
211
- videos = task_result.get("videos", [])
212
 
213
  if videos and "url" in videos[0]:
214
  video_url = videos[0]["url"]
215
  logs += "[Success] Status: SUCCEED. Video generation finished!\n"
216
- yield logs, video_url
217
  else:
218
  logs += "[Error] Status SUCCEED, but no video URL found.\n"
219
- yield logs, None
220
  break
221
 
222
  elif task_status == "failed":
223
  fail_msg = data.get("task_status_msg", "Unknown reason")
224
  logs += f"[Error] Task FAILED. Reason: {fail_msg}\n"
225
- yield logs, None
226
  break
227
 
228
  else:
229
  logs += f"[Kling API] Status: {task_status.upper()}... Waiting 5s...\n"
230
- yield logs, None
231
 
232
  except Exception as e:
233
  logs += f"[Exception] Internal error occurred: {str(e)}\n"
234
- yield logs, None
235
 
236
 
237
  # --- CUSTOM UI STYLES ---
238
  custom_css = (
239
- ".container { max-width: 1100px; margin: auto; }\n"
240
  ".output-media { border-radius: 16px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); }\n"
241
  "button.primary { background: linear-gradient(90deg, #6366f1, #a855f7); border: none; }\n"
242
  "button.primary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(168, 85, 247, 0.4); transition: all 0.3s ease; }\n"
243
- ".log-window textarea { background-color: #1e1e1e !important; color: #4ade80 !important; font-family: 'Courier New', Courier, monospace !important; font-size: 13px !important; }"
 
244
  )
245
 
246
  with gr.Blocks() as demo:
247
- gr.Markdown("<h1 style='text-align: center;'>✨ AI Content Creator Pro</h1>")
248
- gr.Markdown("<p style='text-align: center; color: gray;'>Hyper-realistic UGC Generation: Gemini 3 Pro (Photo) & Kling AI (Video)</p>")
249
 
250
  with gr.Row():
 
251
  with gr.Column(scale=1):
252
- gr.Markdown("### 📥 Inputs")
253
-
254
  with gr.Group():
255
  blogger_imgs = gr.File(label="📸 Blogger (1-5 photos)", file_count="multiple", file_types=["image"])
256
  product_imgs = gr.File(label="🛍️ Product (1-5 photos)", file_count="multiple", file_types=["image"])
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  with gr.Row():
259
- btn_photo = gr.Button("Create Photo", variant="primary", size="lg")
260
- btn_video = gr.Button("Create Video", variant="primary", size="lg")
261
 
262
  logs_output = gr.Textbox(
263
  label="🖥️ System Logs",
264
- lines=8,
265
  interactive=False,
266
  elem_classes="log-window",
267
  placeholder="Generation logs will appear here in real-time..."
268
  )
269
 
 
270
  with gr.Column(scale=1):
271
- gr.Markdown("### 📤 Output")
272
 
273
  with gr.Tabs():
274
- with gr.Tab("Photo"):
275
- out_photo = gr.Image(label="Photo Result", elem_classes="output-media", type="filepath")
276
- with gr.Tab("Video"):
277
- out_video = gr.Video(label="Video Result", elem_classes="output-media")
278
 
279
  btn_photo.click(
280
  fn=create_photo,
@@ -282,10 +341,11 @@ with gr.Blocks() as demo:
282
  outputs=[logs_output, out_photo]
283
  )
284
 
 
285
  btn_video.click(
286
  fn=create_video,
287
  inputs=[out_photo],
288
- outputs=[logs_output, out_video]
289
  )
290
 
291
  if __name__ == "__main__":
 
7
  import io
8
  from PIL import Image
9
 
10
+ # Пытаемся импортировать Google GenAI
11
  try:
12
  from google import genai
13
  from google.genai import types
 
17
  HAS_GENAI = False
18
  GENAI_ERR = str(e)
19
 
20
+ # Пытаемся импортировать OpenAI
21
+ try:
22
+ from openai import OpenAI
23
+ HAS_OPENAI = True
24
+ except ImportError:
25
+ HAS_OPENAI = False
26
+
27
  # --- SECRETS AND CONFIGURATION ---
28
  GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
29
  KLING_ACCESS_KEY = os.getenv("KLING_ACCESS_KEY")
30
  KLING_SECRET_KEY = os.getenv("KLING_SECRET_KEY")
31
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") # НОВЫЙ СЕКРЕТ ДЛЯ GPT-4 Vision
32
 
33
+ # --- STATIC PROMPT FOR PHOTO ---
34
  SYSTEM_PROMPT = "Hyper-realistic Instagram influencer lifestyle photo, shot on iPhone 15 Pro. The exact person [refer to subject images] is looking directly at the camera with a warm, highly attractive, and confident smile showing natural bright white teeth. Healthy, clear, highly realistic skin texture (natural, not overly airbrushed, but free of blemishes or rough spots, with a soft flattering glow). They are casually holding the product [refer to product images] close to the camera, showing it to the viewers. Flattering soft daylight from a window combined with a subtle ambient glow on the face. The background is a stylish, slightly eclectic modern loft with unique decor, lush indoor plants, and rich textures, giving a lived-in, creative, and non-generic vibe. Authentic social media blogger aesthetic, exact facial likeness, highly detailed, and visually pleasing."
35
 
36
  def generate_kling_token(ak, sk):
37
+ headers = {"alg": "HS256", "typ": "JWT"}
38
+ payload = {"iss": ak, "exp": int(time.time()) + 1800, "nbf": int(time.time()) - 5}
 
 
 
 
 
 
 
39
  return jwt.encode(payload, sk, algorithm="HS256", headers=headers)
40
 
41
+ # Функция для вызова GPT-4o Vision
42
+ def generate_video_prompt_with_gpt(base64_image_for_gpt):
43
+ if not HAS_OPENAI or not OPENAI_API_KEY:
44
+ # Fallback, если ключа нет
45
+ return "Natural handheld UGC-style video. The influencer smiles warmly, showing the product to the camera. Subtle micro-shakes, photorealistic, cinematic lighting, casual authentic vibe."
46
+
47
+ try:
48
+ client = OpenAI(api_key=OPENAI_API_KEY)
49
+ response = client.chat.completions.create(
50
+ model="gpt-4o",
51
+ messages=[
52
+ {
53
+ "role": "system",
54
+ "content": "You are an expert AI video generation prompter. Your goal is to write a short, highly detailed prompt for Kling AI (Image-to-Video model). Output ONLY the prompt text, nothing else."
55
+ },
56
+ {
57
+ "role": "user",
58
+ "content":[
59
+ {
60
+ "type": "text",
61
+ "text": "Analyze this starting frame. Write a prompt to animate it. Rules:\n1. Keep it under 400 characters.\n2. Emphasize 'UGC-style', 'handheld smartphone camera', 'subtle micro-shakes'.\n3. Describe natural movements (e.g., 'subtle head tilt', 'natural blinking', 'casually showing the product').\n4. Maintain a photorealistic and authentic vibe."
62
+ },
63
+ {
64
+ "type": "image_url",
65
+ "image_url": {"url": f"data:image/jpeg;base64,{base64_image_for_gpt}"}
66
+ }
67
+ ]
68
+ }
69
+ ],
70
+ max_tokens=150,
71
+ temperature=0.7
72
+ )
73
+ return response.choices[0].message.content.strip()
74
+ except Exception as e:
75
+ print(f"GPT Error: {str(e)}")
76
+ return "Natural handheld UGC-style video, subtle micro-shakes, smiling and showing product, highly realistic."
77
+
78
+
79
  def create_photo(blogger_images, product_images):
80
  logs = ""
81
  if not blogger_images or not product_images:
 
105
  yield logs, None
106
 
107
  client = genai.Client(api_key=GOOGLE_API_KEY)
108
+ contents =[SYSTEM_PROMPT]
109
 
110
  for img in blogger_images:
111
  path = img if isinstance(img, str) else img.name
 
152
 
153
  def create_video(generated_photo_path):
154
  logs = ""
155
+ gpt_prompt = ""
156
+
157
  if not generated_photo_path:
158
  raise gr.Error("Please click 'Create Photo' first to generate an image for the video!")
159
 
160
+ logs += "[System] Initializing Video Pipeline...\n"
161
+ # Возвращаем 3 значения: логи, промпт (пока пустой), видео (None)
162
+ yield logs, gpt_prompt, None
163
+ time.sleep(0.5)
164
 
165
+ # --- 1. ПОДГОТОВКА ИЗОБРАЖЕНИЯ ---
166
+ logs += "[Storage] Preparing base image for analysis and animation...\n"
167
+ yield logs, gpt_prompt, None
168
+
169
+ img_obj = Image.open(generated_photo_path)
170
+ if img_obj.mode != 'RGB':
171
+ img_obj = img_obj.convert('RGB')
172
+
173
+ buffered = io.BytesIO()
174
+ img_obj.save(buffered, format="JPEG", quality=95)
175
+ base64_image_clean = base64.b64encode(buffered.getvalue()).decode('utf-8')
176
+
177
+ # --- 2. ВЫЗОВ GPT-4o VISION ---
178
+ logs += "[GPT-4o Vision] Analyzing generated photo to write perfect Kling prompt...\n"
179
+ yield logs, gpt_prompt, None
180
+
181
+ # Отправляем в GPT (GPT нужно передать чистый base64, функция сама добавит префикс)
182
+ gpt_prompt = generate_video_prompt_with_gpt(base64_image_clean)
183
+
184
+ logs += "[GPT-4o Vision] Prompt generated successfully! Sending to Kling AI...\n"
185
+ # Обновляем UI: теперь в текстовом окне появится текст от GPT!
186
+ yield logs, gpt_prompt, None
187
+
188
+ # --- 3. ВЫЗОВ KLING API ---
189
  if not KLING_ACCESS_KEY or not KLING_SECRET_KEY:
190
  logs += "[Warning] Kling API Keys are not set! Running in Demo Fallback mode...\n"
191
+ yield logs, gpt_prompt, None
 
 
 
 
 
 
192
  time.sleep(2)
 
 
 
193
  logs += "[Success] Status: SUCCEED. Video is ready!\n"
194
+ yield logs, gpt_prompt, "https://www.w3schools.com/html/mov_bbb.mp4"
195
  return
196
 
 
197
  try:
 
 
198
  api_token = generate_kling_token(KLING_ACCESS_KEY, KLING_SECRET_KEY)
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  headers = {
201
  "Authorization": f"Bearer {api_token}",
202
  "Content-Type": "application/json"
 
204
 
205
  payload = {
206
  "model_name": "kling-v2-6",
207
+ "image": base64_image_clean, # Kling требует чистый Base64
208
+ "prompt": gpt_prompt, # Передаем промпт, сгенерированный GPT!
209
+ "negative_prompt": "blur, distort, low quality, bad anatomy, artificial, overacted",
210
  "duration": "5",
211
  "mode": "pro",
212
  "sound": "off"
213
  }
214
 
215
  logs += f"[Kling API] POST /v1/videos/image2video -> Starting task...\n"
216
+ yield logs, gpt_prompt, None
217
 
218
  post_url = "https://api-singapore.klingai.com/v1/videos/image2video"
219
  response = requests.post(post_url, headers=headers, json=payload)
 
222
  if response.status_code != 200 or resp_json.get("code") != 0:
223
  error_msg = resp_json.get('message', 'Unknown error')
224
  logs += f"[Error] API Creation Failed: {error_msg}\n"
225
+ yield logs, gpt_prompt, None
226
  return
227
 
228
  task_id = resp_json.get("data", {}).get("task_id")
229
  logs += f"[Kling API] Task ID: {task_id}. Status: SUBMITTED...\n"
230
+ yield logs, gpt_prompt, None
231
 
232
  get_url = f"https://api-singapore.klingai.com/v1/videos/image2video/{task_id}"
233
 
 
238
 
239
  if poll_json.get("code") != 0:
240
  logs += f"[Error] API Polling Failed: {poll_json.get('message')}\n"
241
+ yield logs, gpt_prompt, None
242
  break
243
 
244
  data = poll_json.get("data", {})
 
246
 
247
  if task_status == "succeed":
248
  task_result = data.get("task_result", {})
249
+ videos = task_result.get("videos",[])
250
 
251
  if videos and "url" in videos[0]:
252
  video_url = videos[0]["url"]
253
  logs += "[Success] Status: SUCCEED. Video generation finished!\n"
254
+ yield logs, gpt_prompt, video_url
255
  else:
256
  logs += "[Error] Status SUCCEED, but no video URL found.\n"
257
+ yield logs, gpt_prompt, None
258
  break
259
 
260
  elif task_status == "failed":
261
  fail_msg = data.get("task_status_msg", "Unknown reason")
262
  logs += f"[Error] Task FAILED. Reason: {fail_msg}\n"
263
+ yield logs, gpt_prompt, None
264
  break
265
 
266
  else:
267
  logs += f"[Kling API] Status: {task_status.upper()}... Waiting 5s...\n"
268
+ yield logs, gpt_prompt, None
269
 
270
  except Exception as e:
271
  logs += f"[Exception] Internal error occurred: {str(e)}\n"
272
+ yield logs, gpt_prompt, None
273
 
274
 
275
  # --- CUSTOM UI STYLES ---
276
  custom_css = (
277
+ ".container { max-width: 1200px; margin: auto; }\n"
278
  ".output-media { border-radius: 16px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); }\n"
279
  "button.primary { background: linear-gradient(90deg, #6366f1, #a855f7); border: none; }\n"
280
  "button.primary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(168, 85, 247, 0.4); transition: all 0.3s ease; }\n"
281
+ ".log-window textarea { background-color: #1e1e1e !important; color: #4ade80 !important; font-family: 'Courier New', Courier, monospace !important; font-size: 13px !important; }\n"
282
+ ".prompt-box textarea { font-size: 14px !important; color: #374151 !important; }"
283
  )
284
 
285
  with gr.Blocks() as demo:
286
+ gr.Markdown("<h1 style='text-align: center;'>✨ AI UGC Content Pipeline</h1>")
287
+ gr.Markdown("<p style='text-align: center; color: gray;'>GenAI (Photo) ➡️ GPT-4o Vision (Analysis) ➡️ Kling AI (Video)</p>")
288
 
289
  with gr.Row():
290
+ # ЛЕВАЯ КОЛОНКА
291
  with gr.Column(scale=1):
292
+ gr.Markdown("### 📥 1. Image Inputs")
 
293
  with gr.Group():
294
  blogger_imgs = gr.File(label="📸 Blogger (1-5 photos)", file_count="multiple", file_types=["image"])
295
  product_imgs = gr.File(label="🛍️ Product (1-5 photos)", file_count="multiple", file_types=["image"])
296
 
297
+ gr.Markdown("### 🧠 2. AI Prompts")
298
+ # Статичное окно для фото
299
+ banana_prompt_ui = gr.Textbox(
300
+ label="GenAI Photo Prompt (Static)",
301
+ value=SYSTEM_PROMPT,
302
+ interactive=False,
303
+ lines=4,
304
+ elem_classes="prompt-box"
305
+ )
306
+
307
+ # Динамическое окно для видео (будет заполняться GPT)
308
+ kling_prompt_ui = gr.Textbox(
309
+ label="Kling Video Prompt (Auto-generated by GPT-4o Vision)",
310
+ placeholder="Click 'Create Video'. GPT-4o will analyze the generated photo and write the perfect prompt here...",
311
+ interactive=True,
312
+ lines=4,
313
+ elem_classes="prompt-box"
314
+ )
315
+
316
  with gr.Row():
317
+ btn_photo = gr.Button("Step 1: Create Photo", variant="primary", size="lg")
318
+ btn_video = gr.Button("Step 2: Create Video", variant="primary", size="lg")
319
 
320
  logs_output = gr.Textbox(
321
  label="🖥️ System Logs",
322
+ lines=6,
323
  interactive=False,
324
  elem_classes="log-window",
325
  placeholder="Generation logs will appear here in real-time..."
326
  )
327
 
328
+ # ПРАВАЯ КОЛОНКА
329
  with gr.Column(scale=1):
330
+ gr.Markdown("### 📤 3. Outputs")
331
 
332
  with gr.Tabs():
333
+ with gr.Tab("Photo Result"):
334
+ out_photo = gr.Image(label="Generated Photo", elem_classes="output-media", type="filepath")
335
+ with gr.Tab("Video Result"):
336
+ out_video = gr.Video(label="Animated UGC Video", elem_classes="output-media")
337
 
338
  btn_photo.click(
339
  fn=create_photo,
 
341
  outputs=[logs_output, out_photo]
342
  )
343
 
344
+ # Теперь возвращаем 3 значения: логи, промпт в текстовое окно, и видео
345
  btn_video.click(
346
  fn=create_video,
347
  inputs=[out_photo],
348
+ outputs=[logs_output, kling_prompt_ui, out_video]
349
  )
350
 
351
  if __name__ == "__main__":
requirements.txt CHANGED
@@ -2,4 +2,5 @@ gradio>=4.0.0
2
  requests
3
  PyJWT
4
  google-genai
5
- pillow
 
 
2
  requests
3
  PyJWT
4
  google-genai
5
+ pillow
6
+ openai