sreepathi-ravikumar commited on
Commit
e0b4b40
·
verified ·
1 Parent(s): be8158b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -53
app.py CHANGED
@@ -488,7 +488,7 @@ def audio_func(id: int, lines, lang: str) -> Tuple[Optional[float], Optional[str
488
  traceback.print_exc()
489
  return None, None
490
  def create_manim_script(problem_data, script_path, audio_path, audio_length):
491
- """Generate Manim script with selective timing adjustment - only equations scale to audio."""
492
 
493
  settings = problem_data.get("video_settings", {
494
  "background_color": "#0f0f23",
@@ -505,6 +505,15 @@ def create_manim_script(problem_data, script_path, audio_path, audio_length):
505
  if not slides:
506
  raise ValueError("No slides provided in input data")
507
 
 
 
 
 
 
 
 
 
 
508
  # Calculate separate durations for different slide types
509
  equation_duration = 0.0
510
  text_title_duration = 0.0
@@ -513,15 +522,14 @@ def create_manim_script(problem_data, script_path, audio_path, audio_length):
513
  slide_duration = float(slide.get("duration", 1.0))
514
  if slide.get("type") == "equation":
515
  equation_duration += slide_duration
516
- else: # text or title
517
  text_title_duration += slide_duration
518
 
519
- # Calculate equation scale factor to fill remaining audio time
520
- target_equation_time = audio_length - text_title_duration
521
 
522
- if equation_duration > 0 and target_equation_time > 0:
523
- equation_scale = target_equation_time / equation_duration
524
- # Prevent extreme scaling (between 0.5x and 2.5x)
525
  equation_scale = max(0.5, min(2.5, equation_scale))
526
  else:
527
  equation_scale = 1.0
@@ -539,7 +547,6 @@ def create_manim_script(problem_data, script_path, audio_path, audio_length):
539
  title_size = settings.get("title_size", 48)
540
 
541
  manim_code = f"""from manim import *
542
-
543
  class GeneratedMathScene(Scene):
544
  def construct(self):
545
  # Scene settings
@@ -552,139 +559,153 @@ class GeneratedMathScene(Scene):
552
  equation_size = {equation_size}
553
  title_size = {title_size}
554
  wrap_width = {wrap_width}
555
- equation_scale = {equation_scale} # Only equations scale to audio
556
-
 
557
  def make_inline_segments(content, color, font, text_size, equation_size):
558
  if not content:
559
  return VGroup()
560
-
561
  segments = content.split("#")
562
  all_lines = []
563
  current_line = []
564
-
 
565
  for segment in segments:
566
  segment = segment.strip()
567
  if not segment:
568
  continue
569
-
 
570
  if segment.startswith("%"):
571
  latex_content = segment[1:]
572
  mob = MathTex(latex_content, color=color, font_size=equation_size)
573
  else:
574
  mob = Text(segment, color=color, font=font, font_size=text_size)
575
-
576
- test_line = current_line + [mob]
577
- test_group = VGroup(*test_line).arrange(RIGHT, buff=0.05)
578
-
579
- if test_group.width > wrap_width and len(current_line) > 0:
 
 
580
  line_group = VGroup(*current_line).arrange(RIGHT, buff=0.05)
581
  all_lines.append(line_group)
582
  current_line = [mob]
 
583
  else:
 
584
  current_line.append(mob)
585
-
 
 
 
 
 
 
 
586
  if current_line:
587
  line_group = VGroup(*current_line).arrange(RIGHT, buff=0.05)
588
  all_lines.append(line_group)
589
-
590
  if not all_lines:
591
  return VGroup()
592
-
593
  final_group = VGroup(*all_lines).arrange(DOWN, aligned_edge=LEFT, buff=0.2)
594
  return final_group
595
-
596
  def make_wrapped_paragraph(content, color, font, font_size, line_spacing=0.2):
597
  lines = []
598
  words = content.split()
599
  current = ""
 
600
  for w in words:
601
  test = w if not current else current + " " + w
602
  test_obj = Text(test, color=color, font=font, font_size=font_size)
603
- if test_obj.width <= wrap_width * 0.9:
 
604
  current = test
605
  else:
606
  if current:
607
  line_obj = Text(current, color=color, font=font, font_size=font_size)
608
  lines.append(line_obj)
609
  current = w
 
610
  if current:
611
  lines.append(Text(current, color=color, font=font, font_size=font_size))
 
612
  if not lines:
613
  return VGroup()
 
614
  first_line = lines[0]
615
  for ln in lines:
616
  ln.align_to(first_line, LEFT)
 
617
  para = VGroup(*lines).arrange(DOWN, aligned_edge=LEFT, buff=line_spacing)
618
  return para
619
-
620
  content_group = VGroup()
621
  current_y = 3.0
622
  line_spacing = 0.8
623
  slides = {slides_repr}
624
-
625
  for idx, slide in enumerate(slides):
626
  obj = None
627
  content = slide.get("content", "")
628
  animation = slide.get("animation", "write_left")
629
  base_duration = slide.get("duration", 1.0)
630
  slide_type = slide.get("type", "text")
631
-
632
- # Apply scale ONLY to equations, not text or title
633
  if slide_type == "equation":
634
  duration = base_duration * equation_scale
635
  else:
636
- duration = base_duration # Keep original timing for text/title
637
-
638
  if slide_type == "title":
639
  obj = make_inline_segments(content, highlight_color, default_font, title_size, equation_size)
640
-
641
  if len(obj) == 0:
642
  obj = Text(content, color=highlight_color, font=default_font, font_size=title_size)
643
-
 
644
  if obj.width > wrap_width:
645
- obj.scale_to_fit_width(wrap_width)
 
646
  obj.move_to(ORIGIN)
647
  self.play(FadeIn(obj), run_time=duration * 0.8)
648
  self.wait(duration * 0.3)
649
  self.play(FadeOut(obj), run_time=duration * 0.3)
650
  continue
651
-
652
  elif slide_type == "text":
653
  obj = make_inline_segments(content, default_color, default_font, text_size, equation_size)
654
-
655
  if len(obj) == 0:
656
  obj = make_wrapped_paragraph(content, default_color, default_font, text_size, line_spacing=0.25)
657
-
 
658
  if obj.width > wrap_width:
659
- obj.scale_to_fit_width(wrap_width)
660
-
661
  elif slide_type == "equation":
662
  eq_content = content
663
- test = MathTex(eq_content, color=default_color, font_size=equation_size)
664
- if test.width > wrap_width:
665
- parts = eq_content.split(" ")
666
- mid = len(parts) // 2
667
- line1 = " ".join(parts[:mid])
668
- line2 = " ".join(parts[mid:])
669
- wrapped_eq = f"{{{{line1}}}} \\\\ {{{{line2}}}}"
670
- obj = MathTex(wrapped_eq, color=default_color, font_size=equation_size)
671
- else:
672
- obj = MathTex(eq_content, color=default_color, font_size=equation_size)
673
  if obj.width > wrap_width:
674
- obj.scale_to_fit_width(wrap_width)
675
-
676
  if obj:
677
  obj.to_edge(LEFT, buff=0.3)
678
  obj.shift(UP * (current_y - obj.height / 2))
 
679
  obj_bottom = obj.get_bottom()[1]
680
-
681
  if obj_bottom < -3.5:
682
  scroll_amount = abs(obj_bottom - (-3.5)) + 0.3
683
  self.play(content_group.animate.shift(UP * scroll_amount), run_time=0.5)
684
  current_y += scroll_amount
685
  obj.shift(UP * scroll_amount)
686
  obj.to_edge(LEFT, buff=0.3)
687
-
688
  if animation == "write_left":
689
  self.play(Write(obj), run_time=duration)
690
  elif animation == "fade_in":
@@ -694,11 +715,11 @@ class GeneratedMathScene(Scene):
694
  self.play(obj.animate.set_color(highlight_color), run_time=duration * 0.4)
695
  else:
696
  self.play(Write(obj), run_time=duration)
697
-
698
  content_group.add(obj)
699
  current_y -= (getattr(obj, "height", 0) + line_spacing)
700
  self.wait(0.3)
701
-
702
  if len(content_group) > 0:
703
  final_box = SurroundingRectangle(content_group[-1], color=highlight_color, buff=0.2)
704
  self.play(Create(final_box), run_time=0.8)
@@ -709,9 +730,12 @@ class GeneratedMathScene(Scene):
709
  with open(script_path, 'w', encoding='utf-8') as f:
710
  f.write(manim_code)
711
  print(f"Generated script at {script_path}")
 
 
712
  print(f"Equation scale factor: {equation_scale:.2f}x")
713
  print(f"Text/Title duration: {text_title_duration:.2f}s (unchanged)")
714
  print(f"Equation duration: {equation_duration:.2f}s -> {equation_duration * equation_scale:.2f}s")
 
715
  except Exception as e:
716
  print(f"Error writing script: {e}")
717
  raise
 
488
  traceback.print_exc()
489
  return None, None
490
  def create_manim_script(problem_data, script_path, audio_path, audio_length):
491
+ """Generate Manim script with proper wrapping and audio-video sync."""
492
 
493
  settings = problem_data.get("video_settings", {
494
  "background_color": "#0f0f23",
 
505
  if not slides:
506
  raise ValueError("No slides provided in input data")
507
 
508
+ # FIX #2: Calculate timing overhead for accurate audio-video sync
509
+ num_slides = len(slides)
510
+ num_titles = sum(1 for s in slides if s.get("type") == "title")
511
+
512
+ overhead_time = (num_slides - num_titles) * 0.3 # wait after each content slide
513
+ overhead_time += num_titles * 0.4 # title animation overhead
514
+ overhead_time += 2.3 # final highlight + wait
515
+ overhead_time += (num_slides / 3) * 0.5 # estimated scroll overhead
516
+
517
  # Calculate separate durations for different slide types
518
  equation_duration = 0.0
519
  text_title_duration = 0.0
 
522
  slide_duration = float(slide.get("duration", 1.0))
523
  if slide.get("type") == "equation":
524
  equation_duration += slide_duration
525
+ else:
526
  text_title_duration += slide_duration
527
 
528
+ # FIX #2: Subtract overhead from available time before calculating scale
529
+ available_time = audio_length - text_title_duration - overhead_time
530
 
531
+ if equation_duration > 0 and available_time > 0:
532
+ equation_scale = available_time / equation_duration
 
533
  equation_scale = max(0.5, min(2.5, equation_scale))
534
  else:
535
  equation_scale = 1.0
 
547
  title_size = settings.get("title_size", 48)
548
 
549
  manim_code = f"""from manim import *
 
550
  class GeneratedMathScene(Scene):
551
  def construct(self):
552
  # Scene settings
 
559
  equation_size = {equation_size}
560
  title_size = {title_size}
561
  wrap_width = {wrap_width}
562
+ equation_scale = {equation_scale}
563
+
564
+ # FIX #1: Improved wrapping function - check width BEFORE arranging
565
  def make_inline_segments(content, color, font, text_size, equation_size):
566
  if not content:
567
  return VGroup()
568
+
569
  segments = content.split("#")
570
  all_lines = []
571
  current_line = []
572
+ current_width = 0.0
573
+
574
  for segment in segments:
575
  segment = segment.strip()
576
  if not segment:
577
  continue
578
+
579
+ # Create mobject
580
  if segment.startswith("%"):
581
  latex_content = segment[1:]
582
  mob = MathTex(latex_content, color=color, font_size=equation_size)
583
  else:
584
  mob = Text(segment, color=color, font=font, font_size=text_size)
585
+
586
+ # FIX #1: Check width BEFORE adding to line
587
+ mob_width = mob.width
588
+ potential_width = current_width + mob_width + (0.05 * len(current_line))
589
+
590
+ if potential_width > wrap_width and len(current_line) > 0:
591
+ # Line is full, save it and start new line
592
  line_group = VGroup(*current_line).arrange(RIGHT, buff=0.05)
593
  all_lines.append(line_group)
594
  current_line = [mob]
595
+ current_width = mob_width
596
  else:
597
+ # Add to current line
598
  current_line.append(mob)
599
+ current_width = potential_width
600
+
601
+ # Safety: If single item exceeds width, scale it down
602
+ if len(current_line) == 1 and mob.width > wrap_width:
603
+ mob.scale_to_fit_width(wrap_width * 0.95)
604
+ current_width = mob.width
605
+
606
+ # Add final line
607
  if current_line:
608
  line_group = VGroup(*current_line).arrange(RIGHT, buff=0.05)
609
  all_lines.append(line_group)
610
+
611
  if not all_lines:
612
  return VGroup()
613
+
614
  final_group = VGroup(*all_lines).arrange(DOWN, aligned_edge=LEFT, buff=0.2)
615
  return final_group
616
+
617
  def make_wrapped_paragraph(content, color, font, font_size, line_spacing=0.2):
618
  lines = []
619
  words = content.split()
620
  current = ""
621
+
622
  for w in words:
623
  test = w if not current else current + " " + w
624
  test_obj = Text(test, color=color, font=font, font_size=font_size)
625
+
626
+ if test_obj.width <= wrap_width * 0.95:
627
  current = test
628
  else:
629
  if current:
630
  line_obj = Text(current, color=color, font=font, font_size=font_size)
631
  lines.append(line_obj)
632
  current = w
633
+
634
  if current:
635
  lines.append(Text(current, color=color, font=font, font_size=font_size))
636
+
637
  if not lines:
638
  return VGroup()
639
+
640
  first_line = lines[0]
641
  for ln in lines:
642
  ln.align_to(first_line, LEFT)
643
+
644
  para = VGroup(*lines).arrange(DOWN, aligned_edge=LEFT, buff=line_spacing)
645
  return para
646
+
647
  content_group = VGroup()
648
  current_y = 3.0
649
  line_spacing = 0.8
650
  slides = {slides_repr}
651
+
652
  for idx, slide in enumerate(slides):
653
  obj = None
654
  content = slide.get("content", "")
655
  animation = slide.get("animation", "write_left")
656
  base_duration = slide.get("duration", 1.0)
657
  slide_type = slide.get("type", "text")
658
+
659
+ # Apply scale ONLY to equations
660
  if slide_type == "equation":
661
  duration = base_duration * equation_scale
662
  else:
663
+ duration = base_duration
664
+
665
  if slide_type == "title":
666
  obj = make_inline_segments(content, highlight_color, default_font, title_size, equation_size)
 
667
  if len(obj) == 0:
668
  obj = Text(content, color=highlight_color, font=default_font, font_size=title_size)
669
+
670
+ # FIX #1: Ensure title fits within screen
671
  if obj.width > wrap_width:
672
+ obj.scale_to_fit_width(wrap_width * 0.95)
673
+
674
  obj.move_to(ORIGIN)
675
  self.play(FadeIn(obj), run_time=duration * 0.8)
676
  self.wait(duration * 0.3)
677
  self.play(FadeOut(obj), run_time=duration * 0.3)
678
  continue
679
+
680
  elif slide_type == "text":
681
  obj = make_inline_segments(content, default_color, default_font, text_size, equation_size)
 
682
  if len(obj) == 0:
683
  obj = make_wrapped_paragraph(content, default_color, default_font, text_size, line_spacing=0.25)
684
+
685
+ # FIX #1: Safety check for text overflow
686
  if obj.width > wrap_width:
687
+ obj.scale_to_fit_width(wrap_width * 0.95)
688
+
689
  elif slide_type == "equation":
690
  eq_content = content
691
+ obj = MathTex(eq_content, color=default_color, font_size=equation_size)
692
+
693
+ # FIX #1: Scale equation instead of splitting by spaces
 
 
 
 
 
 
 
694
  if obj.width > wrap_width:
695
+ obj.scale_to_fit_width(wrap_width * 0.95)
696
+
697
  if obj:
698
  obj.to_edge(LEFT, buff=0.3)
699
  obj.shift(UP * (current_y - obj.height / 2))
700
+
701
  obj_bottom = obj.get_bottom()[1]
 
702
  if obj_bottom < -3.5:
703
  scroll_amount = abs(obj_bottom - (-3.5)) + 0.3
704
  self.play(content_group.animate.shift(UP * scroll_amount), run_time=0.5)
705
  current_y += scroll_amount
706
  obj.shift(UP * scroll_amount)
707
  obj.to_edge(LEFT, buff=0.3)
708
+
709
  if animation == "write_left":
710
  self.play(Write(obj), run_time=duration)
711
  elif animation == "fade_in":
 
715
  self.play(obj.animate.set_color(highlight_color), run_time=duration * 0.4)
716
  else:
717
  self.play(Write(obj), run_time=duration)
718
+
719
  content_group.add(obj)
720
  current_y -= (getattr(obj, "height", 0) + line_spacing)
721
  self.wait(0.3)
722
+
723
  if len(content_group) > 0:
724
  final_box = SurroundingRectangle(content_group[-1], color=highlight_color, buff=0.2)
725
  self.play(Create(final_box), run_time=0.8)
 
730
  with open(script_path, 'w', encoding='utf-8') as f:
731
  f.write(manim_code)
732
  print(f"Generated script at {script_path}")
733
+ print(f"Audio length: {audio_length:.2f}s")
734
+ print(f"Overhead time: {overhead_time:.2f}s")
735
  print(f"Equation scale factor: {equation_scale:.2f}x")
736
  print(f"Text/Title duration: {text_title_duration:.2f}s (unchanged)")
737
  print(f"Equation duration: {equation_duration:.2f}s -> {equation_duration * equation_scale:.2f}s")
738
+ print(f"Expected total: {text_title_duration + (equation_duration * equation_scale) + overhead_time:.2f}s")
739
  except Exception as e:
740
  print(f"Error writing script: {e}")
741
  raise