Opera8 commited on
Commit
1be3e2c
·
verified ·
1 Parent(s): fb5376e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1307 -264
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, status = generate_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, status
1259
 
1260
  def get_duration(
1261
  first_frame,
@@ -1274,20 +1281,21 @@ def get_duration(
1274
  progress
1275
  ):
1276
  extra_time = 0
1277
- if audio_path is not None: extra_time += 10
1278
- if input_video is not None: extra_time += 60
1279
- if duration <= 3: return 60 + extra_time
1280
- elif duration <= 5: return 80 + extra_time
1281
- else: return 120 + extra_time
1282
-
1283
- # تابع جدید برای نمایش پیام خطا
1284
- def get_error_html(message):
1285
- return f"""
1286
- <div style="background-color: #fee2e2; border: 1px solid #ef4444; color: #b91c1c; padding: 12px; border-radius: 8px; text-align: center; margin-bottom: 10px; font-weight: bold; display: flex; align-items: center; justify-content: center; gap: 8px;">
1287
- <span style="font-size: 1.2em;">⛔</span>
1288
- {message}
1289
- </div>
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
- try:
1314
- if audio_path is None:
1315
- print(f'generating with duration:{duration} and LoRA:{camera_lora} in {width}x{height}')
1316
- else:
1317
- print(f'generating with duration:{duration} and audio in {width}x{height}')
1318
 
1319
- current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
1320
- frame_rate = 24.0
1321
- num_frames = int(duration * frame_rate) + 1
1322
-
1323
- with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
1324
- output_path = tmpfile.name
1325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1326
  images = []
1327
- videos = []
1328
-
1329
- if first_frame is not None:
1330
- images.append((first_frame, 0, 1.0))
1331
-
1332
- if generation_mode == "تکمیل فریم‌های میانی" and end_frame is not None:
1333
- end_idx = max(0, num_frames - 1)
1334
- images.append((end_frame, end_idx, 0.5))
1335
-
1336
- embeddings, final_prompt, status = encode_prompt(prompt=prompt, enhance_prompt=enhance_prompt, input_image=first_frame, seed=current_seed, negative_prompt="")
1337
- video_context = embeddings["video_context"].to("cuda", non_blocking=True)
1338
- audio_context = embeddings["audio_context"].to("cuda", non_blocking=True)
1339
- del embeddings, final_prompt, status
1340
- torch.cuda.empty_cache()
1341
-
1342
- if audio_path is not None:
1343
- with torch.inference_mode():
1344
- _, n_audio_context = encode_text_simple(text_encoder, "")
1345
- del audio_context
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
- except Exception as e:
1372
- error_str = str(e)
1373
- print(f"An error occurred: {error_str}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- if resolution == "16:9": w, h = 768, 512
1385
- elif resolution == "1:1": w, h = 512, 512
1386
- elif resolution == "9:16": w, h = 512, 768
 
 
 
 
 
1387
  return int(w), int(h)
1388
 
1389
  def apply_duration(duration: str):
1390
- return int(duration[:-1])
 
1391
 
1392
  def on_mode_change(selected: str):
 
 
1393
  is_interpolate = (selected == "تکمیل فریم‌های میانی")
1394
- return gr.update(visible=False), gr.update(visible=is_interpolate)
 
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
- /* --- IP Reset Guide CSS (مودال) --- */
1408
- :root {
1409
- --guide-bg: rgba(255, 255, 255, 0.98);
1410
- --guide-border: rgba(102, 126, 234, 0.2);
1411
- --guide-text-title: #2d3748;
1412
- --guide-text-body: #4a5568;
1413
- --guide-accent: #667eea;
1414
- --primary-gradient-guide: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1415
- --success-gradient-guide: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
1416
- --radius-md-guide: 12px;
1417
- --radius-lg-guide: 16px;
1418
- --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
1419
- --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
1420
- --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
1421
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1422
 
1423
- @keyframes float {
1424
- 0%, 100% { transform: translateY(0px); }
1425
- 50% { transform: translateY(-10px); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1426
  }
1427
- @keyframes slideInUp {
1428
- from { opacity: 0; transform: translateY(30px); }
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
- font-family: inherit;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1449
  }
1450
- .ip-reset-guide-container::before {
1451
- content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: var(--primary-gradient-guide);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- status_update = "" # Clear status on example load
1595
- return (input_image_update, prompt_update, camera_update, resolution_update, mode_update, video_update, audio_update, audio_update, end_image, output_video_update, status_update)
 
 
 
 
 
 
 
 
 
 
 
 
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;"><h1 style="font-size: 28px; font-weight: bold; margin-bottom: 10px; color: var(--body-text-color);">ساخت ویدیو با هوش مصنوعی</h1><p style="font-size: 18px; color: var(--body-text-color-subdued); margin: 0;">با پشتیبانی از صدا و دو تصویر</p></div>
 
 
 
 
 
 
 
1603
  """
1604
  )
1605
 
1606
  with gr.Column(elem_id="col-container"):
1607
  with gr.Row(elem_id="mode-row"):
1608
- radioanimated_mode = RadioAnimated(choices=["تبدیل تصویر به ویدیو", "تکمیل فریم‌های میانی"], value="تبدیل تصویر به ویدیو", elem_id="radioanimated_mode")
 
 
 
 
 
1609
  with gr.Row():
1610
  with gr.Column(elem_id="step-column"):
 
1611
  with gr.Row():
