Opera8 commited on
Commit
e9ca807
·
verified ·
1 Parent(s): 7de629a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -400
app.py CHANGED
@@ -2319,7 +2319,7 @@ def apply_example(idx: str):
2319
 
2320
 
2321
  ####################################################################################################
2322
- ### PART 20: Gradio UI Layout & Launch
2323
  ####################################################################################################
2324
 
2325
  # JS Function to handle download request via PostMessage
@@ -2329,30 +2329,11 @@ async (video) => {
2329
  alert("لطفاً ابتدا ویدیو را تولید کنید.");
2330
  return;
2331
  }
2332
-
2333
- // Gradio Video component passes an object or string
2334
- let fileUrl = "";
2335
- if (typeof video === 'string') {
2336
- fileUrl = video;
2337
- } else if (video && video.url) {
2338
- fileUrl = video.url;
2339
- } else if (video && video.path) {
2340
- fileUrl = video.path;
2341
- }
2342
-
2343
- // Fix relative paths to absolute URLs for the parent iframe
2344
  if (fileUrl && !fileUrl.startsWith('http')) {
2345
- // Remove leading slash if exists to prevent double slash with origin
2346
  let cleanPath = fileUrl.startsWith('/') ? fileUrl.substring(1) : fileUrl;
2347
-
2348
- // Check if it's already a file route
2349
- if (cleanPath.startsWith('file=')) {
2350
- fileUrl = window.location.origin + "/" + cleanPath;
2351
- } else {
2352
- fileUrl = window.location.origin + "/file=" + cleanPath;
2353
- }
2354
  }
2355
-
2356
  if (fileUrl) {
2357
  console.log("Sending download request for:", fileUrl);
2358
  window.parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: fileUrl }, '*');
@@ -2360,125 +2341,75 @@ async (video) => {
2360
  }
2361
  """
2362
 
2363
- # این تابع جاوااسکریپت جدید برای بهینه سازی پرامپت است
2364
- # این کد در مرورگر کاربر اجرا می شود
2365
- js_enhancer_and_trigger = """
2366
  async (firstFrame, promptText, mode) => {
2367
- // گرفتن دکمه ها و جعبه متن از طریق DOM
2368
- const enhancerBtn = document.querySelector("#enhancer_btn button");
2369
- const realGenerateBtn = document.querySelector("#real_generate_btn button");
2370
- const promptTextarea = document.querySelector("#prompt_ui textarea");
2371
-
2372
- // اگر حالت انتخابی نیازی به بهینه سازی ندارد، مستقیما ویدیو را بساز
2373
  if (mode !== 'تبدیل تصویر به ویدیو' && mode !== 'تکمیل فریم‌های میانی') {
2374
- realGenerateBtn.click();
2375
- return [firstFrame, promptText, mode]; // بازگشت مقادیر بدون تغییر
2376
  }
2377
 
2378
  // API بهینه ساز به تصویر نیاز دارد
2379
  if (!firstFrame) {
2380
- alert("برای بهینه سازی پرامپت، لطفاً ابتدا یک تصویر در قسمت صویر اول' آپلود کنید.");
2381
- return [firstFrame, promptText, mode];
 
2382
  }
2383
 
2384
- const originalButtonText = enhancerBtn.textContent;
2385
- enhancerBtn.disabled = true;
2386
- enhancerBtn.textContent = "⏳ در حال بهینه سازی پرامپت...";
2387
-
2388
  try {
2389
- // داده های تصویر را از URL آن دریافت می کنیم
2390
  const response = await fetch(firstFrame.url);
2391
  const imageBlob = await response.blob();
2392
 
2393
- // آماده سازی FormData برای ارسال به API
2394
  const formData = new FormData();
2395
- // نام فایل را برای سازگاری با API تنظیم می کنیم
2396
  const imageFile = new File([imageBlob], "image.jpg", { type: "image/jpeg" });
2397
  formData.append('image', imageFile);
2398
  formData.append('prompt', promptText);
2399
  formData.append('is_extension', 'false');
2400
 
2401
- // ارسال درخواست به API بهینه ساز
2402
  const apiResponse = await fetch('https://ezmarynoori-vidtolani.hf.space/api/enhance-animation-prompt', {
2403
  method: 'POST',
2404
  body: formData
2405
  });
2406
 
2407
- const data = await apiResponse.json();
2408
-
2409
  if (!apiResponse.ok) {
2410
- throw new Error(data.error || 'خطا در بهینه سازی پرامپت از سمت سرور.');
 
2411
  }
2412
 
2413
- const enhancedPrompt = data.animation_prompt;
2414
- console.log("Original Prompt:", promptText);
2415
- console.log("Enhanced Prompt:", enhancedPrompt);
2416
-
2417
- // پرامپت جدید را در جعبه متن قرار می دهیم
2418
- promptTextarea.value = enhancedPrompt;
2419
- // یک رویداد input ایجاد می کنیم تا Gradio متوجه تغییر شود
2420
- promptTextarea.dispatchEvent(new Event('input', { bubbles: true }));
2421
-
2422
- // حالا دکمه پنهان ساخت ویدیو را کلیک می کنیم
2423
- realGenerateBtn.click();
2424
 
2425
  } catch (error) {
2426
- console.error("Enhancement Error:", error);
2427
- alert(`خطایی در بهینه سازی پرامپت رخ داد: ${error.message}`);
2428
- } finally {
2429
- // دکمه را به حالت اولیه برمی گردانیم
2430
- enhancerBtn.disabled = false;
2431
- enhancerBtn.textContent = originalButtonText;
2432
  }
2433
-
2434
- // مقادیر ورودی را برمیگردانیم تا زنجیره گرادیو قطع نشود
2435
- // گرچه پرامپت اصلی دیگر استفاده نمی شود چون ما آن را مستقیما در DOM تغییر دادیم
2436
- return [firstFrame, promptText, mode];
2437
  }
