TDN-M commited on
Commit
2f7b37f
·
verified ·
1 Parent(s): 7359bd5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -106
app.py CHANGED
@@ -6,42 +6,53 @@ import logging
6
  from PIL import Image
7
  import mimetypes
8
 
9
- # Configure logging
10
  logging.basicConfig(level=logging.INFO)
11
  logger = logging.getLogger(__name__)
12
 
13
- # Vidu API configuration
14
- VIDU_API_KEY = os.getenv("VIDU_API_KEY")
15
- VIDU_API_URL = "https://api.vidu.com"
16
- POLL_INTERVAL = 5 # Seconds between status checks
17
- TIMEOUT = 300 # Max seconds to wait for video generation
 
18
 
19
- # Function to validate image requirements
 
 
 
 
 
 
 
 
 
 
20
  def validate_image(image_path):
21
  if not image_path:
22
- return False, "No image provided."
23
 
24
- # Check file size (<10MB)
25
  if os.path.getsize(image_path) > 10 * 1024 * 1024:
26
- return False, "Image size exceeds 10MB."
27
 
28
- # Check format
29
  mime_type, _ = mimetypes.guess_type(image_path)
30
  if mime_type not in ["image/png", "image/webp", "image/jpeg", "image/jpg"]:
31
- return False, "Unsupported image format. Use PNG, WebP, JPEG, or JPG."
32
 
33
- # Check aspect ratio
34
  try:
35
  img = Image.open(image_path)
36
  width, height = img.size
37
  aspect_ratio = width / height
38
  if aspect_ratio < 0.25 or aspect_ratio > 4:
39
- return False, "Image aspect ratio must be between 1:4 and 4:1."
40
  return True, None
41
  except Exception as e:
42
- return False, f"Error validating image: {str(e)}"
43
 
44
- # Function to validate pixel density ratio for two images
45
  def validate_pixel_density(start_image, end_image):
46
  try:
47
  start_img = Image.open(start_image)
@@ -50,22 +61,22 @@ def validate_pixel_density(start_image, end_image):
50
  end_pixels = end_img.size[0] * end_img.size[1]
51
  ratio = start_pixels / end_pixels
52
  if not (0.8 <= ratio <= 1.25):
53
- return False, "Pixel density ratio between start and end images must be between 0.8 and 1.25."
54
  return True, None
55
  except Exception as e:
56
- return False, f"Error validating pixel density: {str(e)}"
57
 
58
- # Function to upload image to Vidu
59
  def upload_image_to_vidu(image_path):
60
  if not VIDU_API_KEY:
61
- return None, "Error: Vidu API key not configured."
62
 
63
- # Validate image
64
  valid, error_message = validate_image(image_path)
65
  if not valid:
66
  return None, error_message
67
 
68
- # Step 1: Create upload link
69
  url = f"{VIDU_API_URL}/tools/v2/files/uploads"
