Update app.py
Browse files
app.py
CHANGED
@@ -29,7 +29,7 @@ def validate_tdn_key(user_key):
|
|
29 |
return True, "Khóa bí mật được xác thực thành công."
|
30 |
|
31 |
# Hàm kiểm tra yêu cầu hình ảnh
|
32 |
-
def validate_image(image_path, max_size_mb=10):
|
33 |
if not image_path:
|
34 |
return False, "Lỗi: Chưa cung cấp hình ảnh."
|
35 |
|
@@ -42,10 +42,12 @@ def validate_image(image_path, max_size_mb=10):
|
|
42 |
if mime_type not in ["image/png", "image/webp", "image/jpeg", "image/jpg"]:
|
43 |
return False, "Lỗi: Định dạng hình ảnh không được hỗ trợ. Sử dụng PNG, WebP, JPEG hoặc JPG."
|
44 |
|
45 |
-
# Kiểm tra tỷ lệ khung hình
|
46 |
try:
|
47 |
img = Image.open(image_path)
|
48 |
width, height = img.size
|
|
|
|
|
49 |
aspect_ratio = width / height
|
50 |
if aspect_ratio < 0.25 or aspect_ratio > 4:
|
51 |
return False, "Lỗi: Tỷ lệ khung hình phải nằm trong khoảng 1:4 đến 4:1."
|
@@ -153,10 +155,10 @@ def start_end_to_video(start_image, end_image, prompt, resolution="720p", durati
|
|
153 |
return None, error_message
|
154 |
|
155 |
# Tải hình ảnh lên TDNM
|
156 |
-
start_uri, start_message = upload_image_to_vidu(start_image)
|
157 |
if not start_uri:
|
158 |
return None, start_message
|
159 |
-
end_uri, end_message = upload_image_to_vidu(end_image)
|
160 |
if not end_uri:
|
161 |
return None, end_message
|
162 |
|
@@ -208,7 +210,7 @@ def img_to_video(image, prompt, resolution="720p", duration=4, movement_amplitud
|
|
208 |
if prompt and len(prompt) > 1500:
|
209 |
return None, "Lỗi: Mô tả văn bản không được vượt quá 1500 ký tự."
|
210 |
|
211 |
-
# Tải hình ảnh lên TDNM
|
212 |
image_uri, image_message = upload_image_to_vidu(image, max_size_mb=50)
|
213 |
if not image_uri:
|
214 |
return None, image_message
|
@@ -245,13 +247,68 @@ def img_to_video(image, prompt, resolution="720p", duration=4, movement_amplitud
|
|
245 |
logger.error(f"Phản hồi API: {response.text}")
|
246 |
return None, f"Lỗi: {str(e)} - {response.text}"
|
247 |
|
248 |
-
#
|
249 |
-
def
|
250 |
# Kiểm tra TDNM_KEY
|
251 |
valid_key, key_message = validate_tdn_key(user_key)
|
252 |
if not valid_key:
|
253 |
return None, key_message
|
254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
|
256 |
# Hàm kiểm tra trạng thái tác vụ
|
257 |
def check_task_status(task_id):
|
@@ -314,14 +371,14 @@ def gradio_img_to_video(image, prompt, resolution, duration, movement_amplitude,
|
|
314 |
video_url, status_message = check_task_status(task_id)
|
315 |
return video_url, status_message
|
316 |
|
317 |
-
# Hàm giao diện Gradio cho
|
318 |
-
def
|
319 |
reference_images = [img for img in [image1, image2, image3] if img]
|
320 |
if not reference_images:
|
321 |
return None, "Lỗi: Cần cung cấp ít nhất một hình ảnh tham chiếu."
|
322 |
if not prompt:
|
323 |
return None, "Lỗi: Cần cung cấp mô tả văn bản."
|
324 |
-
task_id, message =
|
325 |
if not task_id:
|
326 |
return None, message
|
327 |
video_url, status_message = check_task_status(task_id)
|
@@ -341,8 +398,8 @@ with gr.Blocks(title="Trình Tạo Video TDNM") as demo:
|
|
341 |
start_image = gr.Image(type="filepath", label="Hình Ảnh Đầu")
|
342 |
end_image = gr.Image(type="filepath", label="Hình Ảnh Cuối")
|
343 |
prompt_se = gr.Textbox(label="Mô Tả Văn Bản (Tùy Chọn)", placeholder="Ví dụ: 'Chuyển đổi mượt mà từ khung xe thành xe hoàn chỉnh.'")
|
344 |
-
resolution_se = gr.Dropdown(choices=["360p", "720p"
|
345 |
-
duration_se = gr.Dropdown(choices=[4
|
346 |
movement_amplitude_se = gr.Dropdown(choices=["auto", "small", "medium", "large"], label="Biên Độ Chuyển Động", value="auto")
|
347 |
seed_se = gr.Number(label="Hạt Giống (Tùy Chọn)", value=None, precision=0)
|
348 |
se_button = gr.Button("Tạo Video")
|
@@ -360,8 +417,8 @@ with gr.Blocks(title="Trình Tạo Video TDNM") as demo:
|
|
360 |
gr.Markdown("Tải lên một hình ảnh và mô tả văn bản để tạo video. Hình ảnh phải là PNG, WebP, JPEG hoặc JPG, kích thước dưới 50MB, tỷ lệ khung hình từ 1:4 đến 4:1.")
|
361 |
image_i2v = gr.Image(type="filepath", label="Hình Ảnh")
|
362 |
prompt_i2v = gr.Textbox(label="Mô Tả Văn Bản (Tùy Chọn)", placeholder="Ví dụ: 'Phi hành gia vẫy tay và camera di chuyển lên.'")
|
363 |
-
resolution_i2v = gr.Dropdown(choices=["360p", "720p"
|
364 |
-
duration_i2v = gr.Dropdown(choices=[4
|
365 |
movement_amplitude_i2v = gr.Dropdown(choices=["auto", "small", "medium", "large"], label="Biên Độ Chuyển Động", value="auto")
|
366 |
seed_i2v = gr.Number(label="Hạt Giống (Tùy Chọn)", value=None, precision=0)
|
367 |
i2v_button = gr.Button("Tạo Video")
|
@@ -374,22 +431,25 @@ with gr.Blocks(title="Trình Tạo Video TDNM") as demo:
|
|
374 |
outputs=[i2v_video_output, i2v_message]
|
375 |
)
|
376 |
|
377 |
-
# Tab cho
|
378 |
with gr.Tab("Video Từ Hình Ảnh Tham Chiếu"):
|
379 |
-
gr.Markdown("Tải lên 1–3 hình ảnh tham chiếu và mô tả văn bản để tạo video.
|
380 |
image1 = gr.Image(type="filepath", label="Hình Ảnh Tham Chiếu 1")
|
381 |
image2 = gr.Image(type="filepath", label="Hình Ảnh Tham Chiếu 2 (Tùy Chọn)")
|
382 |
image3 = gr.Image(type="filepath", label="Hình Ảnh Tham Chiếu 3 (Tùy Chọn)")
|
383 |
-
prompt_ref = gr.Textbox(label="Mô Tả Văn Bản", placeholder="Ví dụ: '
|
384 |
-
resolution_ref = gr.Dropdown(choices=["360p", "720p"
|
385 |
-
duration_ref = gr.Dropdown(choices=[4
|
|
|
|
|
|
|
386 |
ref_button = gr.Button("Tạo Video")
|
387 |
ref_video_output = gr.Video(label="Video Được Tạo")
|
388 |
ref_message = gr.Textbox(label="Trạng Thái")
|
389 |
|
390 |
ref_button.click(
|
391 |
-
fn=
|
392 |
-
inputs=[image1, image2, image3, prompt_ref, resolution_ref, duration_ref, user_key],
|
393 |
outputs=[ref_video_output, ref_message]
|
394 |
)
|
395 |
|
|
|
29 |
return True, "Khóa bí mật được xác thực thành công."
|
30 |
|
31 |
# Hàm kiểm tra yêu cầu hình ảnh
|
32 |
+
def validate_image(image_path, max_size_mb=10, min_dimensions=(128, 128)):
|
33 |
if not image_path:
|
34 |
return False, "Lỗi: Chưa cung cấp hình ảnh."
|
35 |
|
|
|
42 |
if mime_type not in ["image/png", "image/webp", "image/jpeg", "image/jpg"]:
|
43 |
return False, "Lỗi: Định dạng hình ảnh không được hỗ trợ. Sử dụng PNG, WebP, JPEG hoặc JPG."
|
44 |
|
45 |
+
# Kiểm tra kích thước và tỷ lệ khung hình
|
46 |
try:
|
47 |
img = Image.open(image_path)
|
48 |
width, height = img.size
|
49 |
+
if width < min_dimensions[0] or height < min_dimensions[1]:
|
50 |
+
return False, f"Lỗi: Kích thước hình ảnh phải ít nhất {min_dimensions[0]}x{min_dimensions[1]} pixel."
|
51 |
aspect_ratio = width / height
|
52 |
if aspect_ratio < 0.25 or aspect_ratio > 4:
|
53 |
return False, "Lỗi: Tỷ lệ khung hình phải nằm trong khoảng 1:4 đến 4:1."
|
|
|
155 |
return None, error_message
|
156 |
|
157 |
# Tải hình ảnh lên TDNM
|
158 |
+
start_uri, start_message = upload_image_to_vidu(start_image, max_size_mb=10)
|
159 |
if not start_uri:
|
160 |
return None, start_message
|
161 |
+
end_uri, end_message = upload_image_to_vidu(end_image, max_size_mb=10)
|
162 |
if not end_uri:
|
163 |
return None, end_message
|
164 |
|
|
|
210 |
if prompt and len(prompt) > 1500:
|
211 |
return None, "Lỗi: Mô tả văn bản không được vượt quá 1500 ký tự."
|
212 |
|
213 |
+
# Tải hình ảnh lên TDNM
|
214 |
image_uri, image_message = upload_image_to_vidu(image, max_size_mb=50)
|
215 |
if not image_uri:
|
216 |
return None, image_message
|
|
|
247 |
logger.error(f"Phản hồi API: {response.text}")
|
248 |
return None, f"Lỗi: {str(e)} - {response.text}"
|
249 |
|
250 |
+
# Hàm gọi API TDNM cho Reference to Video
|
251 |
+
def reference_to_video(reference_images, prompt, resolution="720p", duration=4, aspect_ratio="16:9", movement_amplitude="auto", seed=None, user_key=None):
|
252 |
# Kiểm tra TDNM_KEY
|
253 |
valid_key, key_message = validate_tdn_key(user_key)
|
254 |
if not valid_key:
|
255 |
return None, key_message
|
256 |
+
|
257 |
+
if not VIDU_API_KEY:
|
258 |
+
return None, "Lỗi: Khóa API TDNM chưa được cấu hình."
|
259 |
+
|
260 |
+
if not reference_images:
|
261 |
+
return None, "Lỗi: Cần cung cấp ít nhất một hình ảnh tham chiếu."
|
262 |
+
|
263 |
+
if len(reference_images) > 3:
|
264 |
+
return None, "Lỗi: Chỉ được cung cấp tối đa 3 hình ảnh tham chiếu."
|
265 |
+
|
266 |
+
if not prompt:
|
267 |
+
return None, "Lỗi: Cần cung cấp mô tả văn bản."
|
268 |
+
|
269 |
+
if prompt and len(prompt) > 1500:
|
270 |
+
return None, "Lỗi: Mô tả văn bản không được vượt quá 1500 ký tự."
|
271 |
+
|
272 |
+
# Tải hình ảnh lên TDNM
|
273 |
+
image_uris = []
|
274 |
+
for image in reference_images:
|
275 |
+
uri, message = upload_image_to_vidu(image, max_size_mb=50)
|
276 |
+
if not uri:
|
277 |
+
return None, message
|
278 |
+
image_uris.append(uri)
|
279 |
+
|
280 |
+
url = f"{VIDU_API_URL}/ent/v2/reference2video"
|
281 |
+
headers = {
|
282 |
+
"Authorization": f"Token {VIDU_API_KEY}",
|
283 |
+
"Content-Type": "application/json"
|
284 |
+
}
|
285 |
+
|
286 |
+
payload = {
|
287 |
+
"model": DEFAULT_MODEL,
|
288 |
+
"images": image_uris,
|
289 |
+
"prompt": prompt,
|
290 |
+
"duration": duration,
|
291 |
+
"aspect_ratio": aspect_ratio,
|
292 |
+
"resolution": resolution,
|
293 |
+
"movement_amplitude": movement_amplitude
|
294 |
+
}
|
295 |
+
if seed is not None:
|
296 |
+
payload["seed"] = seed
|
297 |
+
|
298 |
+
try:
|
299 |
+
logger.info(f"Gửi yêu cầu đến API TDNM Reference to Video: {payload}")
|
300 |
+
response = requests.post(url, json=payload, headers=headers)
|
301 |
+
response.raise_for_status()
|
302 |
+
result = response.json()
|
303 |
+
task_id = result.get("task_id")
|
304 |
+
if not task_id:
|
305 |
+
return None, "Lỗi: Không nhận được ID tác vụ."
|
306 |
+
return task_id, f"Tác vụ được tạo thành công. ID tác vụ: {task_id}"
|
307 |
+
except requests.exceptions.RequestException as e:
|
308 |
+
logger.error(f"Lỗi API: {str(e)}")
|
309 |
+
if response.text:
|
310 |
+
logger.error(f"Phản hồi API: {response.text}")
|
311 |
+
return None, f"Lỗi: {str(e)} - {response.text}"
|
312 |
|
313 |
# Hàm kiểm tra trạng thái tác vụ
|
314 |
def check_task_status(task_id):
|
|
|
371 |
video_url, status_message = check_task_status(task_id)
|
372 |
return video_url, status_message
|
373 |
|
374 |
+
# Hàm giao diện Gradio cho Reference to Video
|
375 |
+
def gradio_reference_to_video(image1, image2, image3, prompt, resolution, duration, aspect_ratio, movement_amplitude, seed, user_key):
|
376 |
reference_images = [img for img in [image1, image2, image3] if img]
|
377 |
if not reference_images:
|
378 |
return None, "Lỗi: Cần cung cấp ít nhất một hình ảnh tham chiếu."
|
379 |
if not prompt:
|
380 |
return None, "Lỗi: Cần cung cấp mô tả văn bản."
|
381 |
+
task_id, message = reference_to_video(reference_images, prompt, resolution, duration, aspect_ratio, movement_amplitude, seed, user_key)
|
382 |
if not task_id:
|
383 |
return None, message
|
384 |
video_url, status_message = check_task_status(task_id)
|
|
|
398 |
start_image = gr.Image(type="filepath", label="Hình Ảnh Đầu")
|
399 |
end_image = gr.Image(type="filepath", label="Hình Ảnh Cuối")
|
400 |
prompt_se = gr.Textbox(label="Mô Tả Văn Bản (Tùy Chọn)", placeholder="Ví dụ: 'Chuyển đổi mượt mà từ khung xe thành xe hoàn chỉnh.'")
|
401 |
+
resolution_se = gr.Dropdown(choices=["360p", "720p"], label="Độ Phân Giải", value="720p")
|
402 |
+
duration_se = gr.Dropdown(choices=[4], label="Thời Lượng (giây)", value=4)
|
403 |
movement_amplitude_se = gr.Dropdown(choices=["auto", "small", "medium", "large"], label="Biên Độ Chuyển Động", value="auto")
|
404 |
seed_se = gr.Number(label="Hạt Giống (Tùy Chọn)", value=None, precision=0)
|
405 |
se_button = gr.Button("Tạo Video")
|
|
|
417 |
gr.Markdown("Tải lên một hình ảnh và mô tả văn bản để tạo video. Hình ảnh phải là PNG, WebP, JPEG hoặc JPG, kích thước dưới 50MB, tỷ lệ khung hình từ 1:4 đến 4:1.")
|
418 |
image_i2v = gr.Image(type="filepath", label="Hình Ảnh")
|
419 |
prompt_i2v = gr.Textbox(label="Mô Tả Văn Bản (Tùy Chọn)", placeholder="Ví dụ: 'Phi hành gia vẫy tay và camera di chuyển lên.'")
|
420 |
+
resolution_i2v = gr.Dropdown(choices=["360p", "720p"], label="Độ Phân Giải", value="720p")
|
421 |
+
duration_i2v = gr.Dropdown(choices=[4], label="Thời Lượng (giây)", value=4)
|
422 |
movement_amplitude_i2v = gr.Dropdown(choices=["auto", "small", "medium", "large"], label="Biên Độ Chuyển Động", value="auto")
|
423 |
seed_i2v = gr.Number(label="Hạt Giống (Tùy Chọn)", value=None, precision=0)
|
424 |
i2v_button = gr.Button("Tạo Video")
|
|
|
431 |
outputs=[i2v_video_output, i2v_message]
|
432 |
)
|
433 |
|
434 |
+
# Tab cho Reference to Video
|
435 |
with gr.Tab("Video Từ Hình Ảnh Tham Chiếu"):
|
436 |
+
gr.Markdown("Tải lên 1–3 hình ảnh tham chiếu và mô tả văn bản để tạo video với chủ thể nhất quán. Hình ảnh phải là PNG, WebP, JPEG hoặc JPG, kích thước dưới 50MB, độ phân giải tối thiểu 128x128, tỷ lệ khung hình từ 1:4 đến 4:1.")
|
437 |
image1 = gr.Image(type="filepath", label="Hình Ảnh Tham Chiếu 1")
|
438 |
image2 = gr.Image(type="filepath", label="Hình Ảnh Tham Chiếu 2 (Tùy Chọn)")
|
439 |
image3 = gr.Image(type="filepath", label="Hình Ảnh Tham Chiếu 3 (Tùy Chọn)")
|
440 |
+
prompt_ref = gr.Textbox(label="Mô Tả Văn Bản", placeholder="Ví dụ: 'Ông già Noel và gấu ôm nhau bên hồ.'")
|
441 |
+
resolution_ref = gr.Dropdown(choices=["360p", "720p"], label="Độ Phân Giải", value="720p")
|
442 |
+
duration_ref = gr.Dropdown(choices=[4], label="Thời Lượng (giây)", value=4)
|
443 |
+
aspect_ratio_ref = gr.Dropdown(choices=["16:9", "9:16", "1:1"], label="Tỷ Lệ Khung Hình", value="16:9")
|
444 |
+
movement_amplitude_ref = gr.Dropdown(choices=["auto", "small", "medium", "large"], label="Biên Độ Chuyển Động", value="auto")
|
445 |
+
seed_ref = gr.Number(label="Hạt Giống (Tùy Chọn)", value=None, precision=0)
|
446 |
ref_button = gr.Button("Tạo Video")
|
447 |
ref_video_output = gr.Video(label="Video Được Tạo")
|
448 |
ref_message = gr.Textbox(label="Trạng Thái")
|
449 |
|
450 |
ref_button.click(
|
451 |
+
fn=gradio_reference_to_video,
|
452 |
+
inputs=[image1, image2, image3, prompt_ref, resolution_ref, duration_ref, aspect_ratio_ref, movement_amplitude_ref, seed_ref, user_key],
|
453 |
outputs=[ref_video_output, ref_message]
|
454 |
)
|
455 |
|