2438
  """
2439
 
2440
 
2441
  def apply_example(idx: str):
2442
  idx = int(idx)
2443
-
2444
- # Read the example row from your list
2445
  img, prompt_txt, cam, res, mode, vid, aud, end_img = examples_list[idx]
2446
-
2447
- img_path = img if img else None
2448
- vid_path = vid if vid else None
2449
- aud_path = aud if aud else None
2450
-
2451
- input_image_update = img_path
2452
- prompt_update = prompt_txt
2453
- camera_update = cam
2454
- resolution_update = res
2455
- mode_update = mode
2456
- video_update = gr.update(value=vid_path, visible=(mode == "Motion Control"))
2457
- audio_update = aud_path
2458
- end_image = end_img
2459
-
2460
- # Clear the output video AND Hide download button when loading a new example
2461
- output_video_update = gr.update(value=None)
2462
- download_btn_update = gr.update(visible=False)
2463
-
2464
  return (
2465
- input_image_update,
2466
- prompt_update,
2467
- camera_update,
2468
- resolution_update,
2469
- mode_update,
2470
- video_update,
2471
- audio_update,
2472
- audio_update,
2473
- end_image,
2474
- output_video_update,
2475
- download_btn_update
2476
  )
2477
 
2478
-
2479
  with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
2480
 
2481
- # Updated Header to Persian
2482
  gr.HTML(
2483
  """
2484
  <div style="text-align: center; padding: 20px;">
@@ -2492,9 +2423,12 @@ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
2492
  """
2493
  )
2494
 
 
 
 
 
2495
  with gr.Column(elem_id="col-container"):
2496
  with gr.Row(elem_id="mode-row"):
2497
- # Updated choices to Persian
2498
  radioanimated_mode = RadioAnimated(
2499
  choices=["تبدیل تصویر به ویدیو", "تکمیل فریم‌های میانی"],
2500
  value="تبدیل تصویر به ویدیو",
@@ -2502,335 +2436,134 @@ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
2502
  )
2503
  with gr.Row():
2504
  with gr.Column(elem_id="step-column"):
2505
-
2506
  with gr.Row():
2507
-
2508
  first_frame = gr.Image(
2509
  label="تصویر اول (برای بهینه‌سازی پرامپت الزامی است)",
2510
  type="filepath",
2511
  height=256
2512
  )
2513
-
2514
  end_frame = gr.Image(
2515
  label="تصویر آخر (اختیاری)",
2516
  type="filepath",
2517
  height=256,
2518
  visible=False,
2519
  )
 
2520
 
2521
- # input_video is defined but hidden
2522
- input_video = gr.Video(
2523
- label="Motion Reference Video",
2524
- height=256,
2525
- visible=False,
2526
- )
2527
-
2528
- relocate = gr.HTML(
2529
- value="",
2530
- html_template="<div></div>",
2531
- js_on_load=r"""
2532
  (() => {
2533
  function moveIntoFooter() {
2534
- const promptRoot = document.querySelector("#prompt_ui");
2535
- if (!promptRoot) return false;
2536
-
2537
- const footer = promptRoot.querySelector(".ds-footer");
2538
- if (!footer) return false;
2539
-
2540
  const dur = document.querySelector("#duration_ui .cd-wrap");
2541
  const res = document.querySelector("#resolution_ui .cd-wrap");
2542
  const cam = document.querySelector("#camera_ui .cd-wrap");
2543
-
2544
  if (!dur || !res || !cam) return false;
2545
-
2546
- footer.appendChild(dur);
2547
- footer.appendChild(res);
2548
- footer.appendChild(cam);
2549
-
2550
  return true;
2551
  }
2552
-
2553
- const tick = () => {
2554
- if (!moveIntoFooter()) requestAnimationFrame(tick);
2555
- };
2556
  requestAnimationFrame(tick);
2557
  })();
