File size: 12,419 Bytes
1645305
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
import gradio as gr
import os
import tempfile
import subprocess
import shutil
import logging
import time
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def get_client():
    return OpenAI(
        api_key=os.environ.get("TOGETHER_API_KEY"),
        base_url="https://api.together.xyz/v1"
    )

AVAILABLE_MODELS = [
    "meta-llama/Llama-3.3-70B-Instruct-Turbo",
    "deepseek-ai/DeepSeek-V3",
    "deepseek-ai/DeepSeek-R1",
    "Qwen/QwQ-32B-Preview",
    "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free",
    "Qwen/Qwen2.5-Coder-32B-Instruct"
]

def generate_manim_code(prompt, model_name, temperature=0.7, max_tokens=8192):
    try:
        client = get_client()
        system_prompt = """

        You are an expert in creating mathematical and physics visualizations using Manim (Mathematical Animation Engine).

        Your task is to convert a text prompt into valid, executable Manim Python code.

        

        IMPORTANT RULES FOR COMPILATION SUCCESS:

        1. Only return valid Python code that works with the latest version of Manim Community edition

        2. Do NOT include any explanations outside of code comments

        3. Use ONLY the Scene class as the base class

        4. Include ALL necessary imports at the top (from manim import *)

        5. Use descriptive variable names that follow Python conventions

        6. Include helpful comments for complex parts of the visualization

        7. The class name MUST be "Screen" - always use this exact name

        8. Always implement the construct method correctly

        9. Ensure all objects are properly added to the scene with self.play() or self.add()

        10. Do not create custom classes other than the main Scene class

        11. Include proper self.wait() calls after animations for better viewing

        12. Check all mathematical expressions are valid LaTeX syntax

        13. Avoid advanced or experimental Manim features that might not be widely available

        14. Keep animations under 20 seconds total for better performance

        15. Ensure all coordinates and dimensions are appropriate for the default canvas size

        16. DO NOT include any backticks (```) or markdown formatting in your response

        

        RESPOND WITH ONLY THE EXECUTABLE PYTHON CODE, NO INTRODUCTION OR EXPLANATION, NO MARKDOWN FORMATTING.

        """
        
        final_prompt = f"Create a Manim visualization that explains: {prompt}"
        
        logger.info(f"Generating code with model: {model_name}")
        
        response = client.chat.completions.create(
            model=model_name,
            temperature=temperature,
            max_tokens=max_tokens,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": final_prompt}
            ]
        )
        
        generated_code = response.choices[0].message.content
        
        # Strip markdown formatting if it appears in the response
        if "```python" in generated_code:
            generated_code = generated_code.split("```python")[1]
            if "```" in generated_code:
                generated_code = generated_code.split("```")[0]
        elif "```" in generated_code:
            generated_code = generated_code.split("```")[1]
            if "```" in generated_code:
                generated_code = generated_code.split("```")[0]
        
        # Remove any additional backticks that might cause syntax errors
        generated_code = generated_code.replace('```', '')
        
        # Ensure code starts with proper import
        if not generated_code.strip().startswith('from manim import'):
            generated_code = 'from manim import *\n\n' + generated_code
            
        return generated_code.strip()
        
    except Exception as e:
        logger.error(f"Error generating code: {e}")
        return f"Error generating code: {str(e)}"

def render_manim_video(code, quality="medium_quality"):
    try:
        temp_dir = tempfile.mkdtemp()
        script_path = os.path.join(temp_dir, "manim_script.py")
        
        with open(script_path, "w") as f:
            f.write(code)
        
        class_name = None
        for line in code.split("\n"):
            if line.startswith("class ") and "Scene" in line:
                class_name = line.split("class ")[1].split("(")[0].strip()
                break
        
        if not class_name:
            return "Error: Could not identify the Scene class in the generated code."
        
        if quality == "high_quality":
            command = ["manim", "-qh", script_path, class_name]
            quality_dir = "1080p60"
        elif quality == "low_quality":
            command = ["manim", "-ql", script_path, class_name]
            quality_dir = "480p15"
        else:
            command = ["manim", "-qm", script_path, class_name]
            quality_dir = "720p30"
        
        logger.info(f"Executing command: {' '.join(command)}")
        
        result = subprocess.run(command, cwd=temp_dir, capture_output=True, text=True)
        
        logger.info(f"Manim stdout: {result.stdout}")
        logger.error(f"Manim stderr: {result.stderr}")
        
        if result.returncode != 0:
            logger.error(f"Manim execution failed: {result.stderr}")
            return f"Error rendering video: {result.stderr}"
        
        media_dir = os.path.join(temp_dir, "media")
        videos_dir = os.path.join(media_dir, "videos")
        
        if not os.path.exists(videos_dir):
            return "Error: No video was generated. Check if Manim is installed correctly."
        
        scene_dirs = [d for d in os.listdir(videos_dir) if os.path.isdir(os.path.join(videos_dir, d))]
        
        if not scene_dirs:
            return "Error: No scene directory found in the output."
        
        scene_dir = max([os.path.join(videos_dir, d) for d in scene_dirs], key=os.path.getctime)
        
        mp4_files = [f for f in os.listdir(os.path.join(scene_dir, quality_dir)) if f.endswith(".mp4")]
        
        if not mp4_files:
            return "Error: No MP4 file was generated."
        
        video_file = max([os.path.join(scene_dir, quality_dir, f) for f in mp4_files], key=os.path.getctime)
        
        output_dir = os.path.join(os.getcwd(), "generated_videos")
        os.makedirs(output_dir, exist_ok=True)
        
        timestamp = int(time.time())
        output_file = os.path.join(output_dir, f"manim_video_{timestamp}.mp4")
        
        shutil.copy2(video_file, output_file)
        
        logger.info(f"Video generated: {output_file}")
        
        return output_file
        
    except Exception as e:
        logger.error(f"Error rendering video: {e}")
        return f"Error rendering video: {str(e)}"
    finally:
        if 'temp_dir' in locals():
            try:
                shutil.rmtree(temp_dir)
            except Exception as e:
                logger.error(f"Error cleaning up temporary directory: {e}")