1612
- first_frame = gr.Image(label="تصویر اول (اختیاری)", type="filepath", height=256)
1613
- end_frame = gr.Image(label="تصویر آخر (اختیاری)", type="filepath", height=256, visible=False)
1614
- input_video = gr.Video(label="Motion Reference Video", height=256, visible=False)
1615
-
1616
- relocate = gr.HTML(value="", html_template="<div></div>", js_on_load=r"""...""") # JS from previous parts
1617
- prompt_ui = PromptBox(value="این تصویر را با حرکت سینمایی و انیمیشن روان زنده کن", elem_id="prompt_ui")
1618
- audio_input = gr.File(label="Audio (Optional)", file_types=["audio"], type="filepath", elem_id="audio_input_hidden")
1619
- audio_ui = AudioDropUpload(target_audio_elem_id="audio_input_hidden", elem_id="audio_ui")
1620
- prompt = gr.Textbox(label="Prompt", value="این تصویر را با حرکت سینمایی و انیمیشن روان زنده کن", lines=3, max_lines=3, placeholder="حرکت و انیمیشن مورد نظر خود را توصیف کنید...", visible=False)
1621
- enhance_prompt = gr.Checkbox(label="Enhance Prompt", value=True, visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1622
  with gr.Accordion("تنظیمات پیشرفته", open=False, visible=False):
1623
- seed = gr.Slider(label="سید (Seed)", minimum=0, maximum=MAX_SEED, value=DEFAULT_SEED, step=1)
 
 
 
 
 
 
 
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=450)
1628
- status_box = gr.HTML(label="وضعیت") # --- استاتوس باکس جدید ---
1629
  with gr.Row(elem_id="controls-row"):
1630
- duration_ui = CameraDropdown(choices=["3s", "5s", "10s"], value="5s", title="مدت زمان ویدیو", elem_id="duration_ui")
1631
- duration = gr.Slider(label="Duration (seconds)", minimum=1.0, maximum=10.0, value=5.0, step=0.1, visible=False)
1632
- ICON_16_9 = """...""" # SVG code
1633
- ICON_1_1 = """...""" # SVG code
1634
- ICON_9_16 = """...""" # SVG code
1635
- resolution_ui = CameraDropdown(choices=[{"label": "16:9", "value": "16:9", "icon": ICON_16_9}, {"label": "1:1", "value": "1:1", "icon": ICON_1_1}, {"label": "9:16", "value": "9:16", "icon": ICON_9_16}], value="16:9", title="ابعاد تصویر", elem_id="resolution_ui")
1636
- width = gr.Number(label="Width", value=DEFAULT_1_STAGE_WIDTH, precision=0, visible=False)
1637
- height = gr.Number(label="Height", value=DEFAULT_1_STAGE_HEIGHT, precision=0, visible=False)
1638
- camera_ui = CameraDropdown(choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES], value="No LoRA", title="افکت دوربین (LoRA)", elem_id="camera_ui")
1639
- camera_lora = gr.Dropdown(label="Camera Control LoRA", choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES], value="No LoRA", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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=[first_frame, end_frame, prompt, duration, input_video, radioanimated_mode, enhance_prompt, seed, randomize_seed, height, width, camera_lora, audio_input],
1651
- outputs=[output_video, status_box] # --- خروجی به استاتوس باکس اضافه شد ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1652
  )
1653
 
 
1654
  examples_list = [
1655
- ["examples/supergirl-2.png", "A fuzzy puppet...", "Static", "16:9", "تبدیل تصویر به ویدیو", None, "examples/supergirl.m4a", None],
1656
- ["examples/frame3.png", "a woman in a white dress...", "Zoom In", "16:9", "تکمیل فریم‌های میانی", None, None, "examples/frame4.png"],
1657
- ["examples/supergirl.png", "A fuzzy puppet superhero character...", "No LoRA", "16:9", "تبدیل تصویر به ویدیو", None, None, None],
1658
- ["examples/highland.png", "Realistic POV selfie-style video...", "No LoRA", "16:9", "تبدیل تصویر به ویدیو", None, None, None],
1659
- ["examples/wednesday.png", "A cinematic dolly out of Wednesday...", "Zoom Out", "16:9", "تبدیل تصویر به ویدیو", None, None, None],
1660
- ["examples/astronaut.png", "An astronaut hatches from a fragile egg...", "Static", "1:1", "تبدیل تصویر به ویدیو", None, None, None],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, status_box],
1668
- label="نمونه‌ها", cache_examples=True, visible=False
 
 
1669
  )
1670
 
1671
- preset_gallery = PresetGallery(items=[...], title="برای شروع روی یکی از نمونه‌ها کلیک کنید") # Data from previous parts
 
 
 
 
 
 
 
 
 
 
1672
 
1673
  def on_audio_ui_change(v):
1674
- if v == "__CLEAR__" or v is None or v == "": return None
 
 
 
1675
  return gr.update()
1676
 
1677
- audio_ui.change(fn=on_audio_ui_change, inputs=audio_ui, outputs=audio_input, api_visibility="private")
 
 
 
 
 
1678
 
1679
  preset_gallery.change(
1680
  fn=apply_example,
1681
  inputs=preset_gallery,
1682
- outputs=[first_frame, prompt_ui, camera_ui, resolution_ui, radioanimated_mode, input_video, audio_input, audio_ui, end_frame, output_video, status_box],
 
 
 
 
 
 
 
 
 
 
 
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