2558
- """
2559
- )
2560
 
2561
-
2562
- prompt_ui = PromptBox(
2563
- value="این تصویر را با حرکت سینمایی و انیمیشن روان زنده کن",
2564
- elem_id="prompt_ui",
2565
- )
2566
-
2567
- # Hidden real audio input (backend value)
2568
- audio_input = gr.File(
2569
- label="Audio (Optional)",
2570
- file_types=["audio"],
2571
- type="filepath",
2572
- elem_id="audio_input_hidden",
2573
- )
2574
-
2575
- # Custom UI that feeds the hidden gr.Audio above
2576
- audio_ui = AudioDropUpload(
2577
- target_audio_elem_id="audio_input_hidden",
2578
- elem_id="audio_ui",
2579
- )
2580
-
2581
- prompt = gr.Textbox(
2582
- label="Prompt",
2583
- value="این تصویر را با حرکت سینمایی و انیمیشن روان زنده کن",
2584
- lines=3,
2585
- max_lines=3,
2586
- placeholder="حرکت و انیمیشن مورد نظر خود را توصیف کنید...",
2587
- visible=False
2588
- )
2589
-
2590
- enhance_prompt = gr.Checkbox(
2591
- label="Enhance Prompt",
2592
- value=True,
2593
- visible=False
2594
- )
2595
 
2596
  with gr.Accordion("تنظیمات پیشرفته", open=False, visible=False):
2597
- seed = gr.Slider(
2598
- label="سید (Seed)",
2599
- minimum=0,
2600
- maximum=MAX_SEED,
2601
- value=DEFAULT_SEED,
2602
- step=1
2603
- )
2604
-
2605
  randomize_seed = gr.Checkbox(label="استفاده از سید تصادفی", value=True)
2606
-
2607
 
2608
  with gr.Column(elem_id="step-column"):
2609
  output_video = gr.Video(label="ویدیوی ساخته شده", autoplay=True, height=512)
2610
-
2611
  with gr.Row():
2612
- download_btn = gr.Button(
2613
- "📥 دانلود ویدیو",
2614
- variant="secondary",
2615
- size="sm",
2616
- scale=0,
2617
- visible=False
2618
- )
2619
 
2620
  with gr.Row(elem_id="controls-row"):
2621
-
2622
- duration_ui = CameraDropdown(
2623
- choices=["3s", "5s", "10s"],
2624
- value="5s",
2625
- title="مدت زمان ویدیو",
2626
- elem_id="duration_ui"
2627
- )
2628
-
2629
- duration = gr.Slider(
2630
- label="Duration (seconds)",
2631
- minimum=1.0,
2632
- maximum=10.0,
2633
- value=5.0,
2634
- step=0.1,
2635
- visible=False
2636
- )
2637
-
2638
- ICON_16_9 = """<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
2639
- <rect x="3" y="7" width="18" height="10" rx="2" stroke="currentColor" stroke-width="2"/>
2640
- </svg>"""
2641
-
2642
- ICON_1_1 = """<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
2643
- <rect x="6" y="6" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"/>
2644
- </svg>"""
2645
-
2646
- ICON_9_16 = """<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
2647
- <rect x="7" y="3" width="10" height="18" rx="2" stroke="currentColor" stroke-width="2"/>
2648
- </svg>"""
2649
 
 
 
 
2650
 
2651
  resolution_ui = CameraDropdown(
2652
- choices=[
2653
- {"label": "16:9", "value": "16:9", "icon": ICON_16_9},
2654
- {"label": "1:1", "value": "1:1", "icon": ICON_1_1},
2655
- {"label": "9:16", "value": "9:16", "icon": ICON_9_16},
2656
- ],
2657
- value="16:9",
2658
- title="ابعاد تصویر",
2659
- elem_id="resolution_ui"
2660
  )
2661
-
2662
-
2663
  width = gr.Number(label="Width", value=DEFAULT_1_STAGE_WIDTH, precision=0, visible=False)
2664
  height = gr.Number(label="Height", value=DEFAULT_1_STAGE_HEIGHT, precision=0, visible=False)
2665
-
2666
- camera_ui = CameraDropdown(
2667
- choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES],
2668
- value="No LoRA",
2669
- title="افکت دوربین (LoRA)",
2670
- elem_id="camera_ui",
2671
- )
2672
-
2673
- # Hidden real dropdown (backend value)
2674
- camera_lora = gr.Dropdown(
2675
- label="Camera Control LoRA",
2676
- choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES],
2677
- value="No LoRA",
2678
- visible=False
2679
- )
2680
-
2681
- # دکمه اصلی که کاربر می بیند و فرآیند بهینه سازی را شروع می کند
2682
- enhancer_btn = gr.Button("🤩 ساخت ویدیو", variant="primary", elem_classes="button-gradient", elem_id="enhancer_btn")
2683
 
2684
- # دکمه پنهان که پس از بهینه سازی توسط جاوااسکریپت کلیک می شود
2685
- real_generate_btn = gr.Button("Hidden Generate", visible=False, elem_id="real_generate_btn")
2686
 
 
 
 
2687
 
2688
- camera_ui.change(
2689
- fn=lambda x: x,
2690
- inputs=camera_ui,
2691
- outputs=camera_lora,
2692
- api_visibility="private"
2693
- )
2694
-
2695
- radioanimated_mode.change(
2696
- fn=on_mode_change,
2697
- inputs=radioanimated_mode,
2698
- outputs=[input_video, end_frame],
2699
- api_visibility="private",
2700
- )
2701
 
 
 
 
 
 
 
 
 
2702
 
2703
- duration_ui.change(
2704
- fn=apply_duration,
2705
- inputs=duration_ui,
2706
- outputs=[duration],
2707
- api_visibility="private"
2708
- )
2709
- resolution_ui.change(
2710
- fn=apply_resolution,
2711
- inputs=resolution_ui,
2712
- outputs=[width, height],
2713
- api_visibility="private"
2714
- )
2715
- prompt_ui.change(
2716
- fn=lambda x: x,
2717
- inputs=prompt_ui,
2718
- outputs=prompt,
2719
- api_visibility="private"
2720
- )
2721
-
2722
- # مرحله 1: دکمه قابل مشاهده، جاوا اسکریپت بهینه ساز را اجرا می کند
2723
- enhancer_btn.click(
2724
- fn=None, # هیچ تابع پایتونی اجرا نمی شود
2725
- inputs=[first_frame, prompt, radioanimated_mode],
2726
- js=js_enhancer_and_trigger # فقط جاوااسکریپت اجرا می شود
2727
- )
2728
 
2729
- # مرحله 2: دکمه پنهان، تابع اصلی ساخت ویدیو در پایتون را اجرا می کند
2730
- real_generate_btn.click(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2731
  fn=generate_video,
2732
  inputs=[
2733
- first_frame,
2734
- end_frame,
2735
- prompt, # این prompt حالا توسط جاوااسکریپت بهینه شده است
2736
- duration,
2737
- input_video,
2738
- radioanimated_mode,
2739
- enhance_prompt, # این گزینه دیگر استفاده نمی شود چون بهینه سازی سمت مرورگر است
2740
- seed,
2741
- randomize_seed,
2742
- height,
2743
- width,
2744
- camera_lora,
2745
- audio_input
2746
  ],
2747
  outputs=[output_video]
2748
- ).then(
2749
- fn=lambda: gr.update(visible=True), # بعد از اتمام ساخت، دکمه دانلود ظاهر می شود
 
 
2750
  outputs=[download_btn]
 
 
 
 
 
2751
  )
2752
 
2753
- # اتصال دکمه دانلود به جاوا اسکریپت
2754
- download_btn.click(
2755
- fn=None,
2756
- inputs=[output_video],
2757
- js=js_download_video
2758
- )
2759
-
2760
- # Updated Examples to use Persian modes
2761
  examples_list = [
2762
- [
2763
- "examples/supergirl-2.png",
2764
- "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",
2765
- "Static",
2766
- "16:9",
2767
- "تبدیل تصویر به ویدیو",
2768
- None,
2769
- "examples/supergirl.m4a",
2770
- None,
2771
- ],
2772
- [
2773
- "examples/frame3.png",
2774
- "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.",
2775
- "Zoom In",
2776
- "16:9",
2777
- "تکمیل فریم‌های میانی",
2778
- None,
2779
- None,
2780
- "examples/frame4.png",
2781
- ],
2782
- [
2783
- "examples/supergirl.png",
2784
- "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",
2785
- "No LoRA",
2786
- "16:9",
2787
- "تبدیل تصویر به ویدیو",
2788
- None,
2789
- None,
2790
- None,
2791
- ],
2792
- [
2793
- "examples/highland.png",
2794
- "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.",
2795
- "No LoRA",
2796
- "16:9",
2797
- "تبدیل تصویر به ویدیو",
2798
- None,
2799
- None,
2800
- None,
2801
- ],
2802
- [
2803
- "examples/wednesday.png",
2804
- "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.",
2805
- "Zoom Out",
2806
- "16:9",
2807
- "تبدیل تصویر به ویدیو",
2808
- None,
2809
- None,
2810
- None,
2811
- ],
2812
- [
2813
- "examples/astronaut.png",
2814
- "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.",
2815
- "Static",
2816
- "1:1",
2817
- "تبدیل تصویر به ویدیو",
2818
- None,
2819
- None,
2820
- None,
2821
- ],
2822
  ]
2823
 
2824
- examples_obj = create_examples(
2825
- examples=examples_list,
2826
- fn=generate_video_example,
2827
- inputs=[first_frame, prompt_ui, camera_ui, resolution_ui, radioanimated_mode, input_video, audio_input, end_frame],
2828
- outputs = [output_video],
2829
- label="نمونه‌ها",
2830
- cache_examples=True,
2831
- visible=False
2832
- )
2833
-
2834
  preset_gallery = PresetGallery(
2835
  items=[
2836
  {"thumb": "examples/supergirl-2.png", "label": "تصویر و صدا به ویدیو"},
@@ -2843,35 +2576,13 @@ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
2843
  title="برای شروع روی یکی از نمونه‌ها کلیک کنید",
2844
  )
2845
 
2846
- def on_audio_ui_change(v):
2847
- # Our JS sends "__CLEAR__" when the user presses the X
2848
- if v == "__CLEAR__" or v is None or v == "":
2849
- return None
2850
- # For normal events (uploads), do nothing (keep whatever gr.File already has)
2851
- return gr.update()
2852
-
2853
- audio_ui.change(
2854
- fn=on_audio_ui_change,
2855
- inputs=audio_ui,
2856
- outputs=audio_input,
2857
- api_visibility="private",
2858
- )
2859
-
2860
  preset_gallery.change(
2861
  fn=apply_example,
2862
  inputs=preset_gallery,
2863
  outputs=[
2864
- first_frame,
2865
- prompt_ui,
2866
- camera_ui,
2867
- resolution_ui,
2868
- radioanimated_mode,
2869
- input_video,
2870
- audio_input,
2871
- audio_ui,
2872
- end_frame,
2873
- output_video, # Clears the output video
2874
- download_btn # Hides the download button
2875
  ],
2876
  api_visibility="private",
2877
  )
 
2319
 
2320
 
2321
  ####################################################################################################
2322
+ ### PART 20: Gradio UI Layout & Launch (REVISED & FIXED)
2323
  ####################################################################################################
2324
 
2325
  # JS Function to handle download request via PostMessage
 
2329
  alert("لطفاً ابتدا ویدیو را تولید کنید.");
2330
  return;
2331
  }
2332
+ let fileUrl = typeof video === 'string' ? video : (video && (video.url || video.path));
 
 
 
 
 
 
 
 
 
 
 
2333
  if (fileUrl && !fileUrl.startsWith('http')) {
 
2334
  let cleanPath = fileUrl.startsWith('/') ? fileUrl.substring(1) : fileUrl;
2335
+ fileUrl = window.location.origin + (cleanPath.startsWith('file=') ? "/" + cleanPath : "/file=" + cleanPath);
 
 
 
 
 
 
2336
  }
 
2337
  if (fileUrl) {
2338
  console.log("Sending download request for:", fileUrl);
2339
  window.parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: fileUrl }, '*');
 
2341
  }
2342
  """