def placeholder_for_examples(prompt, model, quality):
    code = """

from manim import *



class PythagoreanTheorem(Scene):

    def construct(self):

        # This is placeholder code for examples

        # Creating a right triangle

        triangle = Polygon(

            ORIGIN, 

            RIGHT * 3, 

            UP * 4, 

            color=WHITE

        )

        

        # Adding labels

        a = Text("a", font_size=30).next_to(triangle, DOWN)

        b = Text("b", font_size=30).next_to(triangle, RIGHT)

        c = Text("c", font_size=30).next_to(

            triangle.get_center(), 

            UP + LEFT

        )

        

        # Add to scene

        self.play(Create(triangle))

        self.play(Write(a), Write(b), Write(c))

        

        # Wait at the end

        self.wait(2)

"""
    return code, None, "Example mode: Click 'Generate Video' to actually process this example"

def process_prompt(prompt, model_name, quality="medium_quality"):
    try:
        code = generate_manim_code(prompt, model_name)
        video_path = render_manim_video(code, quality)
        return code, video_path
    except Exception as e:
        logger.error(f"Error processing prompt: {e}")
        return f"Error: {str(e)}", None

def process_prompt_with_status(prompt, model, quality, progress=gr.Progress()):
    try:
        progress(0, desc="Starting...")
        
        progress(0.3, desc="Generating Manim code using AI...")
        code = generate_manim_code(prompt, model)
        
        progress(0.6, desc="Rendering video with Manim (this may take a few minutes)...")
        video_path = render_manim_video(code, quality)
        
        progress(1.0, desc="Complete")
        
        if not video_path or video_path.startswith("Error"):
            status = video_path if video_path else "Error: Failed to generate video."
            return code, None, status
        else:
            status = "Video generated successfully!"
            return code, video_path, status
            
    except Exception as e:
        logger.error(f"Error in processing: {e}")
        return (code if 'code' in locals() else "Error generating code"), None, f"Error: {str(e)}"

def create_interface():
    with gr.Blocks(title="Math & Physics Video Generator") as app:
        gr.Markdown("# Interactive Math & Physics Video Generator")
        gr.Markdown("Generate educational videos from text prompts using AI and Manim")
        
        with gr.Row():
            with gr.Column():
                model_dropdown = gr.Dropdown(
                    choices=AVAILABLE_MODELS,
                    value=AVAILABLE_MODELS[1],
                    label="Select AI Model"
                )
                quality_radio = gr.Radio(
                    choices=["low_quality", "medium_quality", "high_quality"],
                    value="medium_quality",
                    label="Output Quality (affects rendering time)"
                )
                prompt_input = gr.Textbox(
                    placeholder="Enter a mathematical or physics concept to visualize...",
                    label="Prompt",
                    lines=3
                )
                submit_btn = gr.Button("Generate Video", variant="primary")
                
                with gr.Accordion("Generated Manim Code", open=False):
                    code_output = gr.Code(
                        language="python",
                        label="Generated Manim Code",
                        lines=20
                    )
            
            with gr.Column():
                video_output = gr.Video(
                    label="Generated Animation",
                    width="100%",
                    height=500
                )
                status_output = gr.Textbox(
                    label="Status",
                    value="Ready. Enter a prompt and click 'Generate Video'.",
                    interactive=False
                )
        
        submit_btn.click(
            fn=process_prompt_with_status,
            inputs=[prompt_input, model_dropdown, quality_radio],
            outputs=[code_output, video_output, status_output]
        )
        
        gr.Examples(
            examples=[
                ["Explain the Pythagorean theorem", AVAILABLE_MODELS[1], "medium_quality"],
                ["Show how a pendulum works with damping", AVAILABLE_MODELS[1], "medium_quality"],
                ["Demonstrate the concept of derivatives in calculus", AVAILABLE_MODELS[1], "medium_quality"],
                ["Visualize the wave function of a particle in a box", AVAILABLE_MODELS[1], "medium_quality"],
                ["Explain how a capacitor charges and discharges", AVAILABLE_MODELS[1], "medium_quality"]
            ],
            inputs=[prompt_input, model_dropdown, quality_radio],
            fn=placeholder_for_examples
        )
    
    return app

if __name__ == "__main__":
    app = create_interface()
    app.launch(share=True)