DranikKartofelniy commited on
Commit
dd09aa6
·
verified ·
1 Parent(s): 50afb76

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +205 -104
  2. requirements.txt +1 -0
app.py CHANGED
@@ -1,16 +1,44 @@
1
  import gradio as gr
2
  import os
3
  import time
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  # --- SECRETS AND CONFIGURATION ---
6
- NANOBANANA_API_KEY = os.getenv("NANOBANANA_API_KEY")
7
- KLING_API_KEY = os.getenv("KLING_API_KEY")
 
 
8
 
9
  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 expression. Flawless, beautiful, glowing natural skin with a soft aesthetic finish (no harsh shadows, no rough textures). They are elegantly yet 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 ring-light glow on the face. Bright, aesthetic, cozy modern room background. High-end social media blogger vibe, perfect facial likeness, highly detailed but soft and visually pleasing."""
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  def create_photo(blogger_images, product_images):
12
  logs = ""
13
- # Check if images are uploaded
14
  if not blogger_images or not product_images:
15
  raise gr.Error("Please upload photos of both the blogger and the product!")
16
 
@@ -18,119 +46,192 @@ def create_photo(blogger_images, product_images):
18
  yield logs, None
19
  time.sleep(1)
20
 
21
- logs += "[Storage] Uploading blogger and product images to the cloud...\n"
22
- yield logs, None
23
- time.sleep(1.5)
24
-
25
- logs += f"[NanoBanana Pro] Sending API request. Prompt: {SYSTEM_PROMPT[:50]}...\n"
26
- yield logs, None
27
- time.sleep(1.5)
28
-
29
- logs += "[NanoBanana Pro] Generating image (applying style and likeness)...\n"
30
- yield logs, None
31
- time.sleep(2)
32
-
33
- logs += "[Success] Photo generated! Loading into interface...\n"
34
- # Placeholder realistic photo for demo purposes
35
- photo_url = "https://images.unsplash.com/photo-1611162617474-5b21e879e113?q=80&w=1000&auto=format&fit=crop"
36
-
37
- yield logs, photo_url
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  def create_video(blogger_images, product_images):
40
  logs = ""
41
- # Check if images are uploaded
42
  if not blogger_images or not product_images:
43
  raise gr.Error("Please upload photos of both the blogger and the product!")
44
 
45
- logs += "[System] Initializing video generation...\n"
46
  yield logs, None
47
  time.sleep(1)
48
-
49
- logs += "[Kling API] POST /v1/videos/image2video -> Sending data...\n"
50
- yield logs, None
51
- time.sleep(1.5)
52
-
53
- logs += "[Kling API] Task ID: kl_9876543210ab. Status: QUEUED...\n"
54
- yield logs, None
55
- time.sleep(2)
56
-
57
- logs += "[Kling API] Status: PROCESSING (Rendering frames)...\n"
58
- yield logs, None
59
- time.sleep(2.5)
60
-
61
- logs += "[Success] Status: SUCCEED. Video is ready!\n"
62
- # Placeholder video for demo purposes
63
- video_url = "https://www.w3schools.com/html/mov_bbb.mp4"
64
-
65
- yield logs, video_url
66
 
67
- # --- CUSTOM UI STYLES ---
68
- custom_css = """
69
- .container { max-width: 1100px; margin: auto; }
70
- .output-media { border-radius: 16px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); }
71
- button.primary { background: linear-gradient(90deg, #6366f1, #a855f7); border: none; }
72
- button.primary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(168, 85, 247, 0.4); transition: all 0.3s ease; }
73
- .log-window textarea {
74
- background-color: #1e1e1e !important;
75
- color: #4ade80 !important;
76
- font-family: 'Courier New', Courier, monospace !important;
77
- font-size: 13px !important;
78
- }
79
- """
 
 
 
80
 
81
- # App Layout setup
82
- with gr.Blocks() as demo:
83
- gr.Markdown("<h1 style='text-align: center;'>✨ AI Content Creator Pro</h1>")
84
- gr.Markdown("<p style='text-align: center; color: gray;'>Hyper-realistic UGC Generation: NanoBanana Pro (Photo) & Kling AI (Video)</p>")
85
-
86
- with gr.Row():
87
- # LEFT COLUMN (Inputs)
88
- with gr.Column(scale=1):
89
- gr.Markdown("### 📥 Inputs")
 
90
 
91
- with gr.Group():
92
- blogger_imgs = gr.File(label="📸 Blogger (1-5 photos)", file_count="multiple", file_types=["image"])
93
- product_imgs = gr.File(label="🛍️ Product (1-5 photos)", file_count="multiple", file_types=["image"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
- with gr.Row():
96
- btn_photo = gr.Button("Create Photo", variant="primary", size="lg")
97
- btn_video = gr.Button("Create Video", variant="primary", size="lg")
98
-
99
- # TERMINAL LOGS
100
- logs_output = gr.Textbox(
101
- label="🖥️ System Logs",
102
- lines=6,
103
- interactive=False,
104
- elem_classes="log-window",
105
- placeholder="Generation logs will appear here..."
106
- )
107
-
108
- # RIGHT COLUMN (Outputs)
109
- with gr.Column(scale=1):
110
- gr.Markdown("### 📤 Output")
111
 
112
- with gr.Tabs():
113
- with gr.Tab("Photo"):
114
- out_photo = gr.Image(label="Photo Result", elem_classes="output-media")
115
- with gr.Tab("Video"):
116
- out_video = gr.Video(label="Video Result", elem_classes="output-media")
 
 
117
 
118
- # Button triggers
119
- btn_photo.click(
120
- fn=create_photo,
121
- inputs=[blogger_imgs, product_imgs],
122
- outputs=[logs_output, out_photo]
123
- )
124
-
125
- btn_video.click(
126
- fn=create_video,
127
- inputs=[blogger_imgs, product_imgs],
128
- outputs=[logs_output, out_video]
129
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
- if __name__ == "__main__":
132
- # Launching the app with custom theme and CSS
133
- demo.launch(
134
- theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="violet"),
135
- css=custom_css
136
- )
 
1
  import gradio as gr
2
  import os
3
  import time
4
+ import requests
5
+ import base64
6
+ import jwt
7
+ import io
8
+ from PIL import Image
9
+
10
+ # Импортируем либу из твоего рабочего кода
11
+ try:
12
+ from google import genai
13
+ from google.genai import types
14
+ except ImportError:
15
+ pass
16
 
17
  # --- SECRETS AND CONFIGURATION ---
18
+ # Используем ключ Google из твоего кода (добавь GOOGLE_API_KEY в Secrets)
19
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
20
+ KLING_ACCESS_KEY = os.getenv("KLING_ACCESS_KEY")
21
+ KLING_SECRET_KEY = os.getenv("KLING_SECRET_KEY")
22
 
23
  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 expression. Flawless, beautiful, glowing natural skin with a soft aesthetic finish (no harsh shadows, no rough textures). They are elegantly yet 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 ring-light glow on the face. Bright, aesthetic, cozy modern room background. High-end social media blogger vibe, perfect facial likeness, highly detailed but soft and visually pleasing."""
24
 
25
+ # --- JWT TOKEN GENERATOR FOR KLING ---
26
+ def generate_kling_token(ak, sk):
27
+ """Generates a short-lived JWT token required by Kling API"""
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
+
40
  def create_photo(blogger_images, product_images):
41
  logs = ""
 
42
  if not blogger_images or not product_images:
43
  raise gr.Error("Please upload photos of both the blogger and the product!")
44
 
 
46
  yield logs, None
47
  time.sleep(1)
48
 
49
+ # =========================================================================
50
+ # DEMO FALLBACK (Если ключа нет)
51
+ # =========================================================================
52
+ if not GOOGLE_API_KEY:
53
+ logs += "[Warning] GOOGLE_API_KEY is not set! Running in Demo Fallback mode...\n"
54
+ yield logs, None
55
+ time.sleep(2)
56
+ logs += "[Success] Photo generated! Loading into interface...\n"
57
+ yield logs, "https://images.unsplash.com/photo-1611162617474-5b21e879e113?q=80&w=1000&auto=format&fit=crop"
58
+ return
59
+
60
+ # =========================================================================
61
+ # РЕАЛЬНЫЙ ВЫЗОВ (Из твоего кода)
62
+ # =========================================================================
63
+ try:
64
+ logs += "[Storage] Preparing blogger and product images...\n"
65
+ yield logs, None
66
+
67
+ client = genai.Client(api_key=GOOGLE_API_KEY)
68
+
69
+ # Собираем контент так же, как в твоем скрипте
70
+ contents = [SYSTEM_PROMPT]
71
+
72
+ for img in blogger_images:
73
+ path = img if isinstance(img, str) else img.name
74
+ contents.append(Image.open(path))
75
+
76
+ for img in product_images:
77
+ path = img if isinstance(img, str) else img.name
78
+ contents.append(Image.open(path))
79
+
80
+ logs += f"[Google GenAI] Sending API request (gemini-3-pro-image-preview)...\n"
81
+ yield logs, None
82
+
83
+ # Вызов генерации 1-в-1 как в твоем скрипте
84
+ resp = client.models.generate_content(
85
+ model="gemini-3-pro-image-preview",
86
+ contents=contents,
87
+ config=types.GenerateContentConfig(
88
+ response_modalities=['TEXT', 'IMAGE'],
89
+ image_config=types.ImageConfig(aspect_ratio="3:4", image_size="2K") # 3:4 идеально для блогера
90
+ )
91
+ )
92
+
93
+ # Извлечение байтов картинки
94
+ img_data = None
95
+ for part in resp.parts:
96
+ if part.inline_data:
97
+ img_data = part.inline_data.data
98
+ break
99
+
100
+ if img_data:
101
+ logs += "[Success] Photo generated successfully! Loading to UI...\n"
102
+ # Превращаем байты в картинку для Gradio
103
+ result_img = Image.open(io.BytesIO(img_data))
104
+ yield logs, result_img
105
+ else:
106
+ logs += "[Error] API succeeded but returned no image data.\n"
107
+ yield logs, None
108
+
109
+ except Exception as e:
110
+ err = str(e)
111
+ if "429" in err:
112
+ logs += f"[Error] Rate limited by API (429)\n"
113
+ else:
114
+ logs += f"[Exception] API Error: {err[:100]}\n"
115
+ yield logs, None
116
+
117
 
118
  def create_video(blogger_images, product_images):
119
  logs = ""
 
120
  if not blogger_images or not product_images:
121
  raise gr.Error("Please upload photos of both the blogger and the product!")
122
 
123
+ logs += "[System] Initializing Kling AI video generation...\n"
124
  yield logs, None
125
  time.sleep(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ if not KLING_ACCESS_KEY or not KLING_SECRET_KEY:
128
+ logs += "[Warning] Kling API Keys are not set! Running in Demo Fallback mode...\n"
129
+ yield logs, None
130
+ time.sleep(1)
131
+ logs += "[Kling API] POST /v1/videos/image2video -> Sending data...\n"
132
+ yield logs, None
133
+ time.sleep(1.5)
134
+ logs += "[Kling API] Task ID: kl_demo_98765. Status: SUBMITTED...\n"
135
+ yield logs, None
136
+ time.sleep(2)
137
+ logs += "[Kling API] Status: PROCESSING...\n"
138
+ yield logs, None
139
+ time.sleep(2.5)
140
+ logs += "[Success] Status: SUCCEED. Video is ready!\n"
141
+ yield logs, "https://www.w3schools.com/html/mov_bbb.mp4"
142
+ return
143
 
144
+ try:
145
+ logs += "[Security] Generating 30-min JWT Token using Access & Secret Keys...\n"
146
+ yield logs, None
147
+ api_token = generate_kling_token(KLING_ACCESS_KEY, KLING_SECRET_KEY)
148
+
149
+ first_img = blogger_images[0]
150
+ img_path = first_img if isinstance(first_img, str) else first_img.name
151
+
152
+ with open(img_path, "rb") as f:
153
+ base64_image = base64.b64encode(f.read()).decode('utf-8')
154
 
155
+ logs += "[Storage] Image converted to Base64 successfully.\n"
156
+ yield logs, None
157
+
158
+ headers = {
159
+ "Authorization": f"Bearer {api_token}",
160
+ "Content-Type": "application/json"
161
+ }
162
+
163
+ payload = {
164
+ "model_name": "kling-v2-6",
165
+ "image": f"data:image/jpeg;base64,{base64_image}",
166
+ "prompt": "Camera zooms out, the blogger smiles and enthusiastically shows the product directly to the camera, photorealistic, cinematic lighting",
167
+ "negative_prompt": "blur, distort, low quality, bad anatomy",
168
+ "duration": "5",
169
+ "mode": "pro",
170
+ "sound": "off"
171
+ }
172
+
173
+ logs += f"[Kling API] POST /v1/videos/image2video -> Starting task...\n"
174
+ yield logs, None
175
+
176
+ post_url = "https://api-singapore.klingai.com/v1/videos/image2video"
177
+ response = requests.post(post_url, headers=headers, json=payload)
178
+ resp_json = response.json()
179
+
180
+ if response.status_code != 200 or resp_json.get("code") != 0:
181
+ error_msg = resp_json.get('message', 'Unknown error')
182
+ logs += f"[Error] API Creation Failed: {error_msg}\n"
183
+ yield logs, None
184
+ return
185
 
186
+ task_id = resp_json.get("data", {}).get("task_id")
187
+ logs += f"[Kling API] Task ID: {task_id}. Status: SUBMITTED...\n"
188
+ yield logs, None
189
+
190
+ get_url = f"https://api-singapore.klingai.com/v1/videos/image2video/{task_id}"
191
+
192
+ while True:
193
+ time.sleep(5)
194
+ poll_resp = requests.get(get_url, headers=headers)
195
+ poll_json = poll_resp.json()
 
 
 
 
 
 
196
 
197
+ if poll_json.get("code") != 0:
198
+ logs += f"[Error] API Polling Failed: {poll_json.get('message')}\n"
199
+ yield logs, None
200
+ break
201
+
202
+ data = poll_json.get("data", {})
203
+ task_status = data.get("task_status", "").lower()
204
 
205
+ if task_status == "succeed":
206
+ task_result = data.get("task_result", {})
207
+ videos = task_result.get("videos", [])
208
+
209
+ if videos and "url" in videos[0]:
210
+ video_url = videos[0]["url"]
211
+ logs += "[Success] Status: SUCCEED. Video generation finished!\n"
212
+ yield logs, video_url
213
+ else:
214
+ logs += "[Error] Status SUCCEED, but no video URL found.\n"
215
+ yield logs, None
216
+ break
217
+
218
+ elif task_status == "failed":
219
+ fail_msg = data.get("task_status_msg", "Unknown reason")
220
+ logs += f"[Error] Task FAILED. Reason: {fail_msg}\n"
221
+ yield logs, None
222
+ break
223
+
224
+ else:
225
+ logs += f"[Kling API] Status: {task_status.upper()}... Waiting 5s...\n"
226
+ yield logs, None
227
+
228
+ except Exception as e:
229
+ logs += f"[Exception] Internal error occurred: {str(e)}\n"
230
+ yield logs, None
231
 
232
+ # --- CUSTOM UI STYLES ---
233
+ custom_css = """
234
+ .container { max-width: 1100px; margin: auto; }
235
+ .output-media { border-radius: 16px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); }
236
+ button.primary { background: linear-gradient(90deg, #6366f1, #a855f7); border: none; }
237
+ button.primary:hover
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
  gradio>=4.0.0
2
  requests
 
3
  fal-client
 
1
  gradio>=4.0.0
2
  requests
3
+ PyJWT
4
  fal-client