2343
 
2344
+ # این تابع جاوااسکریپت فقط مسئول فراخوانی API بهینه ساز است
2345
+ # ورودی ها را از پایتون می گیرد و خروجی رامپت بهینه شده) را به پایتون برمی گرداند
2346
+ js_enhancer_api_call = """
2347
  async (firstFrame, promptText, mode) => {
2348
+ // اگر حالت انتخابی نیازی به بهینه سازی ندارد، پرامپت اصلی را برگردان
 
 
 
 
 
2349
  if (mode !== 'تبدیل تصویر به ویدیو' && mode !== 'تکمیل فریم‌های میانی') {
2350
+ return promptText;
 
2351
  }
2352
 
2353
  // API بهینه ساز به تصویر نیاز دارد
2354
  if (!firstFrame) {
2355
+ // چون این مرحله در میانه راه است، به کاربر اطلاع داده و با پرامپت اصلی ادامه می دهیم
2356
+ console.warn("Enhancement skipped: First frame image is required.");
2357
+ return promptText;
2358
  }
2359
 
 
 
 
 
2360
  try {
 
2361
  const response = await fetch(firstFrame.url);
2362
  const imageBlob = await response.blob();
2363
 
 
2364
  const formData = new FormData();
 
2365
  const imageFile = new File([imageBlob], "image.jpg", { type: "image/jpeg" });
2366
  formData.append('image', imageFile);
2367
  formData.append('prompt', promptText);
2368
  formData.append('is_extension', 'false');
2369
 
 
2370
  const apiResponse = await fetch('https://ezmarynoori-vidtolani.hf.space/api/enhance-animation-prompt', {
2371
  method: 'POST',
2372
  body: formData
2373
  });
2374
 
 
 
2375
  if (!apiResponse.ok) {
2376
+ const errorData = await apiResponse.json();
2377
+ throw new Error(errorData.error || 'خطای ناشناخته از سرور بهینه ساز.');
2378
  }
2379
 
2380
+ const data = await apiResponse.json();
2381
+ console.log("Enhanced Prompt received:", data.animation_prompt);
2382
+ // پرامپت بهینه شده را برای مرحله بعد در گرادیو برگردان
2383
+ return data.animation_prompt;
 
 
 
 
 
 
 
2384
 
2385
  } catch (error) {
2386
+ console.error("Enhancement API Error:", error);
2387
+ // در صورت خطا، پرامپت اصلی را برگردان تا فرآیند ساخت ویدیو متوقف نشود
2388
+ return promptText;
 
 
 
2389
  }
 
 
 
 
2390
  }
2391
  """
