drvsbrkcn commited on
Commit
84aa242
·
verified ·
1 Parent(s): 1065577

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +299 -192
app.py CHANGED
@@ -280,213 +280,320 @@ def create_interface():
280
 
281
  @spaces.GPU(timeout=120) # 2 minute timeout for ZeroGPU
282
  def generate_commercial(
283
- brand_name: str,
284
- structure_text: str,
285
- script_text: str,
286
- duration_val: int,
287
- voice_style: str,
288
- music_enabled: bool,
289
- video_model: str,
290
- audio_model: str,
291
- vhs_intensity: float,
292
- seed_val: int
293
- ) -> Tuple[str, str, str]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  """
295
- Generate a complete retro commercial with perfect sync.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  """
 
 
 
 
 
 
 
 
 
 
297
  try:
298
- # Update progress
299
- progress_html = """
300
- <div class="progress-info">
301
- <strong>🎬 Generating Commercial...</strong><br>
302
- <div style="margin-top: 0.5rem;">
303
- <div>📝 Generating script with AI...</div>
304
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  </div>
306
- """
307
- yield progress_html, None, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
309
- # Generate script using LLM
310
- generated_script = script_generator.generate_script(
311
- brand=brand_name or "Brand",
312
- structure=structure_text or "Montage → Close-up → Logo",
313
- script_prompt=script_text or "Back to '87",
314
- duration=duration_val,
315
- voice_style=voice_style,
316
- seed=seed_val
317
  )
318
 
319
- # Update progress
320
- progress_html = """
321
- <div class="progress-info">
322
- <strong>🎬 Generating Commercial...</strong><br>
323
- <div style="margin-top: 0.5rem;">
324
- <div>✅ Script generated</div>
325
- <div>🎥 Generating video...</div>
326
- </div>
327
- </div>
328
- """
329
- yield progress_html, None, None, None
330
 
331
- # Create temporary directory
332
- with tempfile.TemporaryDirectory() as tmpdir:
333
- # Generate video
334
- video_prompt = f"{structure_text}. {script_text}. 1980s commercial, VHS texture, soft lighting, bold retro titles, 4:3, brand {brand_name}"
335
-
336
- # Calculate optimal frame count
337
- num_frames = min(sync_manager.get_optimal_frame_count(duration_val, DEFAULT_FPS), 16) # Cap for ZeroGPU
338
-
339
- clip = synth_t2v(
340
- prompt=video_prompt,
341
- seed=seed_val,
342
- num_frames=num_frames,
343
- fps=DEFAULT_FPS,
344
- device=DEVICE,
345
- model_name=video_model
 
 
 
 
346
  )
347
-
348
- # Save raw video
349
- raw_video_path = os.path.join(tmpdir, "raw.mp4")
350
- clip.write_videofile(
351
- raw_video_path,
352
- fps=DEFAULT_FPS,
353
- codec='libx264',
354
- audio=False,
355
- verbose=False,
356
- logger=None
357
  )
358
 
359
- # Apply retro filters
360
- retro_video_path = os.path.join(tmpdir, "retro.mp4")
361
- apply_retro_filters(raw_video_path, retro_video_path, intensity=vhs_intensity)
362
-
363
- # Update progress
364
- progress_html = """
365
- <div class="progress-info">
366
- <strong>🎬 Generating Commercial...</strong><br>
367
- <div style="margin-top: 0.5rem;">
368
- <div>✅ Script generated</div>
369
- <div>✅ Video generated</div>
370
- <div>🎤 Generating audio...</div>
371
- </div>
372
- </div>
373
- """
374
- yield progress_html, None, None, None
375
-
376
- # Generate audio
377
- voiceover_text = " ".join([seg.text for seg in generated_script.segments])
378
- sr_voice, wav_voice = synth_voice(
379
- text=voiceover_text,
380
- voice_prompt=voice_style,
381
- model_name=audio_model,
382
- device=DEVICE
383
  )
384
 
