Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -1235,11 +1235,18 @@ class AudioDropUpload(gr.HTML):
|
|
| 1235 |
### PART 17: Wrapper Functions (Resolution, Duration, Examples)
|
| 1236 |
####################################################################################################
|
| 1237 |
def generate_video_example(first_frame, prompt, camera_lora, resolution, radioanimated_mode, input_video, input_audio, end_frame, progress=gr.Progress(track_tqdm=True)):
|
|
|
|
| 1238 |
w, h = apply_resolution(resolution)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1239 |
duration_s = 5
|
| 1240 |
|
| 1241 |
with timer(f'generating with LoRA:{camera_lora} in {w}x{h}'):
|
| 1242 |
-
output_video
|
| 1243 |
first_frame,
|
| 1244 |
end_frame,
|
| 1245 |
prompt,
|
|
@@ -1255,7 +1262,7 @@ def generate_video_example(first_frame, prompt, camera_lora, resolution, radioan
|
|
| 1255 |
input_audio,
|
| 1256 |
progress
|
| 1257 |
)
|
| 1258 |
-
return output_video
|
| 1259 |
|
| 1260 |
def get_duration(
|
| 1261 |
first_frame,
|
|
@@ -1274,20 +1281,21 @@ def get_duration(
|
|
| 1274 |
progress
|
| 1275 |
):
|
| 1276 |
extra_time = 0
|
| 1277 |
-
|
| 1278 |
-
if
|
| 1279 |
-
|
| 1280 |
-
|
| 1281 |
-
|
| 1282 |
-
|
| 1283 |
-
|
| 1284 |
-
|
| 1285 |
-
|
| 1286 |
-
|
| 1287 |
-
|
| 1288 |
-
|
| 1289 |
-
|
| 1290 |
-
|
|
|
|
| 1291 |
|
| 1292 |
|
| 1293 |
####################################################################################################
|
|
@@ -1300,7 +1308,7 @@ def generate_video(
|
|
| 1300 |
prompt: str,
|
| 1301 |
duration: float,
|
| 1302 |
input_video = None,
|
| 1303 |
-
generation_mode = "تبدیل تصویر به ویدیو",
|
| 1304 |
enhance_prompt: bool = True,
|
| 1305 |
seed: int = 42,
|
| 1306 |
randomize_seed: bool = True,
|
|
@@ -1310,184 +1318,971 @@ def generate_video(
|
|
| 1310 |
audio_path = None,
|
| 1311 |
progress=gr.Progress(track_tqdm=True),
|
| 1312 |
):
|
| 1313 |
-
|
| 1314 |
-
|
| 1315 |
-
|
| 1316 |
-
else:
|
| 1317 |
-
print(f'generating with duration:{duration} and audio in {width}x{height}')
|
| 1318 |
|
| 1319 |
-
|
| 1320 |
-
|
| 1321 |
-
|
| 1322 |
-
|
| 1323 |
-
|
| 1324 |
-
|
| 1325 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1326 |
images = []
|
| 1327 |
-
|
| 1328 |
-
|
| 1329 |
-
|
| 1330 |
-
|
| 1331 |
-
|
| 1332 |
-
|
| 1333 |
-
|
| 1334 |
-
|
| 1335 |
-
|
| 1336 |
-
|
| 1337 |
-
|
| 1338 |
-
|
| 1339 |
-
|
| 1340 |
-
|
| 1341 |
-
|
| 1342 |
-
|
| 1343 |
-
|
| 1344 |
-
|
| 1345 |
-
|
| 1346 |
-
audio_context = n_audio_context
|
| 1347 |
-
if len(videos) == 0:
|
| 1348 |
-
camera_lora = "Static"
|
| 1349 |
-
torch.cuda.empty_cache()
|
| 1350 |
-
|
| 1351 |
-
name_to_idx = {name: idx for name, idx in RUNTIME_LORA_CHOICES}
|
| 1352 |
-
selected_idx = name_to_idx.get(camera_lora, -1)
|
| 1353 |
-
enable_only_lora(pipeline._transformer, selected_idx)
|
| 1354 |
-
torch.cuda.empty_cache()
|
| 1355 |
-
|
| 1356 |
-
video_seconds = (num_frames - 1) / frame_rate
|
| 1357 |
-
input_waveform, input_waveform_sample_rate = (None, None)
|
| 1358 |
-
if audio_path is not None:
|
| 1359 |
-
input_waveform, input_waveform_sample_rate = match_audio_to_duration(audio_path=audio_path, target_seconds=video_seconds, device="cuda")
|
| 1360 |
-
|
| 1361 |
-
with timer(f'generating with LoRA:{camera_lora} in {width}x{height}'):
|
| 1362 |
-
with torch.inference_mode():
|
| 1363 |
-
pipeline(prompt=prompt, output_path=str(output_path), seed=current_seed, height=height, width=width, num_frames=num_frames, frame_rate=frame_rate, images=images, video_conditioning=videos, tiling_config=TilingConfig.default(), video_context=video_context, audio_context=audio_context, input_waveform=input_waveform, input_waveform_sample_rate=input_waveform_sample_rate)
|
| 1364 |
-
|
| 1365 |
-
del video_context, audio_context
|
| 1366 |
-
torch.cuda.empty_cache()
|
| 1367 |
-
print("successful generation")
|
| 1368 |
|
| 1369 |
-
return str(output_path), "" # Return video path and an empty status message on success
|
| 1370 |
|
| 1371 |
-
|
| 1372 |
-
|
| 1373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1374 |
|
| 1375 |
-
# --- مدیریت خطای GPU QUOTA ---
|
| 1376 |
-
# اگر خطا مربوط به سهمیه باشد، آن را مجدداً ارسال میکنیم تا JS آن را بگیرد
|
| 1377 |
-
if "quota" in error_str.lower() or "exceeded" in error_str.lower():
|
| 1378 |
-
raise e
|
| 1379 |
-
|
| 1380 |
-
# برای خطاهای دیگر، پیام خطا را در استاتوس باکس نمایش میدهیم
|
| 1381 |
-
return None, get_error_html(f"خطایی رخ داد: {error_str}")
|
| 1382 |
|
| 1383 |
def apply_resolution(resolution: str):
|
| 1384 |
-
|
| 1385 |
-
|
| 1386 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1387 |
return int(w), int(h)
|
| 1388 |
|
| 1389 |
def apply_duration(duration: str):
|
| 1390 |
-
|
|
|
|
| 1391 |
|
| 1392 |
def on_mode_change(selected: str):
|
|
|
|
|
|
|
| 1393 |
is_interpolate = (selected == "تکمیل فریمهای میانی")
|
| 1394 |
-
|
|
|
|
| 1395 |
|
| 1396 |
|
| 1397 |
####################################################################################################
|
| 1398 |
### PART 19: CSS Styles
|
| 1399 |
####################################################################################################
|
| 1400 |
css = """
|
| 1401 |
-
/* All previous CSS from Part 19 goes here... */
|
| 1402 |
-
#controls-row { display: none !important; }
|
| 1403 |
-
/* ... (keep all the existing CSS) ... */
|
| 1404 |
-
.aud-filelabel{ margin: 10px 6px 0; color: var(--body-text-color-subdued); font-size: 0.95rem; display: none; }
|
| 1405 |
-
#audio_input_hidden { display: none !important; }
|
| 1406 |
|
| 1407 |
-
/*
|
| 1408 |
-
|
| 1409 |
-
|
| 1410 |
-
-
|
| 1411 |
-
|
| 1412 |
-
-
|
| 1413 |
-
|
| 1414 |
-
|
| 1415 |
-
|
| 1416 |
-
-
|
| 1417 |
-
|
| 1418 |
-
|
| 1419 |
-
-
|
| 1420 |
-
|
| 1421 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1422 |
|
| 1423 |
-
|
| 1424 |
-
|
| 1425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1426 |
}
|
| 1427 |
-
|
| 1428 |
-
|
| 1429 |
-
to { opacity: 1; transform: translateY(0); }
|
| 1430 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1431 |
|
| 1432 |
-
.ip-reset-guide-container {
|
| 1433 |
-
text-align: right;
|
| 1434 |
-
direction: rtl;
|
| 1435 |
-
background: var(--guide-bg);
|
| 1436 |
-
backdrop-filter: blur(10px);
|
| 1437 |
-
padding: 20px;
|
| 1438 |
-
border-radius: var(--radius-lg-guide);
|
| 1439 |
-
box-shadow: var(--shadow-xl);
|
| 1440 |
-
border: 1px solid var(--guide-border);
|
| 1441 |
-
animation: slideInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1) both;
|
| 1442 |
-
width: 90%;
|
| 1443 |
-
max-width: 420px;
|
| 1444 |
-
max-height: 90vh;
|
| 1445 |
-
overflow-y: auto;
|
| 1446 |
-
position: relative;
|
| 1447 |
box-sizing: border-box;
|
| 1448 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1449 |
}
|
| 1450 |
-
.
|
| 1451 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1452 |
}
|
| 1453 |
-
.guide-header { display: flex; align-items: center; margin-bottom: 15px; }
|
| 1454 |
-
.guide-header-icon { width: 45px; height: 45px; margin-left: 15px; animation: float 3s ease-in-out infinite; flex-shrink: 0; }
|
| 1455 |
-
.guide-header h2 { font-size: 1.2rem; color: var(--guide-text-title); font-weight: 700; margin: 0; }
|
| 1456 |
-
.guide-header p { color: var(--guide-text-body); font-size: 0.8rem; margin-top: 3px; margin-bottom: 0; }
|
| 1457 |
-
.guide-content { font-size: 0.9rem; color: var(--guide-text-body); line-height: 1.6; }
|
| 1458 |
-
.info-card { background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); border: 1px solid rgba(102, 126, 234, 0.2); border-radius: var(--radius-md-guide); padding: 12px; margin: 12px 0; position: relative; overflow: hidden; }
|
| 1459 |
-
.info-card p { font-size: 0.85rem; line-height: 1.6; margin: 0; }
|
| 1460 |
-
.info-card::before { content: ''; position: absolute; top: 0; right: 0; width: 3px; height: 100%; background: var(--primary-gradient-guide); }
|
| 1461 |
-
.info-card-header { display: flex; align-items: center; margin-bottom: 8px; }
|
| 1462 |
-
.info-card-icon { width: 18px; height: 18px; margin-left: 8px; }
|
| 1463 |
-
.info-card-title { font-weight: 600; color: var(--guide-text-title); font-size: 0.95rem; }
|
| 1464 |
-
.summary-section { margin-top: 12px; padding: 12px; border-radius: var(--radius-md-guide); background: linear-gradient(135deg, #56ab2f15 0%, #a8e06315 100%); border: 1px solid rgba(86, 171, 47, 0.2); position: relative; overflow: hidden; }
|
| 1465 |
-
.summary-section::before { content: ''; position: absolute; top: 0; right: 0; width: 3px; height: 100%; background: var(--success-gradient-guide); }
|
| 1466 |
-
.summary-header { display: flex; align-items: center; margin-bottom: 8px; }
|
| 1467 |
-
.summary-icon { width: 18px; height: 18px; margin-left: 8px; }
|
| 1468 |
-
.summary-title { font-weight: 600; color: #2f5a33; font-size: 0.95rem; }
|
| 1469 |
-
.summary-text { color: #2f5a33; font-size: 0.85rem; line-height: 1.6; }
|
| 1470 |
-
.video-button-container { text-align: center; margin: 20px 0 15px 0; width: 100%; }
|
| 1471 |
-
.elegant-video-button { display: inline-flex !important; align-items: center; justify-content: center; padding: 10px 20px !important; background-color: #fff !important; color: var(--guide-accent) !important; border: 1px solid #e2e8f0 !important; text-decoration: none; border-radius: 50px !important; font-weight: 600 !important; font-size: 0.9rem !important; cursor: pointer !important; font-family: inherit; transition: all 0.3s ease !important; box-shadow: 0 2px 10px rgba(0,0,0,0.05) !important; width: auto !important; }
|
| 1472 |
-
.elegant-video-button:hover { background: var(--primary-gradient-guide) !important; color: white !important; border-color: transparent !important; transform: translateY(-2px); box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3) !important; }
|
| 1473 |
-
.elegant-video-button-icon { width: 18px; height: 18px; margin-left: 8px; fill: currentColor; }
|
| 1474 |
-
.guide-actions { display: flex !important; gap: 12px !important; margin-top: 20px; padding-top: 20px; border-top: 1px solid #e2e8f0; width: 100% !important; }
|
| 1475 |
-
.action-button { padding: 12px 15px !important; border: none !important; border-radius: 12px !important; font-size: 0.95rem !important; font-weight: 600 !important; cursor: pointer !important; flex: 1 !important; transition: all 0.3s ease !important; display: flex !important; align-items: center; justify-content: center; font-family: inherit; height: 48px !important; }
|
| 1476 |
-
.action-button-icon { width: 20px; height: 20px; margin-right: 0; margin-left: 8px; }
|
| 1477 |
-
.back-button { background: white !important; color: var(--guide-text-body) !important; border: 2px solid #e2e8f0 !important; }
|
| 1478 |
-
.back-button:hover { background: #f7fafc !important; border-color: var(--guide-accent) !important; transform: translateY(-2px); box-shadow: var(--shadow-md) !important; }
|
| 1479 |
-
.retry-button { background: var(--primary-gradient-guide) !important; color: white !important; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important; }
|
| 1480 |
-
.retry-button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important; }
|
| 1481 |
-
|
| 1482 |
-
/* Force toast transparency for cleaner Custom Modal */
|
| 1483 |
-
.toast-body { direction: rtl !important; text-align: right !important; background: transparent !important; box-shadow: none !important; border: none !important; padding: 0 !important; max-width: 100% !important; width: auto !important; }
|
| 1484 |
-
.toast-wrap { background: transparent !important; border: none !important; box-shadow: none !important; }
|
| 1485 |
-
|
| 1486 |
-
footer { display: none !important; }
|
| 1487 |
-
.gradio-container footer { display: none !important; }
|
| 1488 |
-
div.footer { display: none !important; }
|
| 1489 |
-
.flagging { display: none !important; }
|
| 1490 |
-
.api-logo, .built-with { display: none !important; }
|
| 1491 |
"""
|
| 1492 |
|
| 1493 |
|
|
@@ -1526,62 +2321,16 @@ def apply_example(idx: str):
|
|
| 1526 |
####################################################################################################
|
| 1527 |
### PART 20: Gradio UI Layout & Launch
|
| 1528 |
####################################################################################################
|
| 1529 |
-
js_quota_handler = """
|
| 1530 |
-
<script>
|
| 1531 |
-
document.addEventListener('DOMContentLoaded', () => {
|
| 1532 |
-
window.retryGeneration = function() {
|
| 1533 |
-
const modal = document.getElementById('custom-quota-modal');
|
| 1534 |
-
if (modal) modal.remove();
|
| 1535 |
-
const runBtn = document.querySelector('.button-gradient'); // Use class selector for main button
|
| 1536 |
-
if(runBtn) runBtn.click();
|
| 1537 |
-
};
|
| 1538 |
-
|
| 1539 |
-
window.closeErrorModal = function() {
|
| 1540 |
-
const modal = document.getElementById('custom-quota-modal');
|
| 1541 |
-
if (modal) modal.remove();
|
| 1542 |
-
};
|
| 1543 |
-
|
| 1544 |
-
const showQuotaModal = () => {
|
| 1545 |
-
if (document.getElementById('custom-quota-modal')) return;
|
| 1546 |
-
const modalHtml = `
|
| 1547 |
-
<div id="custom-quota-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px); z-index: 99999; display: flex; align-items: center; justify-content: center; font-family: inherit;">
|
| 1548 |
-
<div class="ip-reset-guide-container">
|
| 1549 |
-
<div class="guide-header"><div><h2>ظرفیت GPU موقتا پر است</h2><p>راه حل سریع و آسان</p></div></div>
|
| 1550 |
-
<div class="guide-content">
|
| 1551 |
-
<div class="info-card"><div class="info-card-header"><span class="info-card-title">راه حل سریع</span></div><p>کافیست اینترنت خود را یکبار قطع و وصل کنید یا از اینترنت دیگری استفاده کنید و دکمه «تلاش مجدد» را بزنید.</p></div>
|
| 1552 |
-
<div class="summary-section"><div class="summary-header"><span class="summary-title">چرا این اتفاق میافتد؟</span></div><div class="summary-text">سرورهای ما برای ارائه خدمات رایگان، منابع را بین کاربران به اشتراک میگذارند. گاهی اوقات ترافیک بالا باعث پر شدن ظرفیت میشود. با تغییر اینترنت، شما به سرور دیگری متصل میشوید.</div></div>
|
| 1553 |
-
</div>
|
| 1554 |
-
<div class="guide-actions">
|
| 1555 |
-
<button class="action-button back-button" onclick="window.closeErrorModal()"><span>بازگشت</span></button>
|
| 1556 |
-
<button class="action-button retry-button" onclick="window.retryGeneration()"><span>تلاش مجدد</span></button>
|
| 1557 |
-
</div>
|
| 1558 |
-
</div>
|
| 1559 |
-
</div>`;
|
| 1560 |
-
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
| 1561 |
-
};
|
| 1562 |
-
|
| 1563 |
-
setInterval(() => {
|
| 1564 |
-
const potentialErrors = document.querySelectorAll('.toast-body, .error, .toast-wrap');
|
| 1565 |
-
potentialErrors.forEach(el => {
|
| 1566 |
-
const text = el.innerText || "";
|
| 1567 |
-
if (text.toLowerCase().includes('quota') || text.toLowerCase().includes('exceeded')) {
|
| 1568 |
-
showQuotaModal();
|
| 1569 |
-
el.style.display = 'none';
|
| 1570 |
-
const parentWrap = el.closest('.toast-wrap');
|
| 1571 |
-
if(parentWrap) parentWrap.style.display = 'none';
|
| 1572 |
-
}
|
| 1573 |
-
});
|
| 1574 |
-
}, 200);
|
| 1575 |
-
});
|
| 1576 |
-
</script>
|
| 1577 |
-
"""
|
| 1578 |
-
|
| 1579 |
def apply_example(idx: str):
|
| 1580 |
idx = int(idx)
|
|
|
|
|
|
|
| 1581 |
img, prompt_txt, cam, res, mode, vid, aud, end_img = examples_list[idx]
|
|
|
|
| 1582 |
img_path = img if img else None
|
| 1583 |
vid_path = vid if vid else None
|
| 1584 |
aud_path = aud if aud else None
|
|
|
|
| 1585 |
input_image_update = img_path
|
| 1586 |
prompt_update = prompt_txt
|
| 1587 |
camera_update = cam
|
|
@@ -1590,96 +2339,390 @@ def apply_example(idx: str):
|
|
| 1590 |
video_update = gr.update(value=vid_path, visible=(mode == "Motion Control"))
|
| 1591 |
audio_update = aud_path
|
| 1592 |
end_image = end_img
|
|
|
|
|
|
|
| 1593 |
output_video_update = gr.update(value=None)
|
| 1594 |
-
|
| 1595 |
-
return (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1596 |
|
| 1597 |
with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
|
| 1598 |
-
gr.HTML(js_quota_handler) # --- تزریق جاوا اسکریپت ---
|
| 1599 |
|
|
|
|
| 1600 |
gr.HTML(
|
| 1601 |
"""
|
| 1602 |
-
<div style="text-align: center; padding: 20px;">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1603 |
"""
|
| 1604 |
)
|
| 1605 |
|
| 1606 |
with gr.Column(elem_id="col-container"):
|
| 1607 |
with gr.Row(elem_id="mode-row"):
|
| 1608 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1609 |
with gr.Row():
|
| 1610 |
with gr.Column(elem_id="step-column"):
|
|
|
|
| 1611 |
with gr.Row():
|
| 1612 |
-
|
| 1613 |
-
|
| 1614 |
-
|
| 1615 |
-
|
| 1616 |
-
|
| 1617 |
-
|
| 1618 |
-
|
| 1619 |
-
|
| 1620 |
-
|
| 1621 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1622 |
with gr.Accordion("تنظیمات پیشرفته", open=False, visible=False):
|
| 1623 |
-
seed = gr.Slider(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1624 |
randomize_seed = gr.Checkbox(label="استفاده از سید تصادفی", value=True)
|
| 1625 |
|
|
|
|
| 1626 |
with gr.Column(elem_id="step-column"):
|
| 1627 |
-
output_video = gr.Video(label="ویدیوی ساخته شده", autoplay=True, height=
|
| 1628 |
-
|
| 1629 |
with gr.Row(elem_id="controls-row"):
|
| 1630 |
-
|
| 1631 |
-
|
| 1632 |
-
|
| 1633 |
-
|
| 1634 |
-
|
| 1635 |
-
|
| 1636 |
-
|
| 1637 |
-
|
| 1638 |
-
|
| 1639 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1640 |
generate_btn = gr.Button("🤩 ساخت ویدیو", variant="primary", elem_classes="button-gradient")
|
| 1641 |
|
| 1642 |
-
camera_ui.change(fn=lambda x: x, inputs=camera_ui, outputs=camera_lora, api_visibility="private")
|
| 1643 |
-
radioanimated_mode.change(fn=on_mode_change, inputs=radioanimated_mode, outputs=[input_video, end_frame], api_visibility="private")
|
| 1644 |
-
duration_ui.change(fn=apply_duration, inputs=duration_ui, outputs=[duration], api_visibility="private")
|
| 1645 |
-
resolution_ui.change(fn=apply_resolution, inputs=resolution_ui, outputs=[width, height], api_visibility="private")
|
| 1646 |
-
prompt_ui.change(fn=lambda x: x, inputs=prompt_ui, outputs=prompt, api_visibility="private")
|
| 1647 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1648 |
generate_btn.click(
|
| 1649 |
fn=generate_video,
|
| 1650 |
-
inputs=[
|
| 1651 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1652 |
)
|
| 1653 |
|
|
|
|
| 1654 |
examples_list = [
|
| 1655 |
-
[
|
| 1656 |
-
|
| 1657 |
-
|
| 1658 |
-
|
| 1659 |
-
|
| 1660 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1661 |
]
|
| 1662 |
|
| 1663 |
examples_obj = create_examples(
|
| 1664 |
examples=examples_list,
|
| 1665 |
fn=generate_video_example,
|
| 1666 |
inputs=[first_frame, prompt_ui, camera_ui, resolution_ui, radioanimated_mode, input_video, audio_input, end_frame],
|
| 1667 |
-
outputs=[output_video
|
| 1668 |
-
label="نمونهها",
|
|
|
|
|
|
|
| 1669 |
)
|
| 1670 |
|
| 1671 |
-
preset_gallery = PresetGallery(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1672 |
|
| 1673 |
def on_audio_ui_change(v):
|
| 1674 |
-
|
|
|
|
|
|
|
|
|
|
| 1675 |
return gr.update()
|
| 1676 |
|
| 1677 |
-
audio_ui.change(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1678 |
|
| 1679 |
preset_gallery.change(
|
| 1680 |
fn=apply_example,
|
| 1681 |
inputs=preset_gallery,
|
| 1682 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1683 |
api_visibility="private",
|
| 1684 |
)
|
| 1685 |
|
|
|
|
| 1235 |
### PART 17: Wrapper Functions (Resolution, Duration, Examples)
|
| 1236 |
####################################################################################################
|
| 1237 |
def generate_video_example(first_frame, prompt, camera_lora, resolution, radioanimated_mode, input_video, input_audio, end_frame, progress=gr.Progress(track_tqdm=True)):
|
| 1238 |
+
|
| 1239 |
w, h = apply_resolution(resolution)
|
| 1240 |
+
|
| 1241 |
+
# We pass input_video (from example) to generate_video, though the logic inside generate_video
|
| 1242 |
+
# might ignore it since we removed the Motion Control block.
|
| 1243 |
+
# We keep the argument in this function signature to align with the examples_list columns.
|
| 1244 |
+
|
| 1245 |
+
# Default duration for examples
|
| 1246 |
duration_s = 5
|
| 1247 |
|
| 1248 |
with timer(f'generating with LoRA:{camera_lora} in {w}x{h}'):
|
| 1249 |
+
output_video = generate_video(
|
| 1250 |
first_frame,
|
| 1251 |
end_frame,
|
| 1252 |
prompt,
|
|
|
|
| 1262 |
input_audio,
|
| 1263 |
progress
|
| 1264 |
)
|
| 1265 |
+
return output_video
|
| 1266 |
|
| 1267 |
def get_duration(
|
| 1268 |
first_frame,
|
|
|
|
| 1281 |
progress
|
| 1282 |
):
|
| 1283 |
extra_time = 0
|
| 1284 |
+
|
| 1285 |
+
if audio_path is not None:
|
| 1286 |
+
extra_time += 10
|
| 1287 |
+
|
| 1288 |
+
if input_video is not None:
|
| 1289 |
+
extra_time += 60
|
| 1290 |
+
|
| 1291 |
+
if duration <= 3:
|
| 1292 |
+
return 60 + extra_time
|
| 1293 |
+
elif duration <= 5:
|
| 1294 |
+
return 80 + extra_time
|
| 1295 |
+
elif duration <= 10:
|
| 1296 |
+
return 120 + extra_time
|
| 1297 |
+
else:
|
| 1298 |
+
return 180 + extra_time
|
| 1299 |
|
| 1300 |
|
| 1301 |
####################################################################################################
|
|
|
|
| 1308 |
prompt: str,
|
| 1309 |
duration: float,
|
| 1310 |
input_video = None,
|
| 1311 |
+
generation_mode = "تبدیل تصویر به ویدیو", # Default changed to Persian
|
| 1312 |
enhance_prompt: bool = True,
|
| 1313 |
seed: int = 42,
|
| 1314 |
randomize_seed: bool = True,
|
|
|
|
| 1318 |
audio_path = None,
|
| 1319 |
progress=gr.Progress(track_tqdm=True),
|
| 1320 |
):
|
| 1321 |
+
"""
|
| 1322 |
+
Generate a short cinematic video from a text prompt and optional input image using the LTX-2 distilled pipeline.
|
| 1323 |
+
"""
|
|
|
|
|
|
|
| 1324 |
|
| 1325 |
+
# Removed the 15s warning check since 15s option is removed from UI
|
| 1326 |
+
|
| 1327 |
+
if audio_path is None:
|
| 1328 |
+
print(f'generating with duration:{duration} and LoRA:{camera_lora} in {width}x{height}')
|
| 1329 |
+
else:
|
| 1330 |
+
print(f'generating with duration:{duration} and audio in {width}x{height}')
|
| 1331 |
|
| 1332 |
+
# Randomize seed if checkbox is enabled
|
| 1333 |
+
current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
|
| 1334 |
+
|
| 1335 |
+
# Calculate num_frames from duration (using fixed 24 fps)
|
| 1336 |
+
frame_rate = 24.0
|
| 1337 |
+
num_frames = int(duration * frame_rate) + 1 # +1 to ensure we meet the duration
|
| 1338 |
+
video_seconds = int(duration)
|
| 1339 |
+
|
| 1340 |
+
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
|
| 1341 |
+
output_path = tmpfile.name
|
| 1342 |
+
|
| 1343 |
+
|
| 1344 |
+
images = []
|
| 1345 |
+
videos = []
|
| 1346 |
+
|
| 1347 |
+
# Removed Motion Control block
|
| 1348 |
+
|
| 1349 |
+
if first_frame is not None:
|
| 1350 |
images = []
|
| 1351 |
+
images.append((first_frame, 0, 1.0))
|
| 1352 |
+
|
| 1353 |
+
# Updated logic for Persian string
|
| 1354 |
+
if generation_mode == "تکمیل فریمهای میانی":
|
| 1355 |
+
if end_frame is not None:
|
| 1356 |
+
end_idx = max(0, num_frames - 1)
|
| 1357 |
+
images.append((end_frame, end_idx, 0.5))
|
| 1358 |
+
|
| 1359 |
+
embeddings, final_prompt, status = encode_prompt(
|
| 1360 |
+
prompt=prompt,
|
| 1361 |
+
enhance_prompt=enhance_prompt,
|
| 1362 |
+
input_image=first_frame,
|
| 1363 |
+
seed=current_seed,
|
| 1364 |
+
negative_prompt="",
|
| 1365 |
+
)
|
| 1366 |
+
|
| 1367 |
+
video_context = embeddings["video_context"].to("cuda", non_blocking=True)
|
| 1368 |
+
audio_context = embeddings["audio_context"].to("cuda", non_blocking=True)
|
| 1369 |
+
print("✓ Embeddings loaded successfully")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1370 |
|
|
|
|
| 1371 |
|
| 1372 |
+
# free prompt enhancer / encoder temps ASAP
|
| 1373 |
+
del embeddings, final_prompt, status
|
| 1374 |
+
torch.cuda.empty_cache()
|
| 1375 |
+
|
| 1376 |
+
# ✅ if user provided audio, use a neutral audio_context
|
| 1377 |
+
n_audio_context = None
|
| 1378 |
+
|
| 1379 |
+
if audio_path is not None:
|
| 1380 |
+
with torch.inference_mode():
|
| 1381 |
+
_, n_audio_context = encode_text_simple(text_encoder, "") # returns tensors on GPU already
|
| 1382 |
+
del audio_context
|
| 1383 |
+
audio_context = n_audio_context
|
| 1384 |
+
|
| 1385 |
+
if len(videos) == 0:
|
| 1386 |
+
camera_lora = "Static"
|
| 1387 |
+
|
| 1388 |
+
torch.cuda.empty_cache()
|
| 1389 |
+
|
| 1390 |
+
# Map dropdown name -> adapter index
|
| 1391 |
+
name_to_idx = {name: idx for name, idx in RUNTIME_LORA_CHOICES}
|
| 1392 |
+
selected_idx = name_to_idx.get(camera_lora, -1)
|
| 1393 |
+
|
| 1394 |
+
enable_only_lora(pipeline._transformer, selected_idx)
|
| 1395 |
+
torch.cuda.empty_cache()
|
| 1396 |
+
|
| 1397 |
+
# True video duration in seconds based on your rounding
|
| 1398 |
+
video_seconds = (num_frames - 1) / frame_rate
|
| 1399 |
+
|
| 1400 |
+
if audio_path is not None:
|
| 1401 |
+
input_waveform, input_waveform_sample_rate = match_audio_to_duration(
|
| 1402 |
+
audio_path=audio_path,
|
| 1403 |
+
target_seconds=video_seconds,
|
| 1404 |
+
target_sr=48000, # pick what your model expects; 48k is common for AV models
|
| 1405 |
+
to_mono=True, # set False if your model wants stereo
|
| 1406 |
+
pad_mode="silence", # or "repeat" if you prefer looping over silence
|
| 1407 |
+
device="cuda",
|
| 1408 |
+
)
|
| 1409 |
+
else:
|
| 1410 |
+
input_waveform = None
|
| 1411 |
+
input_waveform_sample_rate = None
|
| 1412 |
+
|
| 1413 |
+
with timer(f'generating with LoRA:{camera_lora} in {width}x{height}'):
|
| 1414 |
+
with torch.inference_mode():
|
| 1415 |
+
pipeline(
|
| 1416 |
+
prompt=prompt,
|
| 1417 |
+
output_path=str(output_path),
|
| 1418 |
+
seed=current_seed,
|
| 1419 |
+
height=height,
|
| 1420 |
+
width=width,
|
| 1421 |
+
num_frames=num_frames,
|
| 1422 |
+
frame_rate=frame_rate,
|
| 1423 |
+
images=images,
|
| 1424 |
+
video_conditioning=videos,
|
| 1425 |
+
tiling_config=TilingConfig.default(),
|
| 1426 |
+
video_context=video_context,
|
| 1427 |
+
audio_context=audio_context,
|
| 1428 |
+
input_waveform=input_waveform,
|
| 1429 |
+
input_waveform_sample_rate=input_waveform_sample_rate,
|
| 1430 |
+
)
|
| 1431 |
+
del video_context, audio_context
|
| 1432 |
+
torch.cuda.empty_cache()
|
| 1433 |
+
print("successful generation")
|
| 1434 |
+
|
| 1435 |
+
return str(output_path)
|
| 1436 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1437 |
|
| 1438 |
def apply_resolution(resolution: str):
|
| 1439 |
+
|
| 1440 |
+
if resolution == "16:9":
|
| 1441 |
+
w, h = 768, 512
|
| 1442 |
+
elif resolution == "1:1":
|
| 1443 |
+
w, h = 512, 512
|
| 1444 |
+
elif resolution == "9:16":
|
| 1445 |
+
w, h = 512, 768
|
| 1446 |
+
|
| 1447 |
return int(w), int(h)
|
| 1448 |
|
| 1449 |
def apply_duration(duration: str):
|
| 1450 |
+
duration_s = int(duration[:-1])
|
| 1451 |
+
return duration_s
|
| 1452 |
|
| 1453 |
def on_mode_change(selected: str):
|
| 1454 |
+
is_motion = False # Removed Motion Control
|
| 1455 |
+
# Updated logic for Persian string
|
| 1456 |
is_interpolate = (selected == "تکمیل فریمهای میانی")
|
| 1457 |
+
|
| 1458 |
+
return (gr.update(visible=is_motion), gr.update(visible=is_interpolate))
|
| 1459 |
|
| 1460 |
|
| 1461 |
####################################################################################################
|
| 1462 |
### PART 19: CSS Styles
|
| 1463 |
####################################################################################################
|
| 1464 |
css = """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1465 |
|
| 1466 |
+
/* Make the row behave nicely */
|
| 1467 |
+
#controls-row {
|
| 1468 |
+
display: none !important;
|
| 1469 |
+
align-items: center;
|
| 1470 |
+
gap: 12px;
|
| 1471 |
+
flex-wrap: nowrap; /* or wrap if you prefer on small screens */
|
| 1472 |
+
}
|
| 1473 |
+
|
| 1474 |
+
/* Stop these components from stretching */
|
| 1475 |
+
#controls-row > * {
|
| 1476 |
+
flex: 0 0 auto !important;
|
| 1477 |
+
width: auto !important;
|
| 1478 |
+
min-width: 0 !important;
|
| 1479 |
+
}
|
| 1480 |
+
|
| 1481 |
+
|
| 1482 |
+
#col-container {
|
| 1483 |
+
margin: 0 auto;
|
| 1484 |
+
max-width: 1600px;
|
| 1485 |
+
}
|
| 1486 |
+
#modal-container {
|
| 1487 |
+
width: 100vw; /* Take full viewport width */
|
| 1488 |
+
height: 100vh; /* Take full viewport height (optional) */
|
| 1489 |
+
display: flex;
|
| 1490 |
+
justify-content: center; /* Center content horizontally */
|
| 1491 |
+
align-items: center; /* Center content vertically if desired */
|
| 1492 |
+
}
|
| 1493 |
+
#modal-content {
|
| 1494 |
+
width: 100%;
|
| 1495 |
+
max-width: 700px; /* Limit content width */
|
| 1496 |
+
margin: 0 auto;
|
| 1497 |
+
border-radius: 8px;
|
| 1498 |
+
padding: 1.5rem;
|
| 1499 |
+
}
|
| 1500 |
+
#step-column {
|
| 1501 |
+
padding: 10px;
|
| 1502 |
+
border-radius: 8px;
|
| 1503 |
+
box-shadow: var(--card-shadow);
|
| 1504 |
+
margin: 10px;
|
| 1505 |
+
}
|
| 1506 |
+
#col-showcase {
|
| 1507 |
+
margin: 0 auto;
|
| 1508 |
+
max-width: 1100px;
|
| 1509 |
+
}
|
| 1510 |
+
.button-gradient {
|
| 1511 |
+
background: linear-gradient(45deg, rgb(255, 65, 108), rgb(255, 75, 43), rgb(255, 155, 0), rgb(255, 65, 108)) 0% 0% / 400% 400%;
|
| 1512 |
+
border: none;
|
| 1513 |
+
padding: 14px 28px;
|
| 1514 |
+
font-size: 16px;
|
| 1515 |
+
font-weight: bold;
|
| 1516 |
+
color: white;
|
| 1517 |
+
border-radius: 10px;
|
| 1518 |
+
cursor: pointer;
|
| 1519 |
+
transition: 0.3s ease-in-out;
|
| 1520 |
+
animation: 2s linear 0s infinite normal none running gradientAnimation;
|
| 1521 |
+
box-shadow: rgba(255, 65, 108, 0.6) 0px 4px 10px;
|
| 1522 |
+
}
|
| 1523 |
+
.toggle-container {
|
| 1524 |
+
display: inline-flex;
|
| 1525 |
+
background-color: #ffd6ff; /* light pink background */
|
| 1526 |
+
border-radius: 9999px;
|
| 1527 |
+
padding: 4px;
|
| 1528 |
+
position: relative;
|
| 1529 |
+
width: fit-content;
|
| 1530 |
+
font-family: sans-serif;
|
| 1531 |
+
}
|
| 1532 |
+
.toggle-container input[type="radio"] {
|
| 1533 |
+
display: none;
|
| 1534 |
+
}
|
| 1535 |
+
.toggle-container label {
|
| 1536 |
+
position: relative;
|
| 1537 |
+
z-index: 2;
|
| 1538 |
+
flex: 1;
|
| 1539 |
+
text-align: center;
|
| 1540 |
+
font-weight: 700;
|
| 1541 |
+
color: #4b2ab5; /* dark purple text for unselected */
|
| 1542 |
+
padding: 6px 22px;
|
| 1543 |
+
border-radius: 9999px;
|
| 1544 |
+
cursor: pointer;
|
| 1545 |
+
transition: color 0.25s ease;
|
| 1546 |
+
}
|
| 1547 |
+
/* Moving highlight */
|
| 1548 |
+
.toggle-highlight {
|
| 1549 |
+
position: absolute;
|
| 1550 |
+
top: 4px;
|
| 1551 |
+
left: 4px;
|
| 1552 |
+
width: calc(50% - 4px);
|
| 1553 |
+
height: calc(100% - 8px);
|
| 1554 |
+
background-color: #4b2ab5; /* dark purple background */
|
| 1555 |
+
border-radius: 9999px;
|
| 1556 |
+
transition: transform 0.25s ease;
|
| 1557 |
+
z-index: 1;
|
| 1558 |
+
}
|
| 1559 |
+
/* When "True" is checked */
|
| 1560 |
+
#true:checked ~ label[for="true"] {
|
| 1561 |
+
color: #ffd6ff; /* light pink text */
|
| 1562 |
+
}
|
| 1563 |
+
/* When "False" is checked */
|
| 1564 |
+
#false:checked ~ label[for="false"] {
|
| 1565 |
+
color: #ffd6ff; /* light pink text */
|
| 1566 |
+
}
|
| 1567 |
+
/* Move highlight to right side when False is checked */
|
| 1568 |
+
#false:checked ~ .toggle-highlight {
|
| 1569 |
+
transform: translateX(100%);
|
| 1570 |
+
}
|
| 1571 |
+
|
| 1572 |
+
/* Center items inside that row */
|
| 1573 |
+
#mode-row{
|
| 1574 |
+
justify-content: center !important;
|
| 1575 |
+
align-items: center !important;
|
| 1576 |
+
}
|
| 1577 |
+
|
| 1578 |
+
/* Center the mode row contents */
|
| 1579 |
+
#mode-row {
|
| 1580 |
+
display: flex !important;
|
| 1581 |
+
justify-content: center !important;
|
| 1582 |
+
align-items: center !important;
|
| 1583 |
+
width: 100% !important;
|
| 1584 |
+
}
|
| 1585 |
+
|
| 1586 |
+
/* Stop Gradio from making children stretch */
|
| 1587 |
+
#mode-row > * {
|
| 1588 |
+
flex: 0 0 auto !important;
|
| 1589 |
+
width: auto !important;
|
| 1590 |
+
min-width: 0 !important;
|
| 1591 |
+
}
|
| 1592 |
+
|
| 1593 |
+
/* Specifically ensure the HTML component wrapper doesn't take full width */
|
| 1594 |
+
#mode-row .gr-html,
|
| 1595 |
+
#mode-row .gradio-html,
|
| 1596 |
+
#mode-row .prose,
|
| 1597 |
+
#mode-row .block {
|
| 1598 |
+
width: auto !important;
|
| 1599 |
+
flex: 0 0 auto !important;
|
| 1600 |
+
display: inline-block !important;
|
| 1601 |
+
}
|
| 1602 |
+
|
| 1603 |
+
/* Center the pill itself */
|
| 1604 |
+
#radioanimated_mode {
|
| 1605 |
+
display: inline-flex !important;
|
| 1606 |
+
justify-content: center !important;
|
| 1607 |
+
width: auto !important;
|
| 1608 |
+
}
|
| 1609 |
+
|
| 1610 |
+
"""
|
| 1611 |
+
|
| 1612 |
+
css += """
|
| 1613 |
+
.cd-trigger-icon{
|
| 1614 |
+
color: rgba(255,255,255,0.9);
|
| 1615 |
+
display: inline-flex;
|
| 1616 |
+
align-items: center;
|
| 1617 |
+
justify-content: center;
|
| 1618 |
+
width: 18px;
|
| 1619 |
+
height: 18px;
|
| 1620 |
+
}
|
| 1621 |
+
.cd-trigger-icon svg {
|
| 1622 |
+
width: 18px;
|
| 1623 |
+
height: 18px;
|
| 1624 |
+
display: block;
|
| 1625 |
+
}
|
| 1626 |
+
"""
|
| 1627 |
+
|
| 1628 |
|
| 1629 |
+
css += """
|
| 1630 |
+
/* ---- radioanimated ---- */
|
| 1631 |
+
.ra-wrap{
|
| 1632 |
+
width: fit-content;
|
| 1633 |
+
}
|
| 1634 |
+
.ra-inner{
|
| 1635 |
+
position: relative;
|
| 1636 |
+
display: inline-flex;
|
| 1637 |
+
align-items: center;
|
| 1638 |
+
gap: 0;
|
| 1639 |
+
padding: 6px;
|
| 1640 |
+
background: #0b0b0b;
|
| 1641 |
+
border-radius: 9999px;
|
| 1642 |
+
overflow: hidden;
|
| 1643 |
+
user-select: none;
|
| 1644 |
+
}
|
| 1645 |
+
.ra-input{
|
| 1646 |
+
display: none;
|
| 1647 |
+
}
|
| 1648 |
+
.ra-label{
|
| 1649 |
+
position: relative;
|
| 1650 |
+
z-index: 2;
|
| 1651 |
+
padding: 10px 18px;
|
| 1652 |
+
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
|
| 1653 |
+
font-size: 14px;
|
| 1654 |
+
font-weight: 600;
|
| 1655 |
+
color: rgba(255,255,255,0.7);
|
| 1656 |
+
cursor: pointer;
|
| 1657 |
+
transition: color 180ms ease;
|
| 1658 |
+
white-space: nowrap;
|
| 1659 |
+
}
|
| 1660 |
+
.ra-highlight{
|
| 1661 |
+
position: absolute;
|
| 1662 |
+
z-index: 1;
|
| 1663 |
+
top: 6px;
|
| 1664 |
+
left: 6px;
|
| 1665 |
+
height: calc(100% - 12px);
|
| 1666 |
+
border-radius: 9999px;
|
| 1667 |
+
background: #8bff97; /* green knob */
|
| 1668 |
+
transition: transform 200ms ease, width 200ms ease;
|
| 1669 |
+
}
|
| 1670 |
+
/* selected label becomes darker like your screenshot */
|
| 1671 |
+
.ra-input:checked + .ra-label{
|
| 1672 |
+
color: rgba(0,0,0,0.75);
|
| 1673 |
+
}
|
| 1674 |
+
"""
|
| 1675 |
+
|
| 1676 |
+
css += """
|
| 1677 |
+
.cd-icn svg{
|
| 1678 |
+
width: 18px;
|
| 1679 |
+
height: 18px;
|
| 1680 |
+
display: block;
|
| 1681 |
}
|
| 1682 |
+
.cd-icn svg *{
|
| 1683 |
+
stroke: rgba(255,255,255,0.9);
|
|
|
|
| 1684 |
}
|
| 1685 |
+
"""
|
| 1686 |
+
|
| 1687 |
+
|
| 1688 |
+
css += """
|
| 1689 |
+
/* --- prompt box --- */
|
| 1690 |
+
.ds-prompt{
|
| 1691 |
+
width: 100%;
|
| 1692 |
+
max-width: 720px;
|
| 1693 |
+
margin-top: 3px;
|
| 1694 |
+
}
|
| 1695 |
+
|
| 1696 |
+
.ds-textarea{
|
| 1697 |
+
width: 100%;
|
| 1698 |
+
box-sizing: border-box;
|
| 1699 |
+
background: #2b2b2b;
|
| 1700 |
+
color: rgba(255,255,255,0.9);
|
| 1701 |
+
border: 1px solid rgba(255,255,255,0.12);
|
| 1702 |
+
border-radius: 14px;
|
| 1703 |
+
padding: 14px 16px;
|
| 1704 |
+
outline: none;
|
| 1705 |
+
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
|
| 1706 |
+
font-size: 15px;
|
| 1707 |
+
line-height: 1.35;
|
| 1708 |
+
resize: none;
|
| 1709 |
+
min-height: 210px;
|
| 1710 |
+
max-height: 210px;
|
| 1711 |
+
overflow-y: auto;
|
| 1712 |
+
|
| 1713 |
+
/* IMPORTANT: space for the footer controls */
|
| 1714 |
+
padding-bottom: 72px;
|
| 1715 |
+
}
|
| 1716 |
+
|
| 1717 |
+
|
| 1718 |
+
.ds-card{
|
| 1719 |
+
width: 100%;
|
| 1720 |
+
max-width: 720px;
|
| 1721 |
+
margin: 0 auto;
|
| 1722 |
+
}
|
| 1723 |
+
.ds-top{
|
| 1724 |
+
position: relative;
|
| 1725 |
+
}
|
| 1726 |
+
|
| 1727 |
+
/* Make room for footer inside textarea */
|
| 1728 |
+
.ds-textarea{
|
| 1729 |
+
padding-bottom: 72px;
|
| 1730 |
+
}
|
| 1731 |
+
|
| 1732 |
+
/* Footer positioning */
|
| 1733 |
+
.ds-footer{
|
| 1734 |
+
position: absolute;
|
| 1735 |
+
right: 12px;
|
| 1736 |
+
bottom: 10px;
|
| 1737 |
+
display: flex;
|
| 1738 |
+
gap: 8px;
|
| 1739 |
+
align-items: center;
|
| 1740 |
+
justify-content: flex-end;
|
| 1741 |
+
z-index: 3;
|
| 1742 |
+
}
|
| 1743 |
+
|
| 1744 |
+
/* Smaller pill buttons inside footer */
|
| 1745 |
+
.ds-footer .cd-trigger{
|
| 1746 |
+
min-height: 32px;
|
| 1747 |
+
padding: 6px 10px;
|
| 1748 |
+
font-size: 12px;
|
| 1749 |
+
gap: 6px;
|
| 1750 |
+
border-radius: 9999px;
|
| 1751 |
+
}
|
| 1752 |
+
.ds-footer .cd-trigger-icon,
|
| 1753 |
+
.ds-footer .cd-icn{
|
| 1754 |
+
width: 14px;
|
| 1755 |
+
height: 14px;
|
| 1756 |
+
}
|
| 1757 |
+
.ds-footer .cd-trigger-icon svg,
|
| 1758 |
+
.ds-footer .cd-icn svg{
|
| 1759 |
+
width: 14px;
|
| 1760 |
+
height: 14px;
|
| 1761 |
+
}
|
| 1762 |
+
.ds-footer .cd-caret{
|
| 1763 |
+
font-size: 11px;
|
| 1764 |
+
}
|
| 1765 |
+
|
| 1766 |
+
/* Bottom safe area bar (optional but looks nicer) */
|
| 1767 |
+
.ds-top::after{
|
| 1768 |
+
content: "";
|
| 1769 |
+
position: absolute;
|
| 1770 |
+
left: 1px;
|
| 1771 |
+
right: 1px;
|
| 1772 |
+
bottom: 1px;
|
| 1773 |
+
height: 56px;
|
| 1774 |
+
background: #2b2b2b;
|
| 1775 |
+
border-bottom-left-radius: 13px;
|
| 1776 |
+
border-bottom-right-radius: 13px;
|
| 1777 |
+
pointer-events: none;
|
| 1778 |
+
z-index: 2;
|
| 1779 |
+
}
|
| 1780 |
+
|
| 1781 |
+
"""
|
| 1782 |
+
|
| 1783 |
+
css += """
|
| 1784 |
+
/* ---- camera dropdown ---- */
|
| 1785 |
+
|
| 1786 |
+
/* 1) Fix overlap: make the Gradio HTML block shrink-to-fit when it contains a CameraDropdown.
|
| 1787 |
+
Gradio uses .gr-html for HTML components in most versions; older themes sometimes use .gradio-html.
|
| 1788 |
+
This keeps your big header HTML unaffected because it doesn't contain .cd-wrap.
|
| 1789 |
+
*/
|
| 1790 |
+
|
| 1791 |
+
/* 2) Actual dropdown layout */
|
| 1792 |
+
.cd-wrap{
|
| 1793 |
+
position: relative;
|
| 1794 |
+
display: inline-block;
|
| 1795 |
+
}
|
| 1796 |
+
|
| 1797 |
+
/* 3) Match RadioAnimated pill size/feel */
|
| 1798 |
+
.cd-trigger{
|
| 1799 |
+
margin-top: 2px;
|
| 1800 |
+
display: inline-flex;
|
| 1801 |
+
align-items: center;
|
| 1802 |
+
justify-content: center;
|
| 1803 |
+
gap: 10px;
|
| 1804 |
+
|
| 1805 |
+
border: none;
|
| 1806 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1807 |
box-sizing: border-box;
|
| 1808 |
+
padding: 10px 18px;
|
| 1809 |
+
min-height: 52px;
|
| 1810 |
+
line-height: 1.2;
|
| 1811 |
+
|
| 1812 |
+
border-radius: 9999px;
|
| 1813 |
+
background: #0b0b0b;
|
| 1814 |
+
|
| 1815 |
+
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
|
| 1816 |
+
font-size: 14px;
|
| 1817 |
+
|
| 1818 |
+
/* ✅ match .ra-label exactly */
|
| 1819 |
+
color: rgba(255,255,255,0.7) !important;
|
| 1820 |
+
font-weight: 600 !important;
|
| 1821 |
+
|
| 1822 |
+
cursor: pointer;
|
| 1823 |
+
user-select: none;
|
| 1824 |
+
white-space: nowrap;
|
| 1825 |
+
}
|
| 1826 |
+
|
| 1827 |
+
/* Ensure inner spans match too */
|
| 1828 |
+
.cd-trigger .cd-trigger-text,
|
| 1829 |
+
.cd-trigger .cd-caret{
|
| 1830 |
+
color: rgba(255,255,255,0.7) !important;
|
| 1831 |
+
}
|
| 1832 |
+
|
| 1833 |
+
/* keep caret styling */
|
| 1834 |
+
.cd-caret{
|
| 1835 |
+
opacity: 0.8;
|
| 1836 |
+
font-weight: 900;
|
| 1837 |
+
}
|
| 1838 |
+
|
| 1839 |
+
/* 4) Ensure menu overlays neighbors and isn't clipped */
|
| 1840 |
+
/* Move dropdown a tiny bit up (closer to the trigger) */
|
| 1841 |
+
.cd-menu{
|
| 1842 |
+
position: absolute;
|
| 1843 |
+
top: calc(100% + 4px); /* was +10px */
|
| 1844 |
+
left: 0;
|
| 1845 |
+
|
| 1846 |
+
min-width: 240px;
|
| 1847 |
+
background: #2b2b2b;
|
| 1848 |
+
border: 1px solid rgba(255,255,255,0.14);
|
| 1849 |
+
border-radius: 14px;
|
| 1850 |
+
box-shadow: 0 18px 40px rgba(0,0,0,0.35);
|
| 1851 |
+
padding: 10px;
|
| 1852 |
+
|
| 1853 |
+
opacity: 0;
|
| 1854 |
+
transform: translateY(-6px);
|
| 1855 |
+
pointer-events: none;
|
| 1856 |
+
transition: opacity 160ms ease, transform 160ms ease;
|
| 1857 |
+
|
| 1858 |
+
z-index: 9999;
|
| 1859 |
+
}
|
| 1860 |
+
|
| 1861 |
+
.cd-title{
|
| 1862 |
+
font-size: 12px;
|
| 1863 |
+
font-weight: 600;
|
| 1864 |
+
text-transform: uppercase;
|
| 1865 |
+
letter-spacing: 0.04em;
|
| 1866 |
+
|
| 1867 |
+
color: rgba(255,255,255,0.45); /* 👈 muted grey */
|
| 1868 |
+
margin-bottom: 6px;
|
| 1869 |
+
padding: 0 6px;
|
| 1870 |
+
pointer-events: none; /* title is non-interactive */
|
| 1871 |
+
}
|
| 1872 |
+
|
| 1873 |
+
|
| 1874 |
+
.cd-menu.open{
|
| 1875 |
+
opacity: 1;
|
| 1876 |
+
transform: translateY(0);
|
| 1877 |
+
pointer-events: auto;
|
| 1878 |
+
}
|
| 1879 |
+
|
| 1880 |
+
.cd-items{
|
| 1881 |
+
display: flex;
|
| 1882 |
+
flex-direction: column;
|
| 1883 |
+
gap: 0px; /* tighter, more like a native menu */
|
| 1884 |
+
}
|
| 1885 |
+
|
| 1886 |
+
/* Items: NO "boxed" buttons by default */
|
| 1887 |
+
.cd-item{
|
| 1888 |
+
width: 100%;
|
| 1889 |
+
text-align: left;
|
| 1890 |
+
border: none;
|
| 1891 |
+
background: transparent; /* ✅ removes box look */
|
| 1892 |
+
color: rgba(255,255,255,0.92);
|
| 1893 |
+
padding: 8px 34px 8px 12px; /* right padding leaves room for tick */
|
| 1894 |
+
border-radius: 10px; /* only matters on hover */
|
| 1895 |
+
cursor: pointer;
|
| 1896 |
+
|
| 1897 |
+
font-size: 14px;
|
| 1898 |
+
font-weight: 700;
|
| 1899 |
+
|
| 1900 |
+
position: relative;
|
| 1901 |
+
transition: background 120ms ease;
|
| 1902 |
+
}
|
| 1903 |
+
|
| 1904 |
+
/* “Box effect” only on hover (not always) */
|
| 1905 |
+
.cd-item:hover{
|
| 1906 |
+
background: rgba(255,255,255,0.08);
|
| 1907 |
+
}
|
| 1908 |
+
|
| 1909 |
+
/* Tick on the right ONLY on hover */
|
| 1910 |
+
.cd-item::after{
|
| 1911 |
+
content: "✓";
|
| 1912 |
+
position: absolute;
|
| 1913 |
+
right: 12px;
|
| 1914 |
+
top: 50%;
|
| 1915 |
+
transform: translateY(-50%);
|
| 1916 |
+
opacity: 0; /* hidden by default */
|
| 1917 |
+
transition: opacity 120ms ease;
|
| 1918 |
+
color: rgba(255,255,255,0.9);
|
| 1919 |
+
font-weight: 900;
|
| 1920 |
+
}
|
| 1921 |
+
|
| 1922 |
+
/* show tick ONLY for selected item */
|
| 1923 |
+
.cd-item[data-selected="true"]::after{
|
| 1924 |
+
opacity: 1;
|
| 1925 |
+
}
|
| 1926 |
+
|
| 1927 |
+
/* keep hover box effect, but no tick change */
|
| 1928 |
+
.cd-item:hover{
|
| 1929 |
+
background: rgba(255,255,255,0.08);
|
| 1930 |
+
}
|
| 1931 |
+
|
| 1932 |
+
|
| 1933 |
+
/* Kill any old “selected” styling just in case */
|
| 1934 |
+
.cd-item.selected{
|
| 1935 |
+
background: transparent !important;
|
| 1936 |
+
border: none !important;
|
| 1937 |
+
}
|
| 1938 |
+
|
| 1939 |
+
|
| 1940 |
+
"""
|
| 1941 |
+
|
| 1942 |
+
css += """
|
| 1943 |
+
/* icons in dropdown items */
|
| 1944 |
+
.cd-item{
|
| 1945 |
+
display: flex;
|
| 1946 |
+
align-items: center;
|
| 1947 |
+
gap: 10px;
|
| 1948 |
+
}
|
| 1949 |
+
.cd-icn{
|
| 1950 |
+
display: inline-flex;
|
| 1951 |
+
align-items: center;
|
| 1952 |
+
justify-content: center;
|
| 1953 |
+
width: 18px;
|
| 1954 |
+
height: 18px;
|
| 1955 |
+
flex: 0 0 18px;
|
| 1956 |
+
}
|
| 1957 |
+
.cd-label{
|
| 1958 |
+
flex: 1;
|
| 1959 |
+
}
|
| 1960 |
+
|
| 1961 |
+
/* =========================
|
| 1962 |
+
FIX: prompt border + scrollbar bleed
|
| 1963 |
+
========================= */
|
| 1964 |
+
|
| 1965 |
+
/* Put the border + background on the wrapper, not the textarea */
|
| 1966 |
+
.ds-top{
|
| 1967 |
+
position: relative;
|
| 1968 |
+
background: #2b2b2b;
|
| 1969 |
+
border: 1px solid rgba(255,255,255,0.12);
|
| 1970 |
+
border-radius: 14px;
|
| 1971 |
+
overflow: hidden; /* ensures the footer bar is clipped to rounded corners */
|
| 1972 |
+
}
|
| 1973 |
+
|
| 1974 |
+
/* Make textarea "transparent" so wrapper owns the border/background */
|
| 1975 |
+
.ds-textarea{
|
| 1976 |
+
background: transparent !important;
|
| 1977 |
+
border: none !important;
|
| 1978 |
+
border-radius: 0 !important; /* wrapper handles radius */
|
| 1979 |
+
outline: none;
|
| 1980 |
+
|
| 1981 |
+
/* keep your spacing */
|
| 1982 |
+
padding: 14px 16px;
|
| 1983 |
+
padding-bottom: 72px; /* room for footer */
|
| 1984 |
+
width: 100%;
|
| 1985 |
+
box-sizing: border-box;
|
| 1986 |
+
|
| 1987 |
+
/* keep scroll behavior */
|
| 1988 |
+
overflow-y: auto;
|
| 1989 |
+
|
| 1990 |
+
/* prevent scrollbar bleed by hiding native scrollbar */
|
| 1991 |
+
scrollbar-width: none; /* Firefox */
|
| 1992 |
+
}
|
| 1993 |
+
.ds-textarea::-webkit-scrollbar{ /* Chrome/Safari */
|
| 1994 |
+
width: 0;
|
| 1995 |
+
height: 0;
|
| 1996 |
+
}
|
| 1997 |
+
|
| 1998 |
+
/* Safe-area bar: now it matches perfectly because it's inside the same bordered wrapper */
|
| 1999 |
+
.ds-top::after{
|
| 2000 |
+
content: "";
|
| 2001 |
+
position: absolute;
|
| 2002 |
+
left: 0;
|
| 2003 |
+
right: 0;
|
| 2004 |
+
bottom: 0;
|
| 2005 |
+
height: 56px;
|
| 2006 |
+
background: #2b2b2b;
|
| 2007 |
+
pointer-events: none;
|
| 2008 |
+
z-index: 2;
|
| 2009 |
+
}
|
| 2010 |
+
|
| 2011 |
+
/* Footer above the bar */
|
| 2012 |
+
.ds-footer{
|
| 2013 |
+
position: absolute;
|
| 2014 |
+
right: 12px;
|
| 2015 |
+
bottom: 10px;
|
| 2016 |
+
display: flex;
|
| 2017 |
+
gap: 8px;
|
| 2018 |
+
align-items: center;
|
| 2019 |
+
justify-content: flex-end;
|
| 2020 |
+
z-index: 3;
|
| 2021 |
+
}
|
| 2022 |
+
|
| 2023 |
+
/* Ensure textarea content sits below overlays */
|
| 2024 |
+
.ds-textarea{
|
| 2025 |
+
position: relative;
|
| 2026 |
+
z-index: 1;
|
| 2027 |
+
}
|
| 2028 |
+
|
| 2029 |
+
/* ===== FIX dropdown menu being clipped/behind ===== */
|
| 2030 |
+
|
| 2031 |
+
/* Let the dropdown menu escape the prompt wrapper */
|
| 2032 |
+
.ds-top{
|
| 2033 |
+
overflow: visible !important; /* IMPORTANT: do not clip the menu */
|
| 2034 |
+
}
|
| 2035 |
+
|
| 2036 |
+
/* Keep the rounded "safe area" look without clipping the menu */
|
| 2037 |
+
.ds-top::after{
|
| 2038 |
+
left: 0 !important;
|
| 2039 |
+
right: 0 !important;
|
| 2040 |
+
bottom: 0 !important;
|
| 2041 |
+
border-bottom-left-radius: 14px !important;
|
| 2042 |
+
border-bottom-right-radius: 14px !important;
|
| 2043 |
+
}
|
| 2044 |
+
|
| 2045 |
+
/* Ensure the footer stays above the safe-area bar */
|
| 2046 |
+
.ds-footer{
|
| 2047 |
+
z-index: 20 !important;
|
| 2048 |
+
}
|
| 2049 |
+
|
| 2050 |
+
/* Make sure the opened menu is above EVERYTHING */
|
| 2051 |
+
.ds-footer .cd-menu{
|
| 2052 |
+
z-index: 999999 !important;
|
| 2053 |
+
}
|
| 2054 |
+
|
| 2055 |
+
/* Sometimes Gradio/columns/cards create stacking contexts;
|
| 2056 |
+
force the whole prompt card above nearby panels */
|
| 2057 |
+
.ds-card{
|
| 2058 |
+
position: relative;
|
| 2059 |
+
z-index: 50;
|
| 2060 |
+
}
|
| 2061 |
+
|
| 2062 |
+
/* --- Fix focus highlight shape (make it match rounded container) --- */
|
| 2063 |
+
|
| 2064 |
+
/* Kill any theme focus ring on the textarea itself */
|
| 2065 |
+
.ds-textarea:focus,
|
| 2066 |
+
.ds-textarea:focus-visible{
|
| 2067 |
+
outline: none !important;
|
| 2068 |
+
box-shadow: none !important;
|
| 2069 |
+
}
|
| 2070 |
+
|
| 2071 |
+
/* Optional: if some themes apply it even when not focused */
|
| 2072 |
+
.ds-textarea{
|
| 2073 |
+
outline: none !important;
|
| 2074 |
+
}
|
| 2075 |
+
|
| 2076 |
+
/* Apply the focus ring to the rounded wrapper instead */
|
| 2077 |
+
.ds-top:focus-within{
|
| 2078 |
+
border-color: rgba(255,255,255,0.22) !important;
|
| 2079 |
+
box-shadow: 0 0 0 3px rgba(255,255,255,0.06) !important;
|
| 2080 |
+
border-radius: 14px !important;
|
| 2081 |
+
}
|
| 2082 |
+
|
| 2083 |
+
/* If you see any tiny square corners, ensure the wrapper clips its own shadow properly */
|
| 2084 |
+
.ds-top{
|
| 2085 |
+
border-radius: 14px !important;
|
| 2086 |
+
}
|
| 2087 |
+
|
| 2088 |
+
/* =========================
|
| 2089 |
+
CameraDropdown: force readable menu text in BOTH themes
|
| 2090 |
+
========================= */
|
| 2091 |
+
|
| 2092 |
+
/* Menu surface */
|
| 2093 |
+
.cd-menu{
|
| 2094 |
+
background: #2b2b2b !important;
|
| 2095 |
+
border: 1px solid rgba(255,255,255,0.14) !important;
|
| 2096 |
+
}
|
| 2097 |
+
|
| 2098 |
+
/* Title */
|
| 2099 |
+
.cd-title{
|
| 2100 |
+
color: rgba(255,255,255,0.55) !important;
|
| 2101 |
+
}
|
| 2102 |
+
|
| 2103 |
+
/* Items + all descendants (fixes spans / inherited theme colors) */
|
| 2104 |
+
.cd-item,
|
| 2105 |
+
.cd-item *{
|
| 2106 |
+
color: rgba(255,255,255,0.92) !important;
|
| 2107 |
+
}
|
| 2108 |
+
|
| 2109 |
+
/* Hover state */
|
| 2110 |
+
.cd-item:hover{
|
| 2111 |
+
background: rgba(255,255,255,0.10) !important;
|
| 2112 |
+
}
|
| 2113 |
+
|
| 2114 |
+
/* Checkmark */
|
| 2115 |
+
.cd-item::after{
|
| 2116 |
+
color: rgba(255,255,255,0.92) !important;
|
| 2117 |
+
}
|
| 2118 |
+
|
| 2119 |
+
/* (Optional) make sure the trigger stays readable too */
|
| 2120 |
+
.cd-trigger,
|
| 2121 |
+
.cd-trigger *{
|
| 2122 |
+
color: rgba(255,255,255,0.75) !important;
|
| 2123 |
+
}
|
| 2124 |
+
|
| 2125 |
+
/* ---- preset gallery ---- */
|
| 2126 |
+
.pg-wrap{
|
| 2127 |
+
width: 100%;
|
| 2128 |
+
max-width: 1100px;
|
| 2129 |
+
margin: 18px auto 0 auto;
|
| 2130 |
+
}
|
| 2131 |
+
.pg-title{
|
| 2132 |
+
text-align: center;
|
| 2133 |
+
margin-bottom: 14px;
|
| 2134 |
+
}
|
| 2135 |
+
.pg-h1{
|
| 2136 |
+
font-size: 34px;
|
| 2137 |
+
font-weight: 800;
|
| 2138 |
+
line-height: 1.1;
|
| 2139 |
+
|
| 2140 |
+
/* ✅ theme-aware */
|
| 2141 |
+
color: var(--body-text-color);
|
| 2142 |
+
}
|
| 2143 |
+
.pg-h2{
|
| 2144 |
+
font-size: 14px;
|
| 2145 |
+
font-weight: 600;
|
| 2146 |
+
color: var(--body-text-color-subdued);
|
| 2147 |
+
margin-top: 6px;
|
| 2148 |
+
}
|
| 2149 |
+
|
| 2150 |
+
.pg-grid{
|
| 2151 |
+
display: grid;
|
| 2152 |
+
grid-template-columns: repeat(3, minmax(0, 1fr)); /* 3 per row */
|
| 2153 |
+
gap: 18px;
|
| 2154 |
+
}
|
| 2155 |
+
|
| 2156 |
+
.pg-card{
|
| 2157 |
+
border: none;
|
| 2158 |
+
background: transparent;
|
| 2159 |
+
padding: 0;
|
| 2160 |
+
cursor: pointer;
|
| 2161 |
+
border-radius: 12px;
|
| 2162 |
+
overflow: hidden;
|
| 2163 |
+
position: relative;
|
| 2164 |
+
transform: translateZ(0);
|
| 2165 |
+
}
|
| 2166 |
+
|
| 2167 |
+
.pg-img{
|
| 2168 |
+
width: 100%;
|
| 2169 |
+
height: 220px; /* adjust to match your look */
|
| 2170 |
+
object-fit: cover;
|
| 2171 |
+
display: block;
|
| 2172 |
+
border-radius: 12px;
|
| 2173 |
+
transition: transform 160ms ease, filter 160ms ease, opacity 160ms ease;
|
| 2174 |
+
}
|
| 2175 |
+
|
| 2176 |
+
/* hover: slight zoom on hovered card */
|
| 2177 |
+
.pg-card:hover .pg-img{
|
| 2178 |
+
transform: scale(1.02);
|
| 2179 |
+
}
|
| 2180 |
+
|
| 2181 |
+
/* dim others while hovering */
|
| 2182 |
+
.pg-card[data-dim="true"] .pg-img{
|
| 2183 |
+
opacity: 0.35;
|
| 2184 |
+
filter: saturate(0.9);
|
| 2185 |
+
}
|
| 2186 |
+
|
| 2187 |
+
/* keep hovered/active crisp */
|
| 2188 |
+
.pg-card[data-active="true"] .pg-img{
|
| 2189 |
+
opacity: 1.0;
|
| 2190 |
+
filter: none;
|
| 2191 |
+
}
|
| 2192 |
+
|
| 2193 |
+
|
| 2194 |
+
"""
|
| 2195 |
+
|
| 2196 |
+
|
| 2197 |
+
css += """
|
| 2198 |
+
/* ---- AudioDropUpload ---- */
|
| 2199 |
+
.aud-wrap{
|
| 2200 |
+
width: 100%;
|
| 2201 |
+
max-width: 720px;
|
| 2202 |
+
}
|
| 2203 |
+
.aud-drop{
|
| 2204 |
+
border: 2px dashed var(--body-text-color-subdued);
|
| 2205 |
+
border-radius: 16px;
|
| 2206 |
+
padding: 18px;
|
| 2207 |
+
text-align: center;
|
| 2208 |
+
cursor: pointer;
|
| 2209 |
+
user-select: none;
|
| 2210 |
+
color: var(--body-text-color);
|
| 2211 |
+
background: var(--block-background-fill);
|
| 2212 |
+
}
|
| 2213 |
+
.aud-drop.dragover{
|
| 2214 |
+
border-color: rgba(255,255,255,0.35);
|
| 2215 |
+
background: rgba(255,255,255,0.06);
|
| 2216 |
+
}
|
| 2217 |
+
.aud-hint{
|
| 2218 |
+
color: var(--body-text-color-subdued);
|
| 2219 |
+
font-size: 0.95rem;
|
| 2220 |
+
margin-top: 6px;
|
| 2221 |
+
}
|
| 2222 |
+
/* pill row like your other controls */
|
| 2223 |
+
.aud-row{
|
| 2224 |
+
display: none;
|
| 2225 |
+
align-items: center;
|
| 2226 |
+
gap: 10px;
|
| 2227 |
+
background: #0b0b0b;
|
| 2228 |
+
border-radius: 9999px;
|
| 2229 |
+
padding: 8px 10px;
|
| 2230 |
+
}
|
| 2231 |
+
.aud-player{
|
| 2232 |
+
flex: 1;
|
| 2233 |
+
width: 100%;
|
| 2234 |
+
height: 34px;
|
| 2235 |
+
border-radius: 9999px;
|
| 2236 |
}
|
| 2237 |
+
.aud-remove{
|
| 2238 |
+
appearance: none;
|
| 2239 |
+
border: none;
|
| 2240 |
+
background: transparent;
|
| 2241 |
+
color: rgba(255,255,255);
|
| 2242 |
+
cursor: pointer;
|
| 2243 |
+
width: 36px;
|
| 2244 |
+
height: 36px;
|
| 2245 |
+
border-radius: 9999px;
|
| 2246 |
+
display: inline-flex;
|
| 2247 |
+
align-items: center;
|
| 2248 |
+
justify-content: center;
|
| 2249 |
+
padding: 0;
|
| 2250 |
+
transition: background 120ms ease, color 120ms ease, opacity 120ms ease;
|
| 2251 |
+
opacity: 0.9;
|
| 2252 |
+
flex: 0 0 auto;
|
| 2253 |
+
}
|
| 2254 |
+
.aud-remove:hover{
|
| 2255 |
+
background: rgba(255,255,255,0.08);
|
| 2256 |
+
color: rgb(255,255,255);
|
| 2257 |
+
opacity: 1;
|
| 2258 |
+
}
|
| 2259 |
+
.aud-filelabel{
|
| 2260 |
+
margin: 10px 6px 0;
|
| 2261 |
+
color: var(--body-text-color-subdued);
|
| 2262 |
+
font-size: 0.95rem;
|
| 2263 |
+
display: none;
|
| 2264 |
+
}
|
| 2265 |
+
#audio_input_hidden { display: none !important; }
|
| 2266 |
+
"""
|
| 2267 |
+
|
| 2268 |
+
# Hiding Gradio Footer, Branding and Settings
|
| 2269 |
+
css += """
|
| 2270 |
+
footer {
|
| 2271 |
+
display: none !important;
|
| 2272 |
+
}
|
| 2273 |
+
.gradio-container footer {
|
| 2274 |
+
display: none !important;
|
| 2275 |
+
}
|
| 2276 |
+
div.footer {
|
| 2277 |
+
display: none !important;
|
| 2278 |
+
}
|
| 2279 |
+
.flagging {
|
| 2280 |
+
display: none !important;
|
| 2281 |
+
}
|
| 2282 |
+
/* Hide the 'Use via API' link if visible */
|
| 2283 |
+
.api-logo, .built-with {
|
| 2284 |
+
display: none !important;
|
| 2285 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2286 |
"""
|
| 2287 |
|
| 2288 |
|
|
|
|
| 2321 |
####################################################################################################
|
| 2322 |
### PART 20: Gradio UI Layout & Launch
|
| 2323 |
####################################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2324 |
def apply_example(idx: str):
|
| 2325 |
idx = int(idx)
|
| 2326 |
+
|
| 2327 |
+
# Read the example row from your list
|
| 2328 |
img, prompt_txt, cam, res, mode, vid, aud, end_img = examples_list[idx]
|
| 2329 |
+
|
| 2330 |
img_path = img if img else None
|
| 2331 |
vid_path = vid if vid else None
|
| 2332 |
aud_path = aud if aud else None
|
| 2333 |
+
|
| 2334 |
input_image_update = img_path
|
| 2335 |
prompt_update = prompt_txt
|
| 2336 |
camera_update = cam
|
|
|
|
| 2339 |
video_update = gr.update(value=vid_path, visible=(mode == "Motion Control"))
|
| 2340 |
audio_update = aud_path
|
| 2341 |
end_image = end_img
|
| 2342 |
+
|
| 2343 |
+
# Clear the output video when loading a new example
|
| 2344 |
output_video_update = gr.update(value=None)
|
| 2345 |
+
|
| 2346 |
+
return (
|
| 2347 |
+
input_image_update,
|
| 2348 |
+
prompt_update,
|
| 2349 |
+
camera_update,
|
| 2350 |
+
resolution_update,
|
| 2351 |
+
mode_update,
|
| 2352 |
+
video_update,
|
| 2353 |
+
audio_update,
|
| 2354 |
+
audio_update,
|
| 2355 |
+
end_image,
|
| 2356 |
+
output_video_update
|
| 2357 |
+
)
|
| 2358 |
+
|
| 2359 |
|
| 2360 |
with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
|
|
|
|
| 2361 |
|
| 2362 |
+
# Updated Header to Persian
|
| 2363 |
gr.HTML(
|
| 2364 |
"""
|
| 2365 |
+
<div style="text-align: center; padding: 20px;">
|
| 2366 |
+
<h1 style="font-size: 28px; font-weight: bold; margin-bottom: 10px; color: var(--body-text-color);">
|
| 2367 |
+
ساخت ویدیو با هوش مصنوعی
|
| 2368 |
+
</h1>
|
| 2369 |
+
<p style="font-size: 18px; color: var(--body-text-color-subdued); margin: 0;">
|
| 2370 |
+
با پشتیبانی از صدا و دو تصویر
|
| 2371 |
+
</p>
|
| 2372 |
+
</div>
|
| 2373 |
"""
|
| 2374 |
)
|
| 2375 |
|
| 2376 |
with gr.Column(elem_id="col-container"):
|
| 2377 |
with gr.Row(elem_id="mode-row"):
|
| 2378 |
+
# Updated choices to Persian
|
| 2379 |
+
radioanimated_mode = RadioAnimated(
|
| 2380 |
+
choices=["تبدیل تصویر به ویدیو", "تکمیل فریمهای میانی"],
|
| 2381 |
+
value="تبدیل تصویر به ویدیو",
|
| 2382 |
+
elem_id="radioanimated_mode"
|
| 2383 |
+
)
|
| 2384 |
with gr.Row():
|
| 2385 |
with gr.Column(elem_id="step-column"):
|
| 2386 |
+
|
| 2387 |
with gr.Row():
|
| 2388 |
+
|
| 2389 |
+
first_frame = gr.Image(
|
| 2390 |
+
label="تصویر اول (اختیاری)",
|
| 2391 |
+
type="filepath",
|
| 2392 |
+
height=256
|
| 2393 |
+
)
|
| 2394 |
+
|
| 2395 |
+
end_frame = gr.Image(
|
| 2396 |
+
label="تصویر آخر (اختیاری)",
|
| 2397 |
+
type="filepath",
|
| 2398 |
+
height=256,
|
| 2399 |
+
visible=False,
|
| 2400 |
+
)
|
| 2401 |
+
|
| 2402 |
+
# input_video is defined but hidden
|
| 2403 |
+
input_video = gr.Video(
|
| 2404 |
+
label="Motion Reference Video",
|
| 2405 |
+
height=256,
|
| 2406 |
+
visible=False,
|
| 2407 |
+
)
|
| 2408 |
+
|
| 2409 |
+
relocate = gr.HTML(
|
| 2410 |
+
value="",
|
| 2411 |
+
html_template="<div></div>",
|
| 2412 |
+
js_on_load=r"""
|
| 2413 |
+
(() => {
|
| 2414 |
+
function moveIntoFooter() {
|
| 2415 |
+
const promptRoot = document.querySelector("#prompt_ui");
|
| 2416 |
+
if (!promptRoot) return false;
|
| 2417 |
+
|
| 2418 |
+
const footer = promptRoot.querySelector(".ds-footer");
|
| 2419 |
+
if (!footer) return false;
|
| 2420 |
+
|
| 2421 |
+
const dur = document.querySelector("#duration_ui .cd-wrap");
|
| 2422 |
+
const res = document.querySelector("#resolution_ui .cd-wrap");
|
| 2423 |
+
const cam = document.querySelector("#camera_ui .cd-wrap");
|
| 2424 |
+
|
| 2425 |
+
if (!dur || !res || !cam) return false;
|
| 2426 |
+
|
| 2427 |
+
footer.appendChild(dur);
|
| 2428 |
+
footer.appendChild(res);
|
| 2429 |
+
footer.appendChild(cam);
|
| 2430 |
+
|
| 2431 |
+
return true;
|
| 2432 |
+
}
|
| 2433 |
+
|
| 2434 |
+
const tick = () => {
|
| 2435 |
+
if (!moveIntoFooter()) requestAnimationFrame(tick);
|
| 2436 |
+
};
|
| 2437 |
+
requestAnimationFrame(tick);
|
| 2438 |
+
})();
|
| 2439 |
+
"""
|
| 2440 |
+
)
|
| 2441 |
+
|
| 2442 |
+
|
| 2443 |
+
prompt_ui = PromptBox(
|
| 2444 |
+
value="این تصویر را با حرکت سینمایی و انیمیشن روان زنده کن",
|
| 2445 |
+
elem_id="prompt_ui",
|
| 2446 |
+
)
|
| 2447 |
+
|
| 2448 |
+
# Hidden real audio input (backend value)
|
| 2449 |
+
audio_input = gr.File(
|
| 2450 |
+
label="Audio (Optional)",
|
| 2451 |
+
file_types=["audio"],
|
| 2452 |
+
type="filepath",
|
| 2453 |
+
elem_id="audio_input_hidden",
|
| 2454 |
+
)
|
| 2455 |
+
|
| 2456 |
+
# Custom UI that feeds the hidden gr.Audio above
|
| 2457 |
+
audio_ui = AudioDropUpload(
|
| 2458 |
+
target_audio_elem_id="audio_input_hidden",
|
| 2459 |
+
elem_id="audio_ui",
|
| 2460 |
+
)
|
| 2461 |
+
|
| 2462 |
+
prompt = gr.Textbox(
|
| 2463 |
+
label="Prompt",
|
| 2464 |
+
value="این تصویر را با حرکت سینمایی و انیمیشن روان زنده کن",
|
| 2465 |
+
lines=3,
|
| 2466 |
+
max_lines=3,
|
| 2467 |
+
placeholder="حرکت و انیمیشن مورد نظر خود را توصیف کنید...",
|
| 2468 |
+
visible=False
|
| 2469 |
+
)
|
| 2470 |
+
|
| 2471 |
+
enhance_prompt = gr.Checkbox(
|
| 2472 |
+
label="Enhance Prompt",
|
| 2473 |
+
value=True,
|
| 2474 |
+
visible=False
|
| 2475 |
+
)
|
| 2476 |
+
|
| 2477 |
with gr.Accordion("تنظیمات پیشرفته", open=False, visible=False):
|
| 2478 |
+
seed = gr.Slider(
|
| 2479 |
+
label="سید (Seed)",
|
| 2480 |
+
minimum=0,
|
| 2481 |
+
maximum=MAX_SEED,
|
| 2482 |
+
value=DEFAULT_SEED,
|
| 2483 |
+
step=1
|
| 2484 |
+
)
|
| 2485 |
+
|
| 2486 |
randomize_seed = gr.Checkbox(label="استفاده از سید تصادفی", value=True)
|
| 2487 |
|
| 2488 |
+
|
| 2489 |
with gr.Column(elem_id="step-column"):
|
| 2490 |
+
output_video = gr.Video(label="ویدیوی ساخته شده", autoplay=True, height=512)
|
| 2491 |
+
|
| 2492 |
with gr.Row(elem_id="controls-row"):
|
| 2493 |
+
|
| 2494 |
+
duration_ui = CameraDropdown(
|
| 2495 |
+
choices=["3s", "5s", "10s"],
|
| 2496 |
+
value="5s",
|
| 2497 |
+
title="مدت زمان ویدیو",
|
| 2498 |
+
elem_id="duration_ui"
|
| 2499 |
+
)
|
| 2500 |
+
|
| 2501 |
+
duration = gr.Slider(
|
| 2502 |
+
label="Duration (seconds)",
|
| 2503 |
+
minimum=1.0,
|
| 2504 |
+
maximum=10.0,
|
| 2505 |
+
value=5.0,
|
| 2506 |
+
step=0.1,
|
| 2507 |
+
visible=False
|
| 2508 |
+
)
|
| 2509 |
+
|
| 2510 |
+
ICON_16_9 = """<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
| 2511 |
+
<rect x="3" y="7" width="18" height="10" rx="2" stroke="currentColor" stroke-width="2"/>
|
| 2512 |
+
</svg>"""
|
| 2513 |
+
|
| 2514 |
+
ICON_1_1 = """<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
| 2515 |
+
<rect x="6" y="6" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"/>
|
| 2516 |
+
</svg>"""
|
| 2517 |
+
|
| 2518 |
+
ICON_9_16 = """<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
| 2519 |
+
<rect x="7" y="3" width="10" height="18" rx="2" stroke="currentColor" stroke-width="2"/>
|
| 2520 |
+
</svg>"""
|
| 2521 |
+
|
| 2522 |
+
|
| 2523 |
+
resolution_ui = CameraDropdown(
|
| 2524 |
+
choices=[
|
| 2525 |
+
{"label": "16:9", "value": "16:9", "icon": ICON_16_9},
|
| 2526 |
+
{"label": "1:1", "value": "1:1", "icon": ICON_1_1},
|
| 2527 |
+
{"label": "9:16", "value": "9:16", "icon": ICON_9_16},
|
| 2528 |
+
],
|
| 2529 |
+
value="16:9",
|
| 2530 |
+
title="ابعاد تصویر",
|
| 2531 |
+
elem_id="resolution_ui"
|
| 2532 |
+
)
|
| 2533 |
+
|
| 2534 |
+
|
| 2535 |
+
width = gr.Number(label="Width", value=DEFAULT_1_STAGE_WIDTH, precision=0, visible=False)
|
| 2536 |
+
height = gr.Number(label="Height", value=DEFAULT_1_STAGE_HEIGHT, precision=0, visible=False)
|
| 2537 |
+
|
| 2538 |
+
camera_ui = CameraDropdown(
|
| 2539 |
+
choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES],
|
| 2540 |
+
value="No LoRA",
|
| 2541 |
+
title="افکت دوربین (LoRA)",
|
| 2542 |
+
elem_id="camera_ui",
|
| 2543 |
+
)
|
| 2544 |
+
|
| 2545 |
+
# Hidden real dropdown (backend value)
|
| 2546 |
+
camera_lora = gr.Dropdown(
|
| 2547 |
+
label="Camera Control LoRA",
|
| 2548 |
+
choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES],
|
| 2549 |
+
value="No LoRA",
|
| 2550 |
+
visible=False
|
| 2551 |
+
)
|
| 2552 |
+
|
| 2553 |
generate_btn = gr.Button("🤩 ساخت ویدیو", variant="primary", elem_classes="button-gradient")
|
| 2554 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2555 |
|
| 2556 |
+
camera_ui.change(
|
| 2557 |
+
fn=lambda x: x,
|
| 2558 |
+
inputs=camera_ui,
|
| 2559 |
+
outputs=camera_lora,
|
| 2560 |
+
api_visibility="private"
|
| 2561 |
+
)
|
| 2562 |
+
|
| 2563 |
+
radioanimated_mode.change(
|
| 2564 |
+
fn=on_mode_change,
|
| 2565 |
+
inputs=radioanimated_mode,
|
| 2566 |
+
outputs=[input_video, end_frame],
|
| 2567 |
+
api_visibility="private",
|
| 2568 |
+
)
|
| 2569 |
+
|
| 2570 |
+
|
| 2571 |
+
duration_ui.change(
|
| 2572 |
+
fn=apply_duration,
|
| 2573 |
+
inputs=duration_ui,
|
| 2574 |
+
outputs=[duration],
|
| 2575 |
+
api_visibility="private"
|
| 2576 |
+
)
|
| 2577 |
+
resolution_ui.change(
|
| 2578 |
+
fn=apply_resolution,
|
| 2579 |
+
inputs=resolution_ui,
|
| 2580 |
+
outputs=[width, height],
|
| 2581 |
+
api_visibility="private"
|
| 2582 |
+
)
|
| 2583 |
+
prompt_ui.change(
|
| 2584 |
+
fn=lambda x: x,
|
| 2585 |
+
inputs=prompt_ui,
|
| 2586 |
+
outputs=prompt,
|
| 2587 |
+
api_visibility="private"
|
| 2588 |
+
)
|
| 2589 |
+
|
| 2590 |
+
|
| 2591 |
generate_btn.click(
|
| 2592 |
fn=generate_video,
|
| 2593 |
+
inputs=[
|
| 2594 |
+
first_frame,
|
| 2595 |
+
end_frame,
|
| 2596 |
+
prompt,
|
| 2597 |
+
duration,
|
| 2598 |
+
input_video,
|
| 2599 |
+
radioanimated_mode,
|
| 2600 |
+
enhance_prompt,
|
| 2601 |
+
seed,
|
| 2602 |
+
randomize_seed,
|
| 2603 |
+
height,
|
| 2604 |
+
width,
|
| 2605 |
+
camera_lora,
|
| 2606 |
+
audio_input
|
| 2607 |
+
],
|
| 2608 |
+
outputs=[output_video]
|
| 2609 |
)
|
| 2610 |
|
| 2611 |
+
# Updated Examples to use Persian modes
|
| 2612 |
examples_list = [
|
| 2613 |
+
[
|
| 2614 |
+
"examples/supergirl-2.png",
|
| 2615 |
+
"A fuzzy puppet superhero character resembling a female puppet with blonde hair and a blue superhero suit sleeping in bed and just waking up, she gradually gets up, rubbing her eyes and looking at her dog that just popped on the bed. the scene feels chaotic, comedic, and emotional with expressive puppet reactions, cinematic lighting, smooth camera motion, shallow depth of field, and high-quality puppet-style animation",
|
| 2616 |
+
"Static",
|
| 2617 |
+
"16:9",
|
| 2618 |
+
"تبدیل تصویر به ویدیو",
|
| 2619 |
+
None,
|
| 2620 |
+
"examples/supergirl.m4a",
|
| 2621 |
+
None,
|
| 2622 |
+
],
|
| 2623 |
+
[
|
| 2624 |
+
"examples/frame3.png",
|
| 2625 |
+
"a woman in a white dress standing in a supermarket, looking at a stack of pomegranates, she picks one and takes a bite, the camera zooms in to a close up of the pomegranate seeds. A calm music is playing in the supermarket and you can hear her taking a bite.",
|
| 2626 |
+
"Zoom In",
|
| 2627 |
+
"16:9",
|
| 2628 |
+
"تکمیل فریمهای میانی",
|
| 2629 |
+
None,
|
| 2630 |
+
None,
|
| 2631 |
+
"examples/frame4.png",
|
| 2632 |
+
],
|
| 2633 |
+
[
|
| 2634 |
+
"examples/supergirl.png",
|
| 2635 |
+
"A fuzzy puppet superhero character resembling a female puppet with blonde hair and a blue superhero suit stands inside an icy cave made of frozen walls and icicles, she looks panicked and frantic, rapidly turning her head left and right and scanning the cave while waving her arms and shouting angrily and desperately, mouthing the words “where the hell is my dog,” her movements exaggerated and puppet-like with high energy and urgency, suddenly a second puppet dog bursts into frame from the side, jumping up excitedly and tackling her affectionately while licking her face repeatedly, she freezes in surprise and then breaks into relief and laughter as the dog continues licking her, the scene feels chaotic, comedic, and emotional with expressive puppet reactions, cinematic lighting, smooth camera motion, shallow depth of field, and high-quality puppet-style animation",
|
| 2636 |
+
"No LoRA",
|
| 2637 |
+
"16:9",
|
| 2638 |
+
"تبدیل تصویر به ویدیو",
|
| 2639 |
+
None,
|
| 2640 |
+
None,
|
| 2641 |
+
None,
|
| 2642 |
+
],
|
| 2643 |
+
[
|
| 2644 |
+
"examples/highland.png",
|
| 2645 |
+
"Realistic POV selfie-style video in a snowy, foggy field. Two shaggy Highland cows with long curved horns stand ahead. The camera is handheld and slightly shaky. The woman filming talks nervously and excitedly in a vlog tone: \"Oh my god guys… look how big those horns are… I’m kinda scared.\" The cow on the left walks toward the camera in a cute, bouncy, hopping way, curious and gentle. Snow crunches under its hooves, breath visible in the cold air. The horns look massive from the POV. As the cow gets very close, its wet nose with slight dripping fills part of the frame. She laughs nervously but reaches out and pets the cow. The cow makes deep, soft, interesting mooing and snorting sounds, calm and friendly. Ultra-realistic, natural lighting, immersive audio, documentary-style realism.",
|
| 2646 |
+
"No LoRA",
|
| 2647 |
+
"16:9",
|
| 2648 |
+
"تبدیل تصویر به ویدیو",
|
| 2649 |
+
None,
|
| 2650 |
+
None,
|
| 2651 |
+
None,
|
| 2652 |
+
],
|
| 2653 |
+
[
|
| 2654 |
+
"examples/wednesday.png",
|
| 2655 |
+
"A cinematic dolly out of Wednesday Addams frozen mid-dance on a dark, blue-lit ballroom floor as students move indistinctly behind her, their footsteps and muffled music reduced to a distant, underwater thrum; the audio foregrounds her steady breathing and the faint rustle of fabric as she slowly raises one arm, never breaking eye contact with the camera, then after a deliberately long silence she speaks in a flat, dry, perfectly controlled voice, “I don’t dance… I vibe code,” each word crisp and unemotional, followed by an abrupt cutoff of her voice as the background sound swells slightly, reinforcing the deadpan humor, with precise lip sync, minimal facial movement, stark gothic lighting, and cinematic realism.",
|
| 2656 |
+
"Zoom Out",
|
| 2657 |
+
"16:9",
|
| 2658 |
+
"تبدیل تصویر به ویدیو",
|
| 2659 |
+
None,
|
| 2660 |
+
None,
|
| 2661 |
+
None,
|
| 2662 |
+
],
|
| 2663 |
+
[
|
| 2664 |
+
"examples/astronaut.png",
|
| 2665 |
+
"An astronaut hatches from a fragile egg on the surface of the Moon, the shell cracking and peeling apart in gentle low-gravity motion. Fine lunar dust lifts and drifts outward with each movement, floating in slow arcs before settling back onto the ground. The astronaut pushes free in a deliberate, weightless motion, small fragments of the egg tumbling and spinning through the air. In the background, the deep darkness of space subtly shifts as stars glide with the camera's movement, emphasizing vast depth and scale. The camera performs a smooth, cinematic slow push-in, with natural parallax between the foreground dust, the astronaut, and the distant starfield. Ultra-realistic detail, physically accurate low-gravity motion, cinematic lighting, and a breath-taking, movie-like shot.",
|
| 2666 |
+
"Static",
|
| 2667 |
+
"1:1",
|
| 2668 |
+
"تبدیل تصویر به ویدیو",
|
| 2669 |
+
None,
|
| 2670 |
+
None,
|
| 2671 |
+
None,
|
| 2672 |
+
],
|
| 2673 |
]
|
| 2674 |
|
| 2675 |
examples_obj = create_examples(
|
| 2676 |
examples=examples_list,
|
| 2677 |
fn=generate_video_example,
|
| 2678 |
inputs=[first_frame, prompt_ui, camera_ui, resolution_ui, radioanimated_mode, input_video, audio_input, end_frame],
|
| 2679 |
+
outputs = [output_video],
|
| 2680 |
+
label="نمونهها",
|
| 2681 |
+
cache_examples=True,
|
| 2682 |
+
visible=False
|
| 2683 |
)
|
| 2684 |
|
| 2685 |
+
preset_gallery = PresetGallery(
|
| 2686 |
+
items=[
|
| 2687 |
+
{"thumb": "examples/supergirl-2.png", "label": "تصویر و صدا به ویدیو"},
|
| 2688 |
+
{"thumb": "examples/frame3.png", "label": "تصویر اول و آخر"},
|
| 2689 |
+
{"thumb": "examples/supergirl.png", "label": "تصویر به ویدیو (عروسک)"},
|
| 2690 |
+
{"thumb": "examples/highland.png", "label": "تصویر به ویدیو (گاو)"},
|
| 2691 |
+
{"thumb": "examples/wednesday.png", "label": "تصویر به ویدیو (ونزدی)"},
|
| 2692 |
+
{"thumb": "examples/astronaut.png", "label": "تصویر به ویدیو (فضانورد)"},
|
| 2693 |
+
],
|
| 2694 |
+
title="برای شروع روی یکی از نمونهها کلیک کنید",
|
| 2695 |
+
)
|
| 2696 |
|
| 2697 |
def on_audio_ui_change(v):
|
| 2698 |
+
# Our JS sends "__CLEAR__" when the user presses the X
|
| 2699 |
+
if v == "__CLEAR__" or v is None or v == "":
|
| 2700 |
+
return None
|
| 2701 |
+
# For normal events (uploads), do nothing (keep whatever gr.File already has)
|
| 2702 |
return gr.update()
|
| 2703 |
|
| 2704 |
+
audio_ui.change(
|
| 2705 |
+
fn=on_audio_ui_change,
|
| 2706 |
+
inputs=audio_ui,
|
| 2707 |
+
outputs=audio_input,
|
| 2708 |
+
api_visibility="private",
|
| 2709 |
+
)
|
| 2710 |
|
| 2711 |
preset_gallery.change(
|
| 2712 |
fn=apply_example,
|
| 2713 |
inputs=preset_gallery,
|
| 2714 |
+
outputs=[
|
| 2715 |
+
first_frame,
|
| 2716 |
+
prompt_ui,
|
| 2717 |
+
camera_ui,
|
| 2718 |
+
resolution_ui,
|
| 2719 |
+
radioanimated_mode,
|
| 2720 |
+
input_video,
|
| 2721 |
+
audio_input,
|
| 2722 |
+
audio_ui,
|
| 2723 |
+
end_frame,
|
| 2724 |
+
output_video # Clears the output video
|
| 2725 |
+
],
|
| 2726 |
api_visibility="private",
|
| 2727 |
)
|
| 2728 |
|