2392
 
2393
 
2394
  def apply_example(idx: str):
2395
  idx = int(idx)
 
 
2396
  img, prompt_txt, cam, res, mode, vid, aud, end_img = examples_list[idx]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2397
  return (
2398
+ img or None,
2399
+ prompt_txt,
2400
+ cam,
2401
+ res,
2402
+ mode,
2403
+ gr.update(value=vid or None, visible=(mode == "Motion Control")),
2404
+ aud or None,
2405
+ aud or None,
2406
+ end_img or None,
2407
+ gr.update(value=None),
2408
+ gr.update(visible=False)
2409
  )
2410
 
 
2411
  with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
2412
 
 
2413
  gr.HTML(
2414
  """
2415
  <div style="text-align: center; padding: 20px;">
 
2423
  """
2424
  )
2425
 
2426
+ # State components to pass data between Python and JS
2427
+ # این کامپوننت ها برای کاربر قابل مشاهده نیستند
2428
+ enhanced_prompt_state = gr.State("")
2429
+
2430
  with gr.Column(elem_id="col-container"):
2431
  with gr.Row(elem_id="mode-row"):
 
2432
  radioanimated_mode = RadioAnimated(
2433
  choices=["تبدیل تصویر به ویدیو", "تکمیل فریم‌های میانی"],
2434
  value="تبدیل تصویر به ویدیو",
 
2436
  )