385
- # Add background music if requested
386
- if music_enabled:
387
- sr_music, wav_music = retro_bed(clip.duration)
388
- sr_final, stereo_audio = mix_to_stereo(
389
- sr_voice, wav_voice, sr_music, wav_music, bed_gain=0.3
390
- )
391
- else:
392
- sr_final = sr_voice
393
- stereo_audio = np.stack([wav_voice, wav_voice], axis=1)
394
-
395
- # Save audio
396
- audio_path = os.path.join(tmpdir, "audio.wav")
397
- write_wav(audio_path, sr_final, stereo_audio)
398
-
399
- # Update progress
400
- progress_html = """
401
- <div class="progress-info">
402
- <strong>🎬 Generating Commercial...</strong><br>
403
- <div style="margin-top: 0.5rem;">
404
- <div>✅ Script generated</div>
405
- <div>✅ Video generated</div>
406
- <div>✅ Audio generated</div>
407
- <div>🔄 Synchronizing audio and video...</div>
408
- </div>
409
- </div>
410
- """
411
- yield progress_html, None, None, None
412
-
413
- # Synchronize audio and video
414
- # Clean brand name for filename (remove spaces and special characters)
415
- clean_brand = "".join(c for c in brand_name if c.isalnum() or c in (' ', '-', '_')).rstrip()
416
- clean_brand = clean_brand.replace(' ', '_')
417
- if not clean_brand:
418
- clean_brand = "commercial"
419
- final_video_path = os.path.join(tmpdir, f"{clean_brand}_commercial.mp4")
420
-
421
- try:
422
- sync_manager.synchronize_media(
423
- video_path=retro_video_path,
424
- audio_path=audio_path,
425
- output_path=final_video_path,
426
- prefer_audio_duration=True
427
- )
428
- except Exception as e:
429
- logger.error(f"Sync failed, using simple mux: {e}")
430
- # Fallback to simple muxing
431
- from utils_video import mux_audio
432
- mux_audio(retro_video_path, audio_path, final_video_path)
433
-
434
- # Validate sync and file existence
435
- if os.path.exists(final_video_path):
436
- is_synced, sync_diff = sync_manager.validate_sync(final_video_path, final_video_path)
437
- else:
438
- logger.error("Final video file was not created")
439
- is_synced, sync_diff = False, float('inf')
440
-
441
- # Format script output
442
- script_lines = []
443
- for i, segment in enumerate(generated_script.segments, 1):
444
- script_lines.append(f"{i}. {segment.timing_marker} {segment.text}")
445
-
446
- script_output = "\n".join(script_lines) + f"\n\nTAGLINE: {generated_script.tagline}"
447
-
448
- # Final progress
449
- sync_status = "✅ Perfect sync" if is_synced else f"⚠️ Sync diff: {sync_diff:.3f}s"
450
- progress_html = f"""
451
- <div class="progress-info">
452
- <strong>🎉 Commercial Complete!</strong><br>
453
- <div style="margin-top: 0.5rem;">
454
- <div>✅ Script generated ({generated_script.word_count} words)</div>
455
- <div>✅ Video generated ({num_frames} frames)</div>
456
- <div>✅ Audio generated ({len(stereo_audio)/sr_final:.1f}s)</div>
457
- <div>{sync_status}</div>
458
- </div>
459
- </div>
460
- """
461
-
462
- yield progress_html, final_video_path, script_output, final_video_path
463
 
464
- except Exception as e:
465
- logger.error(f"Commercial generation failed: {e}")
466
- logger.error(f"Traceback: {traceback.format_exc()}")
467
- error_html = f"""
468
- <div class="error-info">
469
- <strong>❌ Generation Failed</strong><br>
470
- <div style="margin-top: 0.5rem; color: #666;">
471
- Error: {str(e)}<br>
472
- Please try again with different parameters or check the logs.
473
- </div>
474
- </div>
475
- """
476
- yield error_html, None, None, None
477
 
478
- # Connect event handlers
479
- roll_btn.click(
480
- roll_script_suggestion,
481
- inputs=[structure, seed],
482
- outputs=[script_prompt]
483
- )
484
-
485
- generate_btn.click(
486
- generate_commercial,
487
- inputs=[
488
- brand, structure, script_prompt, duration, voice, music,
489
- model_video, model_audio, vhs_intensity, seed
490
  ],
491
  outputs=[progress_info, output_video, output_script, download_btn]
492
  )
 
