Update app.py
Browse files
app.py
CHANGED
|
@@ -519,7 +519,6 @@ def create_manim_script(problem_data, script_path, audio_path, scale=1):
|
|
| 519 |
title_size = settings.get("title_size", 48)
|
| 520 |
|
| 521 |
manim_code = f"""from manim import *
|
| 522 |
-
|
| 523 |
class GeneratedMathScene(Scene):
|
| 524 |
def construct(self):
|
| 525 |
# Scene settings
|
|
@@ -532,16 +531,46 @@ class GeneratedMathScene(Scene):
|
|
| 532 |
equation_size = {equation_size}
|
| 533 |
title_size = {title_size}
|
| 534 |
wrap_width = {wrap_width}
|
| 535 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
def make_wrapped_paragraph(content, color, font, font_size, line_spacing=0.2):
|
| 537 |
lines = []
|
| 538 |
words = content.split()
|
| 539 |
current = ""
|
| 540 |
-
|
| 541 |
for w in words:
|
| 542 |
test = w if not current else current + " " + w
|
| 543 |
test_obj = Text(test, color=color, font=font, font_size=font_size)
|
| 544 |
-
|
| 545 |
if test_obj.width <= wrap_width * 0.9:
|
| 546 |
current = test
|
| 547 |
else:
|
|
@@ -549,25 +578,21 @@ class GeneratedMathScene(Scene):
|
|
| 549 |
line_obj = Text(current, color=color, font=font, font_size=font_size)
|
| 550 |
lines.append(line_obj)
|
| 551 |
current = w
|
| 552 |
-
|
| 553 |
if current:
|
| 554 |
lines.append(Text(current, color=color, font=font, font_size=font_size))
|
| 555 |
-
|
| 556 |
if not lines:
|
| 557 |
return VGroup()
|
| 558 |
-
|
| 559 |
first_line = lines[0]
|
| 560 |
for ln in lines:
|
| 561 |
ln.align_to(first_line, LEFT)
|
| 562 |
-
|
| 563 |
para = VGroup(*lines).arrange(DOWN, aligned_edge=LEFT, buff=line_spacing)
|
| 564 |
return para
|
| 565 |
-
|
| 566 |
content_group = VGroup()
|
| 567 |
current_y = 3.0
|
| 568 |
line_spacing = 0.8
|
| 569 |
slides = {slides_repr}
|
| 570 |
-
|
| 571 |
for idx, slide in enumerate(slides):
|
| 572 |
obj = None
|
| 573 |
content = slide.get("content", "")
|
|
@@ -575,39 +600,35 @@ class GeneratedMathScene(Scene):
|
|
| 575 |
scalelen = slide.get("duration", 1.0)
|
| 576 |
duration = scalelen * {scale}
|
| 577 |
slide_type = slide.get("type", "text")
|
| 578 |
-
|
| 579 |
if slide_type == "title":
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
obj = Text(
|
| 586 |
-
|
| 587 |
if obj.width > wrap_width:
|
| 588 |
obj.scale_to_fit_width(wrap_width)
|
| 589 |
-
|
| 590 |
obj.move_to(ORIGIN)
|
| 591 |
self.play(FadeIn(obj), run_time=duration * 0.8)
|
| 592 |
self.wait(duration * 0.3)
|
| 593 |
self.play(FadeOut(obj), run_time=duration * 0.3)
|
| 594 |
continue
|
| 595 |
-
|
| 596 |
elif slide_type == "text":
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
text_obj = make_wrapped_paragraph(p, default_color, default_font, text_size)
|
| 609 |
-
inline_items.append(text_obj)
|
| 610 |
-
obj = VGroup(*inline_items).arrange(RIGHT, aligned_edge=LEFT, buff=0.15)
|
| 611 |
elif slide_type == "equation":
|
| 612 |
eq_content = content
|
| 613 |
test = MathTex(eq_content, color=default_color, font_size=equation_size)
|
|
@@ -616,26 +637,25 @@ class GeneratedMathScene(Scene):
|
|
| 616 |
mid = len(parts) // 2
|
| 617 |
line1 = " ".join(parts[:mid])
|
| 618 |
line2 = " ".join(parts[mid:])
|
| 619 |
-
wrapped_eq = f"{{{
|
| 620 |
obj = MathTex(wrapped_eq, color=default_color, font_size=equation_size)
|
| 621 |
else:
|
| 622 |
obj = MathTex(eq_content, color=default_color, font_size=equation_size)
|
| 623 |
-
|
| 624 |
if obj.width > wrap_width:
|
| 625 |
obj.scale_to_fit_width(wrap_width)
|
| 626 |
-
|
| 627 |
if obj:
|
| 628 |
obj.to_edge(LEFT, buff=0.3)
|
| 629 |
obj.shift(UP * (current_y - obj.height / 2))
|
| 630 |
-
|
| 631 |
obj_bottom = obj.get_bottom()[1]
|
|
|
|
| 632 |
if obj_bottom < -3.5:
|
| 633 |
scroll_amount = abs(obj_bottom - (-3.5)) + 0.3
|
| 634 |
self.play(content_group.animate.shift(UP * scroll_amount), run_time=0.5)
|
| 635 |
current_y += scroll_amount
|
| 636 |
obj.shift(UP * scroll_amount)
|
| 637 |
obj.to_edge(LEFT, buff=0.3)
|
| 638 |
-
|
| 639 |
if animation == "write_left":
|
| 640 |
self.play(Write(obj), run_time=duration)
|
| 641 |
elif animation == "fade_in":
|
|
@@ -645,11 +665,11 @@ class GeneratedMathScene(Scene):
|
|
| 645 |
self.play(obj.animate.set_color(highlight_color), run_time=duration * 0.4)
|
| 646 |
else:
|
| 647 |
self.play(Write(obj), run_time=duration)
|
| 648 |
-
|
| 649 |
content_group.add(obj)
|
| 650 |
current_y -= (getattr(obj, "height", 0) + line_spacing)
|
| 651 |
self.wait(0.3)
|
| 652 |
-
|
| 653 |
if len(content_group) > 0:
|
| 654 |
final_box = SurroundingRectangle(content_group[-1], color=highlight_color, buff=0.2)
|
| 655 |
self.play(Create(final_box), run_time=0.8)
|
|
|
|
| 519 |
title_size = settings.get("title_size", 48)
|
| 520 |
|
| 521 |
manim_code = f"""from manim import *
|
|
|
|
| 522 |
class GeneratedMathScene(Scene):
|
| 523 |
def construct(self):
|
| 524 |
# Scene settings
|
|
|
|
| 531 |
equation_size = {equation_size}
|
| 532 |
title_size = {title_size}
|
| 533 |
wrap_width = {wrap_width}
|
| 534 |
+
|
| 535 |
+
def make_inline_segments(content, color, font, text_size, equation_size):
|
| 536 |
+
|
| 537 |
+
if not content:
|
| 538 |
+
return VGroup()
|
| 539 |
+
|
| 540 |
+
# Split by # separator
|
| 541 |
+
segments = content.split("#")
|
| 542 |
+
mobjects = []
|
| 543 |
+
|
| 544 |
+
for segment in segments:
|
| 545 |
+
segment = segment.strip()
|
| 546 |
+
if not segment:
|
| 547 |
+
continue
|
| 548 |
+
|
| 549 |
+
# Check if it's a LaTeX equation (starts with %$ and ends with $)
|
| 550 |
+
if segment.startswith("%$") and segment.endswith("$"):
|
| 551 |
+
# Remove %$ from start and $ from end
|
| 552 |
+
latex_content = segment[2:-1]
|
| 553 |
+
mob = MathTex(latex_content, color=color, font_size=equation_size)
|
| 554 |
+
else:
|
| 555 |
+
# Regular text
|
| 556 |
+
mob = Text(segment, color=color, font=font, font_size=text_size)
|
| 557 |
+
|
| 558 |
+
mobjects.append(mob)
|
| 559 |
+
|
| 560 |
+
if not mobjects:
|
| 561 |
+
return VGroup()
|
| 562 |
+
|
| 563 |
+
# Arrange horizontally with minimal spacing
|
| 564 |
+
inline_group = VGroup(*mobjects).arrange(RIGHT, buff=0.05)
|
| 565 |
+
return inline_group
|
| 566 |
+
|
| 567 |
def make_wrapped_paragraph(content, color, font, font_size, line_spacing=0.2):
|
| 568 |
lines = []
|
| 569 |
words = content.split()
|
| 570 |
current = ""
|
|
|
|
| 571 |
for w in words:
|
| 572 |
test = w if not current else current + " " + w
|
| 573 |
test_obj = Text(test, color=color, font=font, font_size=font_size)
|
|
|
|
| 574 |
if test_obj.width <= wrap_width * 0.9:
|
| 575 |
current = test
|
| 576 |
else:
|
|
|
|
| 578 |
line_obj = Text(current, color=color, font=font, font_size=font_size)
|
| 579 |
lines.append(line_obj)
|
| 580 |
current = w
|
|
|
|
| 581 |
if current:
|
| 582 |
lines.append(Text(current, color=color, font=font, font_size=font_size))
|
|
|
|
| 583 |
if not lines:
|
| 584 |
return VGroup()
|
|
|
|
| 585 |
first_line = lines[0]
|
| 586 |
for ln in lines:
|
| 587 |
ln.align_to(first_line, LEFT)
|
|
|
|
| 588 |
para = VGroup(*lines).arrange(DOWN, aligned_edge=LEFT, buff=line_spacing)
|
| 589 |
return para
|
| 590 |
+
|
| 591 |
content_group = VGroup()
|
| 592 |
current_y = 3.0
|
| 593 |
line_spacing = 0.8
|
| 594 |
slides = {slides_repr}
|
| 595 |
+
|
| 596 |
for idx, slide in enumerate(slides):
|
| 597 |
obj = None
|
| 598 |
content = slide.get("content", "")
|
|
|
|
| 600 |
scalelen = slide.get("duration", 1.0)
|
| 601 |
duration = scalelen * {scale}
|
| 602 |
slide_type = slide.get("type", "text")
|
| 603 |
+
|
| 604 |
if slide_type == "title":
|
| 605 |
+
# Use inline segments for title
|
| 606 |
+
obj = make_inline_segments(content, highlight_color, default_font, title_size, equation_size)
|
| 607 |
+
|
| 608 |
+
# Fallback to simple text if no inline segments
|
| 609 |
+
if len(obj) == 0:
|
| 610 |
+
obj = Text(content, color=highlight_color, font=default_font, font_size=title_size)
|
| 611 |
+
|
| 612 |
if obj.width > wrap_width:
|
| 613 |
obj.scale_to_fit_width(wrap_width)
|
|
|
|
| 614 |
obj.move_to(ORIGIN)
|
| 615 |
self.play(FadeIn(obj), run_time=duration * 0.8)
|
| 616 |
self.wait(duration * 0.3)
|
| 617 |
self.play(FadeOut(obj), run_time=duration * 0.3)
|
| 618 |
continue
|
| 619 |
+
|
| 620 |
elif slide_type == "text":
|
| 621 |
+
# Use inline segments for text
|
| 622 |
+
obj = make_inline_segments(content, default_color, default_font, text_size, equation_size)
|
| 623 |
+
|
| 624 |
+
# Fallback if no inline segments detected
|
| 625 |
+
if len(obj) == 0:
|
| 626 |
+
obj = make_wrapped_paragraph(content, default_color, default_font, text_size, line_spacing=0.25)
|
| 627 |
+
|
| 628 |
+
# Handle width overflow
|
| 629 |
+
if obj.width > wrap_width:
|
| 630 |
+
obj.scale_to_fit_width(wrap_width)
|
| 631 |
+
|
|
|
|
|
|
|
|
|
|
| 632 |
elif slide_type == "equation":
|
| 633 |
eq_content = content
|
| 634 |
test = MathTex(eq_content, color=default_color, font_size=equation_size)
|
|
|
|
| 637 |
mid = len(parts) // 2
|
| 638 |
line1 = " ".join(parts[:mid])
|
| 639 |
line2 = " ".join(parts[mid:])
|
| 640 |
+
wrapped_eq = f"{{{line1}}} \\\\ {{{line2}}}"
|
| 641 |
obj = MathTex(wrapped_eq, color=default_color, font_size=equation_size)
|
| 642 |
else:
|
| 643 |
obj = MathTex(eq_content, color=default_color, font_size=equation_size)
|
|
|
|
| 644 |
if obj.width > wrap_width:
|
| 645 |
obj.scale_to_fit_width(wrap_width)
|
| 646 |
+
|
| 647 |
if obj:
|
| 648 |
obj.to_edge(LEFT, buff=0.3)
|
| 649 |
obj.shift(UP * (current_y - obj.height / 2))
|
|
|
|
| 650 |
obj_bottom = obj.get_bottom()[1]
|
| 651 |
+
|
| 652 |
if obj_bottom < -3.5:
|
| 653 |
scroll_amount = abs(obj_bottom - (-3.5)) + 0.3
|
| 654 |
self.play(content_group.animate.shift(UP * scroll_amount), run_time=0.5)
|
| 655 |
current_y += scroll_amount
|
| 656 |
obj.shift(UP * scroll_amount)
|
| 657 |
obj.to_edge(LEFT, buff=0.3)
|
| 658 |
+
|
| 659 |
if animation == "write_left":
|
| 660 |
self.play(Write(obj), run_time=duration)
|
| 661 |
elif animation == "fade_in":
|
|
|
|
| 665 |
self.play(obj.animate.set_color(highlight_color), run_time=duration * 0.4)
|
| 666 |
else:
|
| 667 |
self.play(Write(obj), run_time=duration)
|
| 668 |
+
|
| 669 |
content_group.add(obj)
|
| 670 |
current_y -= (getattr(obj, "height", 0) + line_spacing)
|
| 671 |
self.wait(0.3)
|
| 672 |
+
|
| 673 |
if len(content_group) > 0:
|
| 674 |
final_box = SurroundingRectangle(content_group[-1], color=highlight_color, buff=0.2)
|
| 675 |
self.play(Create(final_box), run_time=0.8)
|