2437
  with gr.Row():
2438
  with gr.Column(elem_id="step-column"):
 
2439
  with gr.Row():
 
2440
  first_frame = gr.Image(
2441
  label="تصویر اول (برای بهینه‌سازی پرامپت الزامی است)",
2442
  type="filepath",
2443
  height=256
2444
  )
 
2445
  end_frame = gr.Image(
2446
  label="تصویر آخر (اختیاری)",
2447
  type="filepath",
2448
  height=256,
2449
  visible=False,
2450
  )
2451
+ input_video = gr.Video(label="Motion Reference Video", height=256, visible=False)
2452
 
2453
+ relocate = gr.HTML(value="", html_template="<div></div>", js_on_load=r"""
 
 
 
 
 
 
 
 
 
 
2454
  (() => {
2455
  function moveIntoFooter() {
2456
+ const promptRoot = document.querySelector("#prompt_ui"); if (!promptRoot) return false;
2457
+ const footer = promptRoot.querySelector(".ds-footer"); if (!footer) return false;
 
 
 
 
2458
  const dur = document.querySelector("#duration_ui .cd-wrap");
2459
  const res = document.querySelector("#resolution_ui .cd-wrap");
2460
  const cam = document.querySelector("#camera_ui .cd-wrap");
 
2461
  if (!dur || !res || !cam) return false;
2462
+ footer.appendChild(dur); footer.appendChild(res); footer.appendChild(cam);
 
 
 
 
2463
  return true;
2464
  }
2465
+ const tick = () => { if (!moveIntoFooter()) requestAnimationFrame(tick); };
 
 
 
2466
  requestAnimationFrame(tick);
2467
  })();
2468
+ """)
 