70
  headers = {
71
  "Authorization": f"Token {VIDU_API_KEY}",
@@ -74,68 +85,73 @@ def upload_image_to_vidu(image_path):
74
  payload = {"scene": "vidu"}
75
 
76
  try:
77
- logger.info("Creating upload link")
78
  response = requests.post(url, json=payload, headers=headers)
79
  response.raise_for_status()
80
  result = response.json()
81
  put_url = result.get("put_url")
82
  resource_id = result.get("id")
83
  if not put_url or not resource_id:
84
- return None, "Error: No put_url or resource_id returned."
85
  except requests.exceptions.RequestException as e:
86
- logger.error(f"Create upload link error: {str(e)}")
87
- return None, f"Error creating upload link: {str(e)}"
88
 
89
- # Step 2: Upload image using put_url
90
  mime_type, _ = mimetypes.guess_type(image_path)
91
  with open(image_path, "rb") as f:
92
  image_data = f.read()
93
 
94
  try:
95
- logger.info("Uploading image to put_url")
96
  response = requests.put(put_url, data=image_data, headers={"Content-Type": mime_type})
97
  response.raise_for_status()
98
  etag = response.headers.get("etag")
99
  if not etag:
100
- return None, "Error: No etag returned."
101
  except requests.exceptions.RequestException as e:
102
- logger.error(f"Upload image error: {str(e)}")
103
- return None, f"Error uploading image: {str(e)}"
104
 
105
- # Step 3: Complete upload
106
  url = f"{VIDU_API_URL}/tools/v2/files/uploads/{resource_id}/finish"
107
  payload = {"etag": etag.strip('"')}
108
 
109
  try:
110
- logger.info("Completing upload")
111
  response = requests.put(url, json=payload, headers=headers)
112
  response.raise_for_status()
113
  result = response.json()
114
  uri = result.get("uri")
115
  if not uri:
116
- return None, "Error: No URI returned."
117
- return uri, "Image uploaded successfully."
118
  except requests.exceptions.RequestException as e:
119
- logger.error(f"Complete upload error: {str(e)}")
120
- return None, f"Error completing upload: {str(e)}"
121
 
122
- # Function to call Vidu API for Start-End to Video
123
- def start_end_to_video(start_image, end_image, prompt, model="vidu2.0", resolution="720p", duration=4, movement_amplitude="auto", seed=None):
 
 
 
 
 
124
  if not VIDU_API_KEY:
125
- return None, "Error: Vidu API key not configured."
126
 
127
  if not start_image or not end_image:
128
- return None, "Error: Both start and end images are required."
129
 
130
  if prompt and len(prompt) > 1500:
131
- return None, "Error: Prompt must be 1500 characters or less."
132
 
133
- # Validate pixel density
134
  valid, error_message = validate_pixel_density(start_image, end_image)
135
  if not valid:
136
  return None, error_message
137
 
138
- # Upload images to Vidu
139
  start_uri, start_message = upload_image_to_vidu(start_image)
140
  if not start_uri:
141
  return None, start_message
@@ -161,28 +177,32 @@ def start_end_to_video(start_image, end_image, prompt, model="vidu2.0", resoluti
161
  payload["seed"] = seed
162
 
163
  try:
164
- logger.info(f"Sending request to Start-End to Video API: {payload}")
165
  response = requests.post(url, json=payload, headers=headers)
166
  response.raise_for_status()
167
  result = response.json()
168
  task_id = result.get("task_id")
169
  if not task_id:
170
- return None, "Error: No task ID returned."
171
- return task_id, f"Task created successfully. Task ID: {task_id}"
172
  except requests.exceptions.RequestException as e:
173
- logger.error(f"API error: {str(e)}")
174
  if response.text:
175
- logger.error(f"API response: {response.text}")
176
- return None, f"Error: {str(e)} - {response.text}"
177
 
178
- # Placeholder for References to Video
179
- def references_to_video(reference_images, prompt, resolution="720p", duration=4):
180
- return None, "Error: References to Video not implemented. Please provide API details."
 
 
 
 
181
 
182
- # Function to check task status
183
  def check_task_status(task_id):
184
  if not task_id:
185
- return None, "Error: Invalid task ID."
186
 
187
  url = f"{VIDU_API_URL}/ent/v2/tasks/{task_id}/creations"
188
  headers = {
@@ -193,7 +213,7 @@ def check_task_status(task_id):
193
  start_time = time.time()
194
  while time.time() - start_time < TIMEOUT:
195
  try:
196
- logger.info(f"Checking task status for task_id: {task_id}")
197
  response = requests.get(url, headers=headers)
198
  response.raise_for_status()
199
  result = response.json()
@@ -204,89 +224,93 @@ def check_task_status(task_id):
204
  if state == "success" and creations:
205
  video_url = creations[0].get("url")
206
  if video_url:
207
- return video_url, "Video generated successfully!"
208
- return None, "Error: No video URL in creations."
209
  elif state == "failed":
210
- return None, f"Error: Video generation failed. Error code: {err_code or 'Unknown'}"
211
  elif state in ["created", "queueing", "processing"]:
212
  time.sleep(POLL_INTERVAL)
213
  else:
214
- return None, f"Error: Unknown state {state}."
215
  except requests.exceptions.RequestException as e:
216
- logger.error(f"Status check error: {str(e)}")
217
  if response.text:
218
- logger.error(f"Status check response: {response.text}")
219
- return None, f"Error checking task status: {str(e)} - {response.text}"
220
 
221
- return None, f"Error: Video generation timed out. Task ID: {task_id}"
222
 
223
- # Gradio interface function for Start-End to Video
224
- def gradio_start_end_to_video(start_image, end_image, prompt, model, resolution, duration, movement_amplitude, seed):
225
  if not start_image or not end_image:
226
- return None, "Error: Both start and end images are required."
227
- task_id, message = start_end_to_video(start_image, end_image, prompt, model, resolution, duration, movement_amplitude, seed)
228
  if not task_id:
229
  return None, message
230
  video_url, status_message = check_task_status(task_id)
231
  return video_url, status_message
232
 
233
- # Gradio interface function for References to Video
234
- def gradio_references_to_video(image1, image2, image3, prompt, resolution, duration):
235
  reference_images = [img for img in [image1, image2, image3] if img]
236
  if not reference_images:
237
- return None, "Error: At least one reference image is required."
238
  if not prompt:
239
- return None, "Error: Prompt is required."
240
- task_id, message = references_to_video(reference_images, prompt, resolution, duration)
241
  if not task_id:
242
  return None, message
243
  video_url, status_message = check_task_status(task_id)
244
  return video_url, status_message
245
 
246
- # Gradio interface
247
- with gr.Blocks(title="LÀM HOẠT HÌNH @PRO") as demo:
248
- gr.Markdown("# AI APPS | by TDNM")
249
- gr.Markdown("SẢN XUẤT HOẠT HÌNH CHUYÊN NGHIỆP")
250
- # Tab for Start-End to Video
251
- with gr.Tab("PHƯƠNG PHÁP ĐẦU - CUỐI"):
252
- gr.Markdown("Tải lên hình ảnh bắt đầu và kết thúc và lời nhắc để tạo video chuyển tiếp. Hình ảnh phải là PNG, WebP, JPEG hoặc JPG, <10MB, với tỷ lệ khung hình từ 1:4 đến 4:1.")
253
- start_image = gr.Image(type="filepath", label="Hình ảnh bắt đầu")
254
- end_image = gr.Image(type="filepath", label="Hình ảnh kết thúc")
255
- prompt_se = gr.Textbox(label="Prompt (Optional)", placeholder="E.g., 'Smoothly transition from a car chassis to a complete car.'")
256
- model_se = gr.Dropdown(choices=["vidu2.0", "vidu1.5"], label="Model", value="vidu2.0")
257
- resolution_se = gr.Dropdown(choices=["360p", "720p", "1080p"], label="Resolution", value="720p")
258
- duration_se = gr.Dropdown(choices=[4, 8], label="Duration (seconds)", value=4)
259
- movement_amplitude_se = gr.Dropdown(choices=["auto", "small", "medium", "large"], label="Movement Amplitude", value="auto")
260
- seed_se = gr.Number(label="Seed (Optional)", value=None, precision=0)
261
- se_button = gr.Button("Generate Video")
262
- se_video_output = gr.Video(label="Generated Video")
263
- se_message = gr.Textbox(label="Status")
 
 
 
 
264
 
265
  se_button.click(
266
  fn=gradio_start_end_to_video,
267
- inputs=[start_image, end_image, prompt_se, model_se, resolution_se, duration_se, movement_amplitude_se, seed_se],
268
  outputs=[se_video_output, se_message]
269
  )
270
 
271
- # Tab for References to Video (Placeholder)
272
- with gr.Tab("References to Video"):
273
- gr.Markdown("Upload 1–3 reference images and a prompt to generate a video. (Not implemented yet.)")
274
- image1 = gr.Image(type="filepath", label="Reference Image 1")
275
- image2 = gr.Image(type="filepath", label="Reference Image 2 (Optional)")
276
- image3 = gr.Image(type="filepath", label="Reference Image 3 (Optional)")
277
- prompt_ref = gr.Textbox(label="Prompt", placeholder="E.g., 'A character walking in a forest.'")
278
- resolution_ref = gr.Dropdown(choices=["360p", "720p", "1080p"], label="Resolution", value="720p")
279
- duration_ref = gr.Dropdown(choices=[4, 8], label="Duration (seconds)", value=4)
280
- ref_button = gr.Button("Generate Video")
281
- ref_video_output = gr.Video(label="Generated Video")
282
- ref_message = gr.Textbox(label="Status")
283
 
284
  ref_button.click(
285
  fn=gradio_references_to_video,
286
- inputs=[image1, image2, image3, prompt_ref, resolution_ref, duration_ref],
287
  outputs=[ref_video_output, ref_message]
288
  )
289
 
290
- # Launch the app (not needed for HF Spaces, included for local testing)
291
  if __name__ == "__main__":
292
  demo.launch()
 
6
  from PIL import Image
7
  import mimetypes
8
 
9
+ # Cấu hình logging
10
  logging.basicConfig(level=logging.INFO)
11
  logger = logging.getLogger(__name__)
12
 
13
+ # Cấu hình API TDNM
14
+ VIDU_API_KEY = os.getenv("VIDU_API_KEY") # Giữ tên biến để tương thích với API
15
+ TDNM_KEY = os.getenv("TDNM_KEY") # Khóa bí mật để xác thực người dùng
16
+ VIDU_API_URL = "https://api.vidu.com" # URL API thực tế
17
+ POLL_INTERVAL = 5 # Giây giữa các lần kiểm tra trạng thái
18
+ TIMEOUT = 300 # Thời gian chờ tối đa để tạo video
19
 
20
+ # Hàm kiểm tra TDNM_KEY
21
+ def validate_tdn_key(user_key):
22
+ if not TDNM_KEY:
23
+ return False, "Lỗi: Khóa bí mật chưa được cấu hình trên máy chủ."
24
+ if not user_key:
25
+ return False, "Lỗi: Vui lòng nhập khóa bí mật."
26
+ if user_key != TDNM_KEY:
27
+ return False, "Lỗi: Khóa bí mật không hợp lệ."
28
+ return True, "Khóa bí mật được xác thực thành công."
29
+
30
+ # Hàm kiểm tra yêu cầu hình ảnh
31
  def validate_image(image_path):
32
  if not image_path:
33
+ return False, "Lỗi: Chưa cung cấp hình ảnh."
34
 
35
+ # Kiểm tra kích thước tệp (<10MB)
36
  if os.path.getsize(image_path) > 10 * 1024 * 1024:
37
+ return False, "Lỗi: Kích thước hình ảnh vượt quá 10MB."
38
 
39
+ # Kiểm tra định dạng
40
  mime_type, _ = mimetypes.guess_type(image_path)
41
  if mime_type not in ["image/png", "image/webp", "image/jpeg", "image/jpg"]:
42
+ return False, "Lỗi: Định dạng hình ảnh không được hỗ trợ. Sử dụng PNG, WebP, JPEG hoặc JPG."
43
 
44
+ # Kiểm tra tỷ lệ khung hình
45
  try:
46
  img = Image.open(image_path)
47
  width, height = img.size
48
  aspect_ratio = width / height
49
  if aspect_ratio < 0.25 or aspect_ratio > 4:
50
+ return False, "Lỗi: Tỷ lệ khung hình phải nằm trong khoảng 1:4 đến 4:1."
51
  return True, None
52
  except Exception as e:
53
+ return False, f"Lỗi khi kiểm tra hình ảnh: {str(e)}"
54
 
55
+ # Hàm kiểm tra tỷ lệ mật độ điểm ảnh giữa hai hình ảnh
56
  def validate_pixel_density(start_image, end_image):
57
  try:
58
  start_img = Image.open(start_image)
 
61
  end_pixels = end_img.size[0] * end_img.size[1]
62
  ratio = start_pixels / end_pixels
63
  if not (0.8 <= ratio <= 1.25):
64
+ return False, "Lỗi: Tỷ lệ mật độ điểm ảnh giữa hai hình ảnh phải nằm trong khoảng 0.8 đến 1.25."
65
  return True, None
66
  except Exception as e:
67
+ return False, f"Lỗi khi kiểm tra mật độ điểm ảnh: {str(e)}"
68
 
69
+ # Hàm tải hình ảnh lên TDNM
70
  def upload_image_to_vidu(image_path):
71
  if not VIDU_API_KEY:
72
+ return None, "Lỗi: Khóa API TDNM chưa được cấu hình."
73
 
74
+ # Kiểm tra hình ảnh
75
  valid, error_message = validate_image(image_path)
76
  if not valid:
77
  return None, error_message
78
 
79
+ # Bước 1: Tạo liên kết tải lên
80
  url = f"{VIDU_API_URL}/tools/v2/files/uploads"
81
  headers = {
82
  "Authorization": f"Token {VIDU_API_KEY}",
 
85
  payload = {"scene": "vidu"}
86
 
87
  try:
88
+ logger.info("Tạo liên kết tải lên")
89
  response = requests.post(url, json=payload, headers=headers)
90
  response.raise_for_status()
91
  result = response.json()
92
  put_url = result.get("put_url")
93
  resource_id = result.get("id")
94
  if not put_url or not resource_id:
95
+ return None, "Lỗi: Không nhận được put_url hoặc resource_id."
96
  except requests.exceptions.RequestException as e:
97
+ logger.error(f"Lỗi tạo liên kết tải lên: {str(e)}")
98
+ return None, f"Lỗi khi tạo liên kết tải lên: {str(e)}"
99
 
100
+ # Bước 2: Tải hình ảnh lên put_url
101
  mime_type, _ = mimetypes.guess_type(image_path)
102
  with open(image_path, "rb") as f:
103
  image_data = f.read()
104
 
105
  try:
106
+ logger.info("Tải hình ảnh lên put_url")
107
  response = requests.put(put_url, data=image_data, headers={"Content-Type": mime_type})
108
  response.raise_for_status()
109
  etag = response.headers.get("etag")
110
  if not etag:
111
+ return None, "Lỗi: Không nhận được etag."
112
  except requests.exceptions.RequestException as e:
113
+ logger.error(f"Lỗi tải hình ảnh: {str(e)}")
114
+ return None, f"Lỗi khi tải hình ảnh: {str(e)}"
115
 
116
+ # Bước 3: Hoàn tất tải lên
117
  url = f"{VIDU_API_URL}/tools/v2/files/uploads/{resource_id}/finish"
118
  payload = {"etag": etag.strip('"')}
119
 
120
  try:
121
+ logger.info("Hoàn tất tải lên")
122
  response = requests.put(url, json=payload, headers=headers)
123
  response.raise_for_status()
124
  result = response.json()
125
  uri = result.get("uri")
126
  if not uri:
127
+ return None, "Lỗi: Không nhận được URI."
128
+ return uri, "Tải hình ảnh lên thành công."
129
  except requests.exceptions.RequestException as e:
130
+ logger.error(f"Lỗi hoàn tất tải lên: {str(e)}")
131
+ return None, f"Lỗi khi hoàn tất tải lên: {str(e)}"
132
 
133
+ # Hàm gọi API TDNM cho Start-End to Video
134
+ def start_end_to_video(start_image, end_image, prompt, model="vidu2.0", resolution="720p", duration=4, movement_amplitude="auto", seed=None, user_key=None):
135
+ # Kiểm tra TDNM_KEY
136
+ valid_key, key_message = validate_tdn_key(user_key)
137
+ if not valid_key:
138
+ return None, key_message
139
+
140
  if not VIDU_API_KEY:
141
+ return None, "Lỗi: Khóa API TDNM chưa được cấu hình."
142
 
143
  if not start_image or not end_image:
144
+ return None, "Lỗi: Cần cung cấp cả hai hình ảnh đầu và cuối."
145
 
146
  if prompt and len(prompt) > 1500:
147
+ return None, "Lỗi: tả văn bản không được vượt quá 1500 tự."
148
 
149
+ # Kiểm tra mật độ điểm ảnh
150
  valid, error_message = validate_pixel_density(start_image, end_image)
151
  if not valid:
152
  return None, error_message
153
 
154
+ # Tải hình ảnh lên TDNM
155
  start_uri, start_message = upload_image_to_vidu(start_image)
156
  if not start_uri:
157
  return None, start_message
 
177
  payload["seed"] = seed
178
 
179
  try:
180
+ logger.info(f"Gửi yêu cầu đến API TDNM Start-End to Video: {payload}")
181
  response = requests.post(url, json=payload, headers=headers)
182
  response.raise_for_status()
183
  result = response.json()
184
  task_id = result.get("task_id")
185
  if not task_id:
186
+ return None, "Lỗi: Không nhận được ID tác vụ."
187
+ return task_id, f"Tác vụ được tạo thành công. ID tác vụ: {task_id}"
188
  except requests.exceptions.RequestException as e:
189
+ logger.error(f"Lỗi API: {str(e)}")
190
  if response.text:
191
+ logger.error(f"Phản hồi API: {response.text}")
192
+ return None, f"Lỗi: {str(e)} - {response.text}"
193
 
194
+ # Placeholder cho References to Video
195
+ def references_to_video(reference_images, prompt, resolution="720p", duration=4, user_key=None):
196
+ # Kiểm tra TDNM_KEY
197
+ valid_key, key_message = validate_tdn_key(user_key)
198
+ if not valid_key:
199
+ return None, key_message
200
+ return None, "Lỗi: Chức năng References to Video chưa được triển khai. Vui lòng cung cấp chi tiết API."
201
 
202
+ # Hàm kiểm tra trạng thái tác vụ
203
  def check_task_status(task_id):
204
  if not task_id:
205
+ return None, "Lỗi: ID tác vụ không hợp lệ."
206
 
207
  url = f"{VIDU_API_URL}/ent/v2/tasks/{task_id}/creations"
208
  headers = {
 
213
  start_time = time.time()
214
  while time.time() - start_time < TIMEOUT:
215
  try:
216
+ logger.info(f"Kiểm tra trạng thái tác vụ cho task_id: {task_id}")
217
  response = requests.get(url, headers=headers)
218
  response.raise_for_status()
219
  result = response.json()
 
224
  if state == "success" and creations:
225
  video_url = creations[0].get("url")
226
  if video_url:
227
+ return video_url, "Tạo video thành công!"
228
+ return None, "Lỗi: Không URL video trong kết quả."
229
  elif state == "failed":
230
+ return None, f"Lỗi: Tạo video thất bại. lỗi: {err_code or 'Không xác định'}"
231
  elif state in ["created", "queueing", "processing"]:
232
  time.sleep(POLL_INTERVAL)
233
  else:
234
+ return None, f"Lỗi: Trạng thái không xác định {state}."
235
  except requests.exceptions.RequestException as e:
236
+ logger.error(f"Lỗi kiểm tra trạng thái: {str(e)}")
237
  if response.text:
238
+ logger.error(f"Phản hồi kiểm tra trạng thái: {response.text}")
239
+ return None, f"Lỗi khi kiểm tra trạng thái tác vụ: {str(e)} - {response.text}"
240
 
241
+ return None, f"Lỗi: Hết thời gian tạo video. ID tác vụ: {task_id}"
242
 
243
+ # Hàm giao diện Gradio cho Start-End to Video
244
+ def gradio_start_end_to_video(start_image, end_image, prompt, model, resolution, duration, movement_amplitude, seed, user_key):
245
  if not start_image or not end_image:
246
+ return None, "Lỗi: Cần cung cấp cả hai hình ảnh đầu và cuối."
247
+ task_id, message = start_end_to_video(start_image, end_image, prompt, model, resolution, duration, movement_amplitude, seed, user_key)
248
  if not task_id:
249
  return None, message
250
  video_url, status_message = check_task_status(task_id)
251
  return video_url, status_message
252
 
253
+ # Hàm giao diện Gradio cho References to Video
254
+ def gradio_references_to_video(image1, image2, image3, prompt, resolution, duration, user_key):
255
  reference_images = [img for img in [image1, image2, image3] if img]
256
  if not reference_images:
257
+ return None, "Lỗi: Cần cung cấp ít nhất một hình ảnh tham chiếu."
258
  if not prompt:
259
+ return None, "Lỗi: Cần cung cấp mô tả văn bản."
260
+ task_id, message = references_to_video(reference_images, prompt, resolution, duration, user_key)
261
  if not task_id:
262
  return None, message
263
  video_url, status_message = check_task_status(task_id)
264
  return video_url, status_message
265
 
266
+ # Giao diện Gradio
267
+ with gr.Blocks(title="Trình Tạo Video TDNM") as demo:
268
+ gr.Markdown("# Trình Tạo Video TDNM")
269
+ gr.Markdown("Tạo video với TDNM. Vui lòng nhập khóa bí mật (TDNM_KEY) để sử dụng ứng dụng.")
270
+
271
+ # Trường nhập khóa bí mật (áp dụng cho cả hai tab)
272
+ user_key = gr.Textbox(label="Khóa Mật (TDNM_KEY)", type="password", placeholder="Nhập khóa mật của bạn")
273
+
274
+ # Tab cho Start-End to Video
275
+ with gr.Tab("Video Chuyển Đổi Hình Ảnh"):
276
+ gr.Markdown("Tải lên hai hình ảnh (đầu và cuối) và mô tả văn bản để tạo video chuyển đổi. Hình ảnh phải là PNG, WebP, JPEG hoặc JPG, kích thước dưới 10MB, tỷ lệ khung hình từ 1:4 đến 4:1.")
277
+ start_image = gr.Image(type="filepath", label="Hình Ảnh Đầu")
278
+ end_image = gr.Image(type="filepath", label="Hình Ảnh Cuối")
279
+ prompt_se = gr.Textbox(label=" Tả Văn Bản (Tùy Chọn)", placeholder=" dụ: 'Chuyển đổi mượt mà từ khung xe thành xe hoàn chỉnh.'")
280
+ model_se = gr.Dropdown(choices=["vidu2.0", "vidu1.5"], label="Mô Hình", value="vidu2.0")
281
+ resolution_se = gr.Dropdown(choices=["360p", "720p", "1080p"], label="Độ Phân Giải", value="720p")
282
+ duration_se = gr.Dropdown(choices=[4, 8], label="Thời Lượng (giây)", value=4)
283
+ movement_amplitude_se = gr.Dropdown(choices=["auto", "small", "medium", "large"], label="Biên Độ Chuyển Động", value="auto")
284
+ seed_se = gr.Number(label="Hạt Giống (Tùy Chọn)", value=None, precision=0)
285
+ se_button = gr.Button("Tạo Video")
286
+ se_video_output = gr.Video(label="Video Được Tạo")
287
+ se_message = gr.Textbox(label="Trạng Thái")
288
 
289
  se_button.click(
290
  fn=gradio_start_end_to_video,
291
+ inputs=[start_image, end_image, prompt_se, model_se, resolution_se, duration_se, movement_amplitude_se, seed_se, user_key],
292
  outputs=[se_video_output, se_message]
293
  )
294
 
295
+ # Tab cho References to Video (Placeholder)
296
+ with gr.Tab("Video Từ Hình Ảnh Tham Chiếu"):
297
+ gr.Markdown("Tải lên 1–3 hình ảnh tham chiếu tả văn bản để tạo video. (Chưa được triển khai.)")
298
+ image1 = gr.Image(type="filepath", label="Hình Ảnh Tham Chiếu 1")
299
+ image2 = gr.Image(type="filepath", label="Hình Ảnh Tham Chiếu 2 (Tùy Chọn)")
300
+ image3 = gr.Image(type="filepath", label="Hình Ảnh Tham Chiếu 3 (Tùy Chọn)")
301
+ prompt_ref = gr.Textbox(label="Mô Tả Văn Bản", placeholder=" dụ: 'Một nhân vật đi bộ trong rừng.'")
302
+ resolution_ref = gr.Dropdown(choices=["360p", "720p", "1080p"], label="Độ Phân Giải", value="720p")
303
+ duration_ref = gr.Dropdown(choices=[4, 8], label="Thời Lượng (giây)", value=4)
304
+ ref_button = gr.Button("Tạo Video")
305
+ ref_video_output = gr.Video(label="Video Được Tạo")
306
+ ref_message = gr.Textbox(label="Trạng Thái")
307
 
308
  ref_button.click(
309
  fn=gradio_references_to_video,
310
+ inputs=[image1, image2, image3, prompt_ref, resolution_ref, duration_ref, user_key],
311
  outputs=[ref_video_output, ref_message]
312
  )
313
 
314
+ # Khởi chạy ứng dụng (không cần cho HF Spaces, dùng để kiểm tra cục bộ)
315
  if __name__ == "__main__":
316
  demo.launch()