theminji commited on
Commit
0ea56ae
·
verified ·
1 Parent(s): cb15fca

Upload 7 files

Browse files
Files changed (7) hide show
  1. Dockerfile +23 -0
  2. app.py +116 -0
  3. requirements.txt +2 -0
  4. static/script.js +8 -0
  5. static/styles.css +149 -0
  6. templates/index.html +33 -0
  7. templates/result.html +29 -0
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image.
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory in the container.
5
+ WORKDIR /app
6
+
7
+ # Copy requirements.txt into the container at /app.
8
+ COPY requirements.txt .
9
+
10
+ # Install any needed packages specified in requirements.txt.
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy the rest of the code into the container at /app.
14
+ COPY . .
15
+
16
+ # Expose the port that Flask uses. (Hugging Face Spaces usually expects port 7860, but for Flask default port 5000 is common)
17
+ EXPOSE 7860
18
+
19
+ # Define environment variable for Flask (optional)
20
+ ENV FLASK_APP=app.py
21
+
22
+ # Run the command to start your app.
23
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, url_for, send_from_directory, after_this_request
2
+ from google import genai
3
+ import re
4
+ import subprocess
5
+ import os
6
+ import shutil
7
+ import time
8
+ from threading import Timer
9
+
10
+ app = Flask(__name__)
11
+
12
+ # Load API key from environment variable
13
+ API_KEY = os.environ.get("GOOGLE_API_KEY")
14
+ if not API_KEY:
15
+ raise ValueError("Missing GOOGLE_API_KEY environment variable.")
16
+ client = genai.Client(api_key=API_KEY)
17
+
18
+ @app.route("/", methods=["GET", "POST"])
19
+ def index():
20
+ if request.method == "POST":
21
+ prompt = request.form.get("prompt")
22
+ if not prompt:
23
+ return render_template("index.html")
24
+
25
+ while True:
26
+ try:
27
+ ai_response = client.models.generate_content(
28
+ model="gemini-2.0-flash-lite-preview-02-05",
29
+ 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.
30
+ The user wants you to code this: {prompt}.
31
+ Plan out in chain of thought what you are going to do first, then give the final code output in ```python``` codeblock.
32
+ Make sure to not use external images or resources other than default Manim.
33
+ Keep the scene uncluttered and aesthetically pleasing.
34
+ You got this!! <3
35
+ """
36
+ )
37
+ except Exception as e:
38
+ print("Error calling GenAI API:", e)
39
+ time.sleep(2)
40
+ continue
41
+
42
+ pattern = r"```python\s*(.*?)\s*```"
43
+ match = re.search(pattern, ai_response.text, re.DOTALL)
44
+ if match:
45
+ code = match.group(1)
46
+ else:
47
+ print("No python code block found in the AI response, retrying...")
48
+ time.sleep(2)
49
+ continue
50
+
51
+ scene_match = re.search(r"class\s+(\w+)\(.*Scene.*\):", code)
52
+ if scene_match:
53
+ scene_name = scene_match.group(1)
54
+ else:
55
+ scene_name = "MyScene"
56
+
57
+ code_filename = "generated_manim_scene.py"
58
+ try:
59
+ with open(code_filename, "w") as f:
60
+ f.write(code)
61
+ except Exception as e:
62
+ print("Error writing code file:", e)
63
+ time.sleep(2)
64
+ continue
65
+
66
+ video_filename = "output_video.mp4"
67
+ cmd = [
68
+ "manim",
69
+ "-qm",
70
+ "-o", video_filename,
71
+ code_filename,
72
+ scene_name
73
+ ]
74
+ try:
75
+ subprocess.run(cmd, check=True)
76
+ except subprocess.CalledProcessError as e:
77
+ print("Error during Manim rendering:", e)
78
+ time.sleep(2)
79
+ continue
80
+
81
+ video_path_in_media = os.path.join("media", "videos", code_filename.replace(".py", ""), "720p30", video_filename)
82
+ static_video_path = os.path.join("static", video_filename)
83
+
84
+ try:
85
+ shutil.move(video_path_in_media, static_video_path)
86
+ except FileNotFoundError:
87
+ print("Manim output file not found. Check your manim code and output location.")
88
+ time.sleep(2)
89
+ continue
90
+ except Exception as e:
91
+ print("Error moving video file:", e)
92
+ time.sleep(2)
93
+ continue
94
+
95
+ video_url = url_for('get_video', filename=video_filename)
96
+ return render_template("result.html", video_url=video_url)
97
+
98
+ return render_template("index.html")
99
+
100
+ @app.route("/video/<filename>")
101
+ def get_video(filename):
102
+ video_path = os.path.join("static", filename)
103
+ response = send_from_directory("static", filename)
104
+
105
+ def remove_file():
106
+ try:
107
+ os.remove(video_path)
108
+ print("Removed video file:", video_path)
109
+ except Exception as e:
110
+ app.logger.error("Error removing video file: %s", e)
111
+ Timer(5, remove_file).start()
112
+
113
+ return response
114
+
115
+ if __name__ == "__main__":
116
+ app.run(debug=False)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ google-genai
2
+ flask
static/script.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener("DOMContentLoaded", function() {
2
+ const form = document.getElementById('promptForm');
3
+ form.addEventListener('submit', function() {
4
+ // Display the loading overlay when the form is submitted.
5
+ document.getElementById("loading").style.display = "flex";
6
+ });
7
+ });
8
+
static/styles.css ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Base resets */
2
+ * {
3
+ box-sizing: border-box;
4
+ margin: 0;
5
+ padding: 0;
6
+ }
7
+
8
+ body {
9
+ font-family: 'Roboto', sans-serif;
10
+ background-color: #0d1117; /* GitHub dark background */
11
+ color: #c9d1d9; /* Light text color */
12
+ min-height: 100vh;
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: center;
16
+ padding: 20px;
17
+ }
18
+
19
+ /* Header */
20
+ header {
21
+ text-align: center;
22
+ margin-top: 20px;
23
+ margin-bottom: 30px;
24
+ }
25
+
26
+ header h1 {
27
+ font-size: 2.5rem;
28
+ font-weight: 700;
29
+ margin-bottom: 5px;
30
+ }
31
+
32
+ header p {
33
+ font-size: 1.1rem;
34
+ color: #8b949e;
35
+ }
36
+
37
+ /* Container Card */
38
+ .container {
39
+ background: #161b22; /* dark card background */
40
+ padding: 2rem;
41
+ border-radius: 8px;
42
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
43
+ width: 100%;
44
+ max-width: 600px;
45
+ margin-bottom: 30px;
46
+ }
47
+
48
+ /* Form Styles */
49
+ form label {
50
+ display: block;
51
+ margin-bottom: 0.5rem;
52
+ font-size: 1.1rem;
53
+ font-weight: 500;
54
+ }
55
+
56
+ form input[type="text"] {
57
+ width: 100%;
58
+ padding: 0.75rem;
59
+ border: 1px solid #30363d;
60
+ border-radius: 4px;
61
+ background: #0d1117;
62
+ color: #c9d1d9;
63
+ margin-bottom: 1rem;
64
+ font-size: 1rem;
65
+ }
66
+
67
+ form input[type="text"]::placeholder {
68
+ color: #8b949e;
69
+ }
70
+
71
+ form button {
72
+ width: 100%;
73
+ padding: 0.75rem;
74
+ background-color: #238636; /* GitHub accent green */
75
+ color: #ffffff;
76
+ border: none;
77
+ border-radius: 4px;
78
+ font-size: 1.1rem;
79
+ font-weight: 600;
80
+ cursor: pointer;
81
+ transition: background 0.3s ease;
82
+ }
83
+
84
+ form button:hover {
85
+ background-color: #2ea043;
86
+ }
87
+
88
+ /* Button Link on Result Page */
89
+ a.btn {
90
+ display: block; /* Make it block level */
91
+ width: fit-content; /* Shrink wrap the button */
92
+ padding: 0.75rem 1rem;
93
+ background-color: #238636;
94
+ color: #ffffff;
95
+ border-radius: 4px;
96
+ text-decoration: none;
97
+ font-size: 1.1rem;
98
+ font-weight: 600;
99
+ transition: background 0.3s ease;
100
+ margin: 20px auto 0 auto; /* Center the button and add top margin */
101
+ }
102
+
103
+ a.btn:hover {
104
+ background-color: #2ea043;
105
+ }
106
+
107
+ /* Error Message */
108
+ .error {
109
+ color: #f85149;
110
+ font-size: 1.1rem;
111
+ margin-bottom: 1rem;
112
+ }
113
+
114
+ /* Loading Overlay Styles */
115
+ .loading-overlay {
116
+ position: fixed;
117
+ top: 0;
118
+ left: 0;
119
+ width: 100%;
120
+ height: 100%;
121
+ background: rgba(13, 17, 23, 0.95);
122
+ z-index: 9999;
123
+ display: none; /* Hidden by default */
124
+ flex-direction: column;
125
+ align-items: center;
126
+ justify-content: center;
127
+ }
128
+
129
+ .loading-overlay p {
130
+ margin-top: 1rem;
131
+ font-size: 1.2rem;
132
+ color: #c9d1d9;
133
+ }
134
+
135
+ /* Spinner Animation */
136
+ .spinner {
137
+ border: 8px solid rgba(255, 255, 255, 0.2);
138
+ border-top: 8px solid #58a6ff;
139
+ border-radius: 50%;
140
+ width: 60px;
141
+ height: 60px;
142
+ animation: spin 1.5s linear infinite;
143
+ }
144
+
145
+ @keyframes spin {
146
+ 0% { transform: rotate(0deg); }
147
+ 100% { transform: rotate(360deg); }
148
+ }
149
+
templates/index.html ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Manimator - AI Video Generator</title>
7
+ <!-- Google Fonts -->
8
+ <link href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
10
+ </head>
11
+ <body>
12
+ <header>
13
+ <h1>Manimator</h1>
14
+ <p>Your AI-powered video generator</p>
15
+ </header>
16
+
17
+ <div class="container">
18
+ <form id="promptForm" method="POST" action="/">
19
+ <label for="prompt">Enter your prompt:</label>
20
+ <input type="text" id="prompt" name="prompt" required autocomplete="off" placeholder="Type something awesome...">
21
+ <button type="submit">Generate Video</button>
22
+ </form>
23
+ </div>
24
+
25
+ <!-- Loading overlay (hidden by default) -->
26
+ <div id="loading" class="loading-overlay">
27
+ <div class="spinner"></div>
28
+ <p>Generating your video... please wait</p>
29
+ </div>
30
+
31
+ <script src="{{ url_for('static', filename='script.js') }}"></script>
32
+ </body>
33
+ </html>
templates/result.html ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Manimator - Your Video is Ready!</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <h1>Manimator</h1>
12
+ <p>Your AI-powered Manim video generator</p>
13
+ </header>
14
+
15
+ <div class="container">
16
+ <h2>Your Generated Video</h2>
17
+ {% if error %}
18
+ <p class="error">{{ error }}</p>
19
+ {% else %}
20
+ <video width="100%" height="auto" controls>
21
+ <source src="{{ video_url }}" type="video/mp4">
22
+ Your browser does not support the video tag.
23
+ </video>
24
+ {% endif %}
25
+ <br>
26
+ <a class="btn" href="{{ url_for('index') }}">Submit Another Prompt</a>
27
+ </div>
28
+ </body>
29
+ </html>