2469
 
2470
+ prompt_ui = PromptBox(value="این تصویر را با حرکت سینمایی و انیمیشن روان زنده کن", elem_id="prompt_ui")
2471
+ audio_input = gr.File(label="Audio (Optional)", file_types=["audio"], type="filepath", elem_id="audio_input_hidden")
2472
+ audio_ui = AudioDropUpload(target_audio_elem_id="audio_input_hidden", elem_id="audio_ui")
2473
+ prompt = gr.Textbox(label="Prompt", value="این تصویر را با حرکت سینمایی و انیمیشن روان زنده کن", lines=3, max_lines=3, placeholder="حرکت و انیمیشن مورد نظر خود را توصیف کنید...", visible=False)
2474
+ enhance_prompt = gr.Checkbox(label="Enhance Prompt", value=True, visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2475
 
2476
  with gr.Accordion("تنظیمات پیشرفته", open=False, visible=False):
2477
+ seed = gr.Slider(label="سید (Seed)", minimum=0, maximum=MAX_SEED, value=DEFAULT_SEED, step=1)
 
 
 
 
 
 
 
2478
  randomize_seed = gr.Checkbox(label="استفاده از سید تصادفی", value=True)
 
2479
 
2480
  with gr.Column(elem_id="step-column"):
2481
  output_video = gr.Video(label="ویدیوی ساخته شده", autoplay=True, height=512)
 
2482
  with gr.Row():
2483
+ download_btn = gr.Button("📥 دانلود ویدیو", variant="secondary", size="sm", scale=0, visible=False)
 
 
 
 
 
 
2484
 
2485
  with gr.Row(elem_id="controls-row"):
2486
+ duration_ui = CameraDropdown(choices=["3s", "5s", "10s"], value="5s", title="مدت زمان ویدیو", elem_id="duration_ui")
2487
+ duration = gr.Slider(label="Duration (seconds)", minimum=1.0, maximum=10.0, value=5.0, step=0.1, visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2488
 
2489
+ ICON_16_9 = """<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="3" y="7" width="18" height="10" rx="2" stroke="currentColor" stroke-width="2"/></svg>"""
2490
+ ICON_1_1 = """<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"/></svg>"""
2491
+ ICON_9_16 = """<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="7" y="3" width="10" height="18" rx="2" stroke="currentColor" stroke-width="2"/></svg>"""
2492
 
2493
  resolution_ui = CameraDropdown(
2494
+ 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}],
2495
+ value="16:9", title="ابعاد تصویر", elem_id="resolution_ui"
 
 
 
 
 
 
2496
  )
 
 
2497
  width = gr.Number(label="Width", value=DEFAULT_1_STAGE_WIDTH, precision=0, visible=False)
2498
  height = gr.Number(label="Height", value=DEFAULT_1_STAGE_HEIGHT, precision=0, visible=False)