280
 
281
  @spaces.GPU(timeout=120) # 2 minute timeout for ZeroGPU
282
  def generate_commercial(
283
+ brand_name: str,
284
+ structure_text: str,
285
+ script_text: str,
286
+ duration_val: int,
287
+ voice_style: str,
288
+ music_enabled: bool,
289
+ video_model: str,
290
+ audio_model: str,
291
+ vhs_intensity: float,
292
+ seed_val: int
293
+ ) -> Tuple[str, str, str]:
294
+ """
295
+ Generate a complete retro commercial with perfect sync.
296
+ """
297
+ try:
298
+ # Update progress
299
+ progress_html = """
300
+ <div class="progress-info">
301
+ <strong>🎬 Generating Commercial...</strong><br>
302
+ <div style="margin-top: 0.5rem;">
303
+ <div>📝 Generating script with AI...</div>
304
+ </div>
305
+ </div>
306
+ """
307
+ yield progress_html, None, None, None
308
+
309
+ # Generate script using LLM
310
+ generated_script = script_generator.generate_script(
311
+ brand=brand_name or "Brand",
312
+ structure=structure_text or "Montage → Close-up → Logo",
313
+ script_prompt=script_text or "Back to '87",
314
+ duration=duration_val,
315
+ voice_style=voice_style,
316
+ seed=seed_val
317
+ )
318
+
319
+ # Update progress
320
+ progress_html = """
321
+ <div class="progress-info">
322
+ <strong>🎬 Generating Commercial...</strong><br>
323
+ <div style="margin-top: 0.5rem;">
324
+ <div>✅ Script generated</div>
325
+ <div>🎥 Generating video...</div>
326
+ </div>
327
+ </div>
328
+ """
329
+ yield progress_html, None, None, None
330
+
331
+ # Create temporary directory
332
+ with tempfile.TemporaryDirectory() as tmpdir:
333
+ # Generate video
334
+ video_prompt = f"{structure_text}. {script_text}. 1980s commercial, VHS texture, soft lighting, bold retro titles, 4:3, brand {brand_name}"
335
+
336
+ # Calculate optimal frame count
337
+ num_frames = min(sync_manager.get_optimal_frame_count(duration_val, DEFAULT_FPS), 16) # Cap for ZeroGPU
338
+
339
+ clip = synth_t2v(
340
+ prompt=video_prompt,
341
+ seed=seed_val,
342
+ num_frames=num_frames,
343
+ fps=DEFAULT_FPS,
344
+ device=DEVICE,
345
+ model_name=video_model
346
+ )
347
+
348
+ # Save raw video
349
+ raw_video_path = os.path.join(tmpdir, "raw.mp4")
350
+ clip.write_videofile(
351
+ raw_video_path,
352
+ fps=DEFAULT_FPS,
353
+ codec='libx264',
354
+ audio=False,
355
+ verbose=False,
356
+ logger=None
357
+ )
358
+
359
+ # Apply retro filters
360
+ retro_video_path = os.path.join(tmpdir, "retro.mp4")
361
+ apply_retro_filters(raw_video_path, retro_video_path, intensity=vhs_intensity)
362
+
363
+ # Update progress
364
+ progress_html = """
365
+ <div class="progress-info">
366
+ <strong>🎬 Generating Commercial...</strong><br>
367
+ <div style="margin-top: 0.5rem;">
368
+ <div>✅ Script generated</div>
369
+ <div>✅ Video generated</div>
370
+ <div>🎤 Generating audio...</div>
371
+ </div>
372
+ </div>
373
  """
