Update app.py
Browse files
app.py
CHANGED
@@ -6,42 +6,53 @@ import logging
|
|
6 |
from PIL import Image
|
7 |
import mimetypes
|
8 |
|
9 |
-
#
|
10 |
logging.basicConfig(level=logging.INFO)
|
11 |
logger = logging.getLogger(__name__)
|
12 |
|
13 |
-
#
|
14 |
-
VIDU_API_KEY = os.getenv("VIDU_API_KEY")
|
15 |
-
|
16 |
-
|
17 |
-
|
|
|
18 |
|
19 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
def validate_image(image_path):
|
21 |
if not image_path:
|
22 |
-
return False, "
|
23 |
|
24 |
-
#
|
25 |
if os.path.getsize(image_path) > 10 * 1024 * 1024:
|
26 |
-
return False, "
|
27 |
|
28 |
-
#
|
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, "
|
32 |
|
33 |
-
#
|
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, "
|
40 |
return True, None
|
41 |
except Exception as e:
|
42 |
-
return False, f"
|
43 |
|
44 |
-
#
|
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, "
|
54 |
return True, None
|
55 |
except Exception as e:
|
56 |
-
return False, f"
|
57 |
|
58 |
-
#
|
59 |
def upload_image_to_vidu(image_path):
|
60 |
if not VIDU_API_KEY:
|
61 |
-
return None, "
|
62 |
|
63 |
-
#
|
64 |
valid, error_message = validate_image(image_path)
|
65 |
if not valid:
|
66 |
return None, error_message
|
67 |
|
68 |
-
#
|
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("
|
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, "
|
85 |
except requests.exceptions.RequestException as e:
|
86 |
-
logger.error(f"
|
87 |
-
return None, f"
|
88 |
|
89 |
-
#
|
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("
|
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, "
|
101 |
except requests.exceptions.RequestException as e:
|
102 |
-
logger.error(f"
|
103 |
-
return None, f"
|
104 |
|
105 |
-
#
|
106 |
url = f"{VIDU_API_URL}/tools/v2/files/uploads/{resource_id}/finish"
|
107 |
payload = {"etag": etag.strip('"')}
|
108 |
|
109 |
try:
|
110 |
-
logger.info("
|
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, "
|
117 |
-
return uri, "
|
118 |
except requests.exceptions.RequestException as e:
|
119 |
-
logger.error(f"
|
120 |
-
return None, f"
|
121 |
|
122 |
-
#
|
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, "
|
126 |
|
127 |
if not start_image or not end_image:
|
128 |
-
return None, "
|
129 |
|
130 |
if prompt and len(prompt) > 1500:
|
131 |
-
return None, "
|
132 |
|
133 |
-
#
|
134 |
valid, error_message = validate_pixel_density(start_image, end_image)
|
135 |
if not valid:
|
136 |
return None, error_message
|
137 |
|
138 |
-
#
|
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"
|
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, "
|
171 |
-
return task_id, f"
|
172 |
except requests.exceptions.RequestException as e:
|
173 |
-
logger.error(f"API
|
174 |
if response.text:
|
175 |
-
logger.error(f"API
|
176 |
-
return None, f"
|
177 |
|
178 |
-
# Placeholder
|
179 |
-
def references_to_video(reference_images, prompt, resolution="720p", duration=4):
|
180 |
-
|
|
|
|
|
|
|
|
|
181 |
|
182 |
-
#
|
183 |
def check_task_status(task_id):
|
184 |
if not task_id:
|
185 |
-
return None, "
|
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"
|
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, "
|
208 |
-
return None, "
|
209 |
elif state == "failed":
|
210 |
-
return None, f"
|
211 |
elif state in ["created", "queueing", "processing"]:
|
212 |
time.sleep(POLL_INTERVAL)
|
213 |
else:
|
214 |
-
return None, f"
|
215 |
except requests.exceptions.RequestException as e:
|
216 |
-
logger.error(f"
|
217 |
if response.text:
|
218 |
-
logger.error(f"
|
219 |
-
return None, f"
|
220 |
|
221 |
-
return None, f"
|
222 |
|
223 |
-
#
|
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, "
|
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 |
-
#
|
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, "
|
238 |
if not prompt:
|
239 |
-
return None, "
|
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
|
247 |
-
with gr.Blocks(title="
|
248 |
-
gr.Markdown("#
|
249 |
-
gr.Markdown("
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
|
|
|
|
|
|
|
|
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
|
272 |
-
with gr.Tab("
|
273 |
-
gr.Markdown("
|
274 |
-
image1 = gr.Image(type="filepath", label="
|
275 |
-
image2 = gr.Image(type="filepath", label="
|
276 |
-
image3 = gr.Image(type="filepath", label="
|
277 |
-
prompt_ref = gr.Textbox(label="
|
278 |
-
resolution_ref = gr.Dropdown(choices=["360p", "720p", "1080p"], label="
|
279 |
-
duration_ref = gr.Dropdown(choices=[4, 8], label="
|
280 |
-
ref_button = gr.Button("
|
281 |
-
ref_video_output = gr.Video(label="
|
282 |
-
ref_message = gr.Textbox(label="
|
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 |
-
#
|
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: Mô tả văn bản không được vượt quá 1500 ký 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 có URL video trong kết quả."
|
229 |
elif state == "failed":
|
230 |
+
return None, f"Lỗi: Tạo video thất bại. Mã 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 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
|
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="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")
|
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 và mô 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="Ví 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()
|