2499
+ camera_ui = CameraDropdown(choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES], value="No LoRA", title="افکت دوربین (LoRA)", elem_id="camera_ui")
2500
+ camera_lora = gr.Dropdown(label="Camera Control LoRA", choices=[name for name, _ in VISIBLE_RUNTIME_LORA_CHOICES], value="No LoRA", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2501
 
2502
+ generate_btn = gr.Button("🤩 ساخت ویدیو", variant="primary", elem_classes="button-gradient")
 
2503
 
2504
+ # توابع کمکی برای به‌روزرسانی UI
2505
+ def lock_ui():
2506
+ return gr.Button(value="⏳ در حال بهینه سازی پرامپت...", interactive=False)
2507
 
2508
+ def re_enable_ui():
2509
+ return gr.Button(value="🤩 ساخت ویدیو", interactive=True)
 
 
 
 
 
 
 
 
 
 
 
2510
 
2511
+ # اتصال رویدادها
2512
+ camera_ui.change(fn=lambda x: x, inputs=camera_ui, outputs=camera_lora, api_visibility="private")
2513
+ radioanimated_mode.change(fn=on_mode_change, inputs=radioanimated_mode, outputs=[input_video, end_frame], api_visibility="private")
2514
+ duration_ui.change(fn=apply_duration, inputs=duration_ui, outputs=[duration], api_visibility="private")
2515
+ resolution_ui.change(fn=apply_resolution, inputs=resolution_ui, outputs=[width, height], api_visibility="private")
2516
+ prompt_ui.change(fn=lambda x: x, inputs=prompt_ui, outputs=prompt, api_visibility="private")
2517
+ audio_ui.change(fn=lambda v: None if v == "__CLEAR__" or not v else gr.update(), inputs=audio_ui, outputs=audio_input, api_visibility="private")
2518
+ download_btn.click(fn=None, inputs=[output_video], js=js_download_video)
2519
 
2520
+ # --- زنجیره اصلی رویداد ساخت ویدیو ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2521
 
2522
+ # 1. وقتی کاربر روی دکمه کلیک می کند، ابتدا UI را قفل کرده و متن دکمه را تغییر می دهیم.
2523
+ # این تابع پایتون هیچ کاری جز آماده سازی UI انجام نمی دهد.
2524
+ generate_btn.click(
2525
+ fn=lock_ui,
2526
+ outputs=[generate_btn]
2527
+ ) \
2528
+ .then(
2529
+ # 2. سپس، تابع جاوااسکریپت را برای بهینه سازی پرامپت فراخوانی می کنیم.
2530
+ # ورودی های آن از کامپوننت های گرادیو گرفته شده و خروجی آن در یک State ذخیره می شود.
2531
+ fn=None,
2532
+ inputs=[first_frame, prompt, radioanimated_mode],
2533
+ outputs=[enhanced_prompt_state],
2534
+ js=js_enhancer_api_call
2535
+ ) \
2536
+ .then(
2537
+ # 3. حالا، تابع اصلی ساخت ویدیو را با پرامپت بهینه شده (که از State خوانده می شود) اجرا می کنیم.
2538
  fn=generate_video,
2539
  inputs=[
2540
+ first_frame, end_frame, enhanced_prompt_state, duration, input_video,
2541
+ radioanimated_mode, enhance_prompt, seed, randomize_seed,
2542
+ height, width, camera_lora, audio_input
 
 
 
 
 
 
 
 
 
 
2543
  ],
2544
  outputs=[output_video]
2545
+ ) \
2546
+ .then(
2547
+ # 4. پس از اتمام ساخت ویدیو، دکمه دانلود را نمایش می دهیم.
2548
+ fn=lambda: gr.update(visible=True),
2549
  outputs=[download_btn]
2550
+ ) \
2551
+ .then(
2552
+ # 5. در نهایت، UI را دوباره فعال می کنیم.
2553
+ fn=re_enable_ui,
2554
+ outputs=[generate_btn]
2555
  )
2556
 
2557
+ # منطق نمونه ها
 
 
 
 
 
 
 
2558
  examples_list = [
2559
+ ["examples/supergirl-2.png", "A fuzzy puppet superhero...", "Static", "16:9", "تبدیل تصویر به ویدیو", None, "examples/supergirl.m4a", None],
2560
+ ["examples/frame3.png", "a woman in a white dress...", "Zoom In", "16:9", "تکمیل فریم‌های میانی", None, None, "examples/frame4.png"],
2561
+ ["examples/supergirl.png", "A fuzzy puppet superhero character...", "No LoRA", "16:9", "تبدیل تصویر به ویدیو", None, None, None],
2562
+ ["examples/highland.png", "Realistic POV selfie-style video...", "No LoRA", "16:9", "تبدیل تصویر به ویدیو", None, None, None],
2563
+ ["examples/wednesday.png", "A cinematic dolly out of Wednesday Addams...", "Zoom Out", "16:9", "تبدیل تصویر به ویدیو", None, None, None],
2564
+ ["examples/astronaut.png", "An astronaut hatches from a fragile egg...", "Static", "1:1", "تبدیل تصویر به ویدیو", None, None, None],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2565
  ]
2566
 
 
 
 
 
 
 
 
 
 
 
2567
  preset_gallery = PresetGallery(
2568
  items=[
2569
  {"thumb": "examples/supergirl-2.png", "label": "تصویر و صدا به ویدیو"},
 
2576
  title="برای شروع روی یکی از نمونه‌ها کلیک کنید",
2577
  )
2578
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2579
  preset_gallery.change(
2580
  fn=apply_example,
2581
  inputs=preset_gallery,
2582
  outputs=[
2583
+ first_frame, prompt_ui, camera_ui, resolution_ui, radioanimated_mode,
2584
+ input_video, audio_input, audio_ui, end_frame,
2585
+ output_video, download_btn
 
 
 
 
 
 
 
 
2586
  ],
2587
  api_visibility="private",
2588
  )