374
+ yield progress_html, None, None, None
375
+
376
+ # Generate audio
377
+ voiceover_text = " ".join([seg.text for seg in generated_script.segments])
378
+ sr_voice, wav_voice = synth_voice(
379
+ text=voiceover_text,
380
+ voice_prompt=voice_style,
381
+ model_name=audio_model,
382
+ device=DEVICE
383
+ )
384
+
385
+ # Add background music if requested
386
+ if music_enabled:
387
+ sr_music, wav_music = retro_bed(clip.duration)
388
+ sr_final, stereo_audio = mix_to_stereo(
389
+ sr_voice, wav_voice, sr_music, wav_music, bed_gain=0.3
390
+ )
391
+ else:
392
+ sr_final = sr_voice
393
+ stereo_audio = np.stack([wav_voice, wav_voice], axis=1)
394
+
395
+ # Save audio
396
+ audio_path = os.path.join(tmpdir, "audio.wav")
397
+ write_wav(audio_path, sr_final, stereo_audio)
398
+
399
+ # Update progress
400
+ progress_html = """
401
+ <div class="progress-info">
402
+ <strong>🎬 Generating Commercial...</strong><br>
403
+ <div style="margin-top: 0.5rem;">
404
+ <div>✅ Script generated</div>
405
+ <div>✅ Video generated</div>
406
+ <div>✅ Audio generated</div>
407
+ <div>🔄 Synchronizing audio and video...</div>
408
+ </div>
409
+ </div>
410
  """
411
+ yield progress_html, None, None, None
412
+
413
+ # Synchronize audio and video
414
+ # Clean brand name for filename (remove spaces and special characters)
415
+ clean_brand = "".join(c for c in brand_name if c.isalnum() or c in (' ', '-', '_')).rstrip()
416
+ clean_brand = clean_brand.replace(' ', '_')
417
+ if not clean_brand:
418
+ clean_brand = "commercial"
419
+ final_video_path = os.path.join(tmpdir, f"{clean_brand}_commercial.mp4")
420
+
421
  try:
422
+ sync_manager.synchronize_media(
423
+ video_path=retro_video_path,
424
+ audio_path=audio_path,
425
+ output_path=final_video_path,
426
+ prefer_audio_duration=True
427
+ )
428
+ except Exception as e:
429
+ logger.error(f"Sync failed, using simple mux: {e}")
430
+ # Fallback to simple muxing
431
+ from utils_video import mux_audio
432
+ mux_audio(retro_video_path, audio_path, final_video_path)
433
+
434
+ # Validate sync and file existence
435
+ if os.path.exists(final_video_path):
436
+ is_synced, sync_diff = sync_manager.validate_sync(final_video_path, final_video_path)
437
+ else:
438
+ logger.error("Final video file was not created")
439
+ is_synced, sync_diff = False, float('inf')
440
+
441
+ # Format script output
442
+ script_lines = []
443
+ for i, segment in enumerate(generated_script.segments, 1):
444
+ script_lines.append(f"{i}. {segment.timing_marker} {segment.text}")
445
+
446
+ script_output = "\n".join(script_lines) + f"\n\nTAGLINE: {generated_script.tagline}"
447
+
448
+ # Final progress
449
+ sync_status = "✅ Perfect sync" if is_synced else f"⚠️ Sync diff: {sync_diff:.3f}s"
450
+ progress_html = f"""
451
+ <div class="progress-info">
452
+ <strong>🎉 Commercial Complete!</strong><br>
453
+ <div style="margin-top: 0.5rem;">
454
+ <div>✅ Script generated ({generated_script.word_count} words)</div>
455
+ <div>✅ Video generated ({num_frames} frames)</div>
456
+ <div>✅ Audio generated ({len(stereo_audio)/sr_final:.1f}s)</div>
457
+ <div>{sync_status}</div>
458
  </div>
459
+ </div>
460
+ """
461
+
462
+ yield progress_html, final_video_path, script_output, final_video_path
463
+
464
+ except Exception as e:
465
+ logger.error(f"Commercial generation failed: {e}")
466
+ logger.error(f"Traceback: {traceback.format_exc()}")
467
+ error_html = f"""
468
+ <div class="error-info">
469
+ <strong>❌ Generation Failed</strong><br>
470
+ <div style="margin-top: 0.5rem; color: #666;">
471
+ Error: {str(e)}<br>
472
+ Please try again with different parameters or check the logs.
473
+ </div>
474
+ </div>
475
+ """
476
+ yield error_html, None, None, None
477
+
478
+ def create_interface():
479
+ """Create the Gradio interface."""
480
+ with gr.Blocks(
481
+ title="EceMotion Pictures",
482
+ theme=gr.themes.Soft(),
483
+ css="""
484
+ .progress-info {
485
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
486
+ color: white;
487
+ padding: 1rem;
488
+ border-radius: 10px;
489
+ margin: 1rem 0;
490
+ }
491
+ .error-info {
492
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
493
+ color: white;
494
+ padding: 1rem;
495
+ border-radius: 10px;
496
+ margin: 1rem 0;
497
+ }
498
+ """
499
+ ) as demo:
500
+ gr.Markdown("""
501
+ # 🎬 EceMotion Pictures
502
+ ## AI-Powered 1980s Style Commercial Generator
503
+
504
+ Create authentic retro commercials with perfect audio-video synchronization!
505
+ """)
506
+
507
+ with gr.Row():
508
+ with gr.Column(scale=1):
509
+ gr.Markdown("### Commercial Setup")
510
 
