Update app.py
Browse files
app.py
CHANGED
@@ -11,11 +11,12 @@ 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")
|
15 |
-
TDNM_KEY = os.getenv("TDNM_KEY")
|
16 |
-
VIDU_API_URL = "https://api.vidu.com"
|
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):
|
@@ -28,13 +29,13 @@ def validate_tdn_key(user_key):
|
|
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
|
36 |
-
if os.path.getsize(image_path) >
|
37 |
-
return False, "Lỗi: Kích thước hình ảnh vượt quá
|
38 |
|
39 |
# Kiểm tra định dạng
|
40 |
mime_type, _ = mimetypes.guess_type(image_path)
|
@@ -52,7 +53,7 @@ def validate_image(image_path):
|
|
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
|
56 |
def validate_pixel_density(start_image, end_image):
|
57 |
try:
|
58 |
start_img = Image.open(start_image)
|
@@ -67,12 +68,12 @@ def validate_pixel_density(start_image, end_image):
|
|
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 |
|
@@ -131,7 +132,7 @@ def upload_image_to_vidu(image_path):
|
|
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,
|
135 |
# Kiểm tra TDNM_KEY
|
136 |
valid_key, key_message = validate_tdn_key(user_key)
|
137 |
if not valid_key:
|
@@ -166,7 +167,7 @@ def start_end_to_video(start_image, end_image, prompt, model="vidu2.0", resoluti
|
|
166 |
}
|
167 |
|
168 |
payload = {
|
169 |
-
"model":
|
170 |
"images": [start_uri, end_uri],
|
171 |
"prompt": prompt or "",
|
172 |
"duration": duration,
|
@@ -191,6 +192,59 @@ def start_end_to_video(start_image, end_image, prompt, model="vidu2.0", resoluti
|
|
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
|
@@ -241,10 +295,20 @@ def check_task_status(task_id):
|
|
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,
|
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,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
if not task_id:
|
249 |
return None, message
|
250 |
video_url, status_message = check_task_status(task_id)
|
@@ -268,7 +332,7 @@ 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ả
|
272 |
user_key = gr.Textbox(label="Khóa Bí Mật (TDNM_KEY)", type="password", placeholder="Nhập khóa bí mật của bạn")
|
273 |
|
274 |
# Tab cho Start-End to Video
|
@@ -277,7 +341,6 @@ with gr.Blocks(title="Trình Tạo Video TDNM") as demo:
|
|
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="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.'")
|
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")
|
@@ -288,10 +351,29 @@ with gr.Blocks(title="Trình Tạo Video TDNM") as demo:
|
|
288 |
|
289 |
se_button.click(
|
290 |
fn=gradio_start_end_to_video,
|
291 |
-
inputs=[start_image, end_image, prompt_se,
|
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 và mô tả văn bản để tạo video. (Chưa được triển khai.)")
|
|
|
11 |
logger = logging.getLogger(__name__)
|
12 |
|
13 |
# Cấu hình API TDNM
|
14 |
+
VIDU_API_KEY = os.getenv("VIDU_API_KEY")
|
15 |
+
TDNM_KEY = os.getenv("TDNM_KEY")
|
16 |
+
VIDU_API_URL = "https://api.vidu.com"
|
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 |
+
DEFAULT_MODEL = "vidu2.0" # Mô hình mặc định
|
20 |
|
21 |
# Hàm kiểm tra TDNM_KEY
|
22 |
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 |
|
36 |
+
# Kiểm tra kích thước tệp
|
37 |
+
if os.path.getsize(image_path) > max_size_mb * 1024 * 1024:
|
38 |
+
return False, f"Lỗi: Kích thước hình ảnh vượt quá {max_size_mb}MB."
|
39 |
|
40 |
# Kiểm tra định dạng
|
41 |
mime_type, _ = mimetypes.guess_type(image_path)
|
|
|
53 |
except Exception as e:
|
54 |
return False, f"Lỗi khi kiểm tra hình ảnh: {str(e)}"
|
55 |
|
56 |
+
# Hàm kiểm tra tỷ lệ mật độ điểm ảnh (cho start-end)
|
57 |
def validate_pixel_density(start_image, end_image):
|
58 |
try:
|
59 |
start_img = Image.open(start_image)
|
|
|
68 |
return False, f"Lỗi khi kiểm tra mật độ điểm ảnh: {str(e)}"
|
69 |
|
70 |
# Hàm tải hình ảnh lên TDNM
|
71 |
+
def upload_image_to_vidu(image_path, max_size_mb=10):
|
72 |
if not VIDU_API_KEY:
|
73 |
return None, "Lỗi: Khóa API TDNM chưa được cấu hình."
|
74 |
|
75 |
# Kiểm tra hình ảnh
|
76 |
+
valid, error_message = validate_image(image_path, max_size_mb)
|
77 |
if not valid:
|
78 |
return None, error_message
|
79 |
|
|
|
132 |
return None, f"Lỗi khi hoàn tất tải lên: {str(e)}"
|
133 |
|
134 |
# Hàm gọi API TDNM cho Start-End to Video
|
135 |
+
def start_end_to_video(start_image, end_image, prompt, resolution="720p", duration=4, movement_amplitude="auto", seed=None, user_key=None):
|
136 |
# Kiểm tra TDNM_KEY
|
137 |
valid_key, key_message = validate_tdn_key(user_key)
|
138 |
if not valid_key:
|
|
|
167 |
}
|
168 |
|
169 |
payload = {
|
170 |
+
"model": DEFAULT_MODEL,
|
171 |
"images": [start_uri, end_uri],
|
172 |
"prompt": prompt or "",
|
173 |
"duration": duration,
|
|
|
192 |
logger.error(f"Phản hồi API: {response.text}")
|
193 |
return None, f"Lỗi: {str(e)} - {response.text}"
|
194 |
|
195 |
+
# Hàm gọi API TDNM cho Img to Video
|
196 |
+
def img_to_video(image, prompt, resolution="720p", duration=4, movement_amplitude="auto", seed=None, user_key=None):
|
197 |
+
# Kiểm tra TDNM_KEY
|
198 |
+
valid_key, key_message = validate_tdn_key(user_key)
|
199 |
+
if not valid_key:
|
200 |
+
return None, key_message
|
201 |
+
|
202 |
+
if not VIDU_API_KEY:
|
203 |
+
return None, "Lỗi: Khóa API TDNM chưa được cấu hình."
|
204 |
+
|
205 |
+
if not image:
|
206 |
+
return None, "Lỗi: Cần cung cấp một hình ảnh."
|
207 |
+
|
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 (giới hạn 50MB cho img2video)
|
212 |
+
image_uri, image_message = upload_image_to_vidu(image, max_size_mb=50)
|
213 |
+
if not image_uri:
|
214 |
+
return None, image_message
|
215 |
+
|
216 |
+
url = f"{VIDU_API_URL}/ent/v2/img2video"
|
217 |
+
headers = {
|
218 |
+
"Authorization": f"Token {VIDU_API_KEY}",
|
219 |
+
"Content-Type": "application/json"
|
220 |
+
}
|
221 |
+
|
222 |
+
payload = {
|
223 |
+
"model": DEFAULT_MODEL,
|
224 |
+
"images": [image_uri],
|
225 |
+
"prompt": prompt or "",
|
226 |
+
"duration": duration,
|
227 |
+
"resolution": resolution,
|
228 |
+
"movement_amplitude": movement_amplitude
|
229 |
+
}
|
230 |
+
if seed is not None:
|
231 |
+
payload["seed"] = seed
|
232 |
+
|
233 |
+
try:
|
234 |
+
logger.info(f"Gửi yêu cầu đến API TDNM Img to Video: {payload}")
|
235 |
+
response = requests.post(url, json=payload, headers=headers)
|
236 |
+
response.raise_for_status()
|
237 |
+
result = response.json()
|
238 |
+
task_id = result.get("task_id")
|
239 |
+
if not task_id:
|
240 |
+
return None, "Lỗi: Không nhận được ID tác vụ."
|
241 |
+
return task_id, f"Tác vụ được tạo thành công. ID tác vụ: {task_id}"
|
242 |
+
except requests.exceptions.RequestException as e:
|
243 |
+
logger.error(f"Lỗi API: {str(e)}")
|
244 |
+
if response.text:
|
245 |
+
logger.error(f"Phản hồi API: {response.text}")
|
246 |
+
return None, f"Lỗi: {str(e)} - {response.text}"
|
247 |
+
|
248 |
# Placeholder cho References to Video
|
249 |
def references_to_video(reference_images, prompt, resolution="720p", duration=4, user_key=None):
|
250 |
# Kiểm tra TDNM_KEY
|
|
|
295 |
return None, f"Lỗi: Hết thời gian tạo video. ID tác vụ: {task_id}"
|
296 |
|
297 |
# Hàm giao diện Gradio cho Start-End to Video
|
298 |
+
def gradio_start_end_to_video(start_image, end_image, prompt, resolution, duration, movement_amplitude, seed, user_key):
|
299 |
if not start_image or not end_image:
|
300 |
return None, "Lỗi: Cần cung cấp cả hai hình ảnh đầu và cuối."
|
301 |
+
task_id, message = start_end_to_video(start_image, end_image, prompt, resolution, duration, movement_amplitude, seed, user_key)
|
302 |
+
if not task_id:
|
303 |
+
return None, message
|
304 |
+
video_url, status_message = check_task_status(task_id)
|
305 |
+
return video_url, status_message
|
306 |
+
|
307 |
+
# Hàm giao diện Gradio cho Img to Video
|
308 |
+
def gradio_img_to_video(image, prompt, resolution, duration, movement_amplitude, seed, user_key):
|
309 |
+
if not image:
|
310 |
+
return None, "Lỗi: Cần cung cấp một hình ảnh."
|
311 |
+
task_id, message = img_to_video(image, prompt, resolution, duration, movement_amplitude, seed, user_key)
|
312 |
if not task_id:
|
313 |
return None, message
|
314 |
video_url, status_message = check_task_status(task_id)
|
|
|
332 |
gr.Markdown("# Trình Tạo Video TDNM")
|
333 |
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.")
|
334 |
|
335 |
+
# Trường nhập khóa bí mật (áp dụng cho tất cả các tab)
|
336 |
user_key = gr.Textbox(label="Khóa Bí Mật (TDNM_KEY)", type="password", placeholder="Nhập khóa bí mật của bạn")
|
337 |
|
338 |
# Tab cho Start-End to Video
|
|
|
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", "1080p"], label="Độ Phân Giải", value="720p")
|
345 |
duration_se = gr.Dropdown(choices=[4, 8], label="Thời Lượng (giây)", value=4)
|
346 |
movement_amplitude_se = gr.Dropdown(choices=["auto", "small", "medium", "large"], label="Biên Độ Chuyển Động", value="auto")
|
|
|
351 |
|
352 |
se_button.click(
|
353 |
fn=gradio_start_end_to_video,
|
354 |
+
inputs=[start_image, end_image, prompt_se, resolution_se, duration_se, movement_amplitude_se, seed_se, user_key],
|
355 |
outputs=[se_video_output, se_message]
|
356 |
)
|
357 |
|
358 |
+
# Tab cho Img to Video
|
359 |
+
with gr.Tab("Video Từ Một Ảnh"):
|
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", "1080p"], label="Độ Phân Giải", value="720p")
|
364 |
+
duration_i2v = gr.Dropdown(choices=[4, 8], label="Thời Lượng (giây)", value=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")
|
368 |
+
i2v_video_output = gr.Video(label="Video Được Tạo")
|
369 |
+
i2v_message = gr.Textbox(label="Trạng Thái")
|
370 |
+
|
371 |
+
i2v_button.click(
|
372 |
+
fn=gradio_img_to_video,
|
373 |
+
inputs=[image_i2v, prompt_i2v, resolution_i2v, duration_i2v, movement_amplitude_i2v, seed_i2v, user_key],
|
374 |
+
outputs=[i2v_video_output, i2v_message]
|
375 |
+
)
|
376 |
+
|
377 |
# Tab cho References to Video (Placeholder)
|
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. (Chưa được triển khai.)")
|