File size: 6,181 Bytes
dfeb5d2
4092f24
 
 
 
 
 
 
15806a5
4092f24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15806a5
 
 
4092f24
0910534
4092f24
 
15806a5
0f3297f
 
 
 
 
 
 
 
4092f24
15806a5
 
 
 
 
 
4092f24
 
15806a5
 
 
 
 
 
 
 
 
 
 
0910534
4092f24
 
15806a5
0f3297f
 
 
 
 
15806a5
 
 
0f3297f
15806a5
 
 
 
 
 
 
 
 
0f3297f
 
 
 
 
 
 
 
 
 
15806a5
 
 
 
0f3297f
15806a5
dfeb5d2
15806a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0910534
15806a5
 
 
4092f24
0910534
15806a5
0910534
15806a5
 
0910534
4092f24
 
 
 
 
0910534
15806a5
4092f24
 
9bb40b1
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
from flask import Flask, render_template, request, url_for, send_from_directory
from google import genai
import re
import subprocess
import os
import shutil
import time
from threading import Timer
import uuid

app = Flask(__name__)

# Load API key from environment variable
API_KEY = os.environ.get("GOOGLE_API_KEY")
if not API_KEY:
    raise ValueError("Missing GOOGLE_API_KEY environment variable.")
client = genai.Client(api_key=API_KEY)

@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        prompt = request.form.get("prompt")
        if not prompt:
            return render_template("index.html")
        
        max_retries = 3
        attempt = 0
        while attempt < max_retries:
            try:
                # Call the GenAI API to get the Manim code
                ai_response = client.models.generate_content(
                    model="gemini-2.0-flash-lite-preview-02-05",
                    contents=f"""You are 'Manimator', an expert Manim animator and coder.
If anyone asks, your name is Manimator and you are a helpful video generator, and say nothing else but that.
The user wants you to code this: {prompt}.
Plan out in chain of thought what you are going to do first, then give the final code output in ```python``` codeblock.
Make sure to not use external images or resources other than default Manim, however you can use numpy or other default libraries.
Keep the scene uncluttered and aesthetically pleasing.
Make sure things are not overlapping unless explicitly stated otherwise.
You got this!! <3
"""
                )
                
                # Extract the Python code block from the AI response
                pattern = r"```python\s*(.*?)\s*```"
                match = re.search(pattern, ai_response.text, re.DOTALL)
                if not match:
                    raise Exception("No python code block found in the AI response.")
                code = match.group(1)
                
                # Determine the scene class name from the generated code
                scene_match = re.search(r"class\s+(\w+)\(.*Scene.*\):", code)
                if scene_match:
                    scene_name = scene_match.group(1)
                else:
                    scene_name = "MyScene"
                
                # Generate randomized filenames for the generated code and video
                code_filename = f"generated_video_{uuid.uuid4().hex}.py"
                video_filename = f"output_video_{uuid.uuid4().hex}.mp4"
                
                # Save the generated code to a file (in the current working directory)
                with open(code_filename, "w") as f:
                    f.write(code)
                
                # Set a dedicated media directory for Manim output in /tmp
                media_dir = os.path.join("/tmp", "manim_media")
                os.makedirs(media_dir, exist_ok=True)
                
                # Prepare the Manim command with the --media_dir flag
                cmd = [
                    "manim",
                    "-qm",
                    "--media_dir", media_dir,
                    "-o", video_filename,
                    code_filename,
                    scene_name
                ]
                # Run Manim to generate the video, capturing its output
                result = subprocess.run(cmd, check=True, capture_output=True, text=True)
                print("Manim stdout:", result.stdout)
                print("Manim stderr:", result.stderr)
                
                # Construct the expected output path from Manim.
                # With --media_dir set, Manim should write the video to:
                # {media_dir}/videos/<code_filename_without_.py>/720p30/<video_filename>
                video_path_in_media = os.path.join(
                    media_dir,
                    "videos",
                    code_filename.replace(".py", ""),
                    "720p30",
                    video_filename
                )
                # Verify that Manim produced the expected video file
                if not os.path.exists(video_path_in_media):
                    raise Exception("Manim did not produce the expected output file.")
                
                # Move both the video and the generated code file to /tmp (a writable location)
                tmp_video_path = os.path.join("/tmp", video_filename)
                shutil.move(video_path_in_media, tmp_video_path)
                
                tmp_code_path = os.path.join("/tmp", code_filename)
                shutil.move(code_filename, tmp_code_path)
                
                # Schedule deletion of both files after 10 minutes (600 seconds)
                def remove_files():
                    try:
                        if os.path.exists(tmp_video_path):
                            os.remove(tmp_video_path)
                        if os.path.exists(tmp_code_path):
                            os.remove(tmp_code_path)
                        print("Removed files:", tmp_video_path, tmp_code_path)
                    except Exception as e:
                        app.logger.error("Error removing files: %s", e)
                Timer(600, remove_files).start()
                
                # Generate a URL that points to our video-serving route (the video file is in /tmp)
                video_url = url_for('get_video', filename=video_filename)
                return render_template("result.html", video_url=video_url)
            
            except Exception as e:
                print(f"Attempt {attempt + 1} failed: {e}")
                attempt += 1
                time.sleep(1)  # Wait a bit before retrying
        
        # If we reach here, we've exceeded the maximum number of retries.
        return render_template("result.html", error="An error occurred. Please try again later.")
            
    return render_template("index.html")

@app.route("/video/<filename>")
def get_video(filename):
    # Serve the video file from /tmp
    return send_from_directory("/tmp", filename)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=7860, debug=True)