511
+ brand = gr.Textbox(
512
+ label="Brand Name",
513
+ placeholder="Enter your brand name...",
514
+ value="EceMotion Pictures"
 
 
 
 
515
  )
516
 
517
+ structure = gr.Dropdown(
518
+ label="Commercial Structure",
519
+ choices=[
520
+ "Montage Close-up → Logo stinger",
521
+ "Problem → Solution → Call to action",
522
+ "Story Product reveal → Tagline",
523
+ "Before/After Benefits → Brand"
524
+ ],
525
+ value="Montage → Close-up → Logo stinger"
526
+ )
 
527
 
528
+ script_prompt = gr.Textbox(
529
+ label="Script Hook",
530
+ placeholder="Enter your script prompt...",
531
+ value="Remember when technology was simple?"
532
+ )
533
+
534
+ duration = gr.Slider(
535
+ label="Duration (seconds)",
536
+ minimum=5,
537
+ maximum=15,
538
+ value=10,
539
+ step=1
540
+ )
541
+
542
+ with gr.Row():
543
+ voice = gr.Dropdown(
544
+ label="Voice Style",
545
+ choices=["Announcer '80s", "Friendly '80s", "Dramatic '80s", "Upbeat '80s"],
546
+ value="Announcer '80s"
547
  )
548
+ music = gr.Checkbox(label="Background Music", value=True)
549
+
550
+ with gr.Accordion("Advanced Settings", open=False):
551
+ model_video = gr.Dropdown(
552
+ label="Video Model",
553
+ choices=["damo-vilab/text-to-video-ms-1.7b", "THUDM/CogVideoX-5b"],
554
+ value="damo-vilab/text-to-video-ms-1.7b"
 
 
 
555
  )
556
 
557
+ model_audio = gr.Dropdown(
558
+ label="Audio Model",
559
+ choices=["parler-tts/parler-tts-mini-v1", "SWivid/F5-TTS"],
560
+ value="parler-tts/parler-tts-mini-v1"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
  )
562
 
563
+ vhs_intensity = gr.Slider(
564
+ label="VHS Effect Intensity",
565
+ minimum=0.0,
566
+ maximum=1.0,
567
+ value=0.5,
568
+ step=0.1
569
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
 
571
+ seed = gr.Number(label="Random Seed", value=42, precision=0)
572
+
573
+ with gr.Row():
574
+ roll_btn = gr.Button("🎲 Roll Script", variant="secondary")
575
+ generate_btn = gr.Button("🎬 Generate Commercial", variant="primary")
576
+
577
+ with gr.Column(scale=2):
578
+ gr.Markdown("### Generated Commercial")
579
+
580
+ progress_info = gr.HTML()
581
+ output_video = gr.Video(label="Commercial Preview")
582
+ output_script = gr.Textbox(label="Generated Script", lines=10)
583
+ download_btn = gr.DownloadButton("📥 Download Commercial", visible=False)
584
 
585
+ # Connect event handlers
586
+ roll_btn.click(
587
+ roll_script_suggestion,
588
+ inputs=[structure, seed],
589
+ outputs=[script_prompt]
590
+ )
591
+
592
+ generate_btn.click(
593
+ generate_commercial,
594
+ inputs=[
595
+ brand, structure, script_prompt, duration, voice, music,
596
+ model_video, model_audio, vhs_intensity, seed
597
  ],
598
  outputs=[progress_info, output_video, output_script, download_btn]
599
  )