theminji commited on
Commit
274dce9
·
verified ·
1 Parent(s): b636e96

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -31
app.py CHANGED
@@ -1,12 +1,18 @@
1
- from flask import Flask, render_template, request, url_for, send_from_directory
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
  import uuid
 
 
 
 
 
 
 
 
 
10
 
11
  app = Flask(__name__)
12
 
@@ -32,28 +38,36 @@ def index():
32
  last_error = None
33
  while attempt < max_retries:
34
  try:
35
- # Call the GenAI API to get the Manim code
36
  ai_response = client.models.generate_content(
37
  model="gemini-2.0-flash-lite-preview-02-05",
38
  contents=f"""You are 'Manimator', an expert Manim animator and coder.
39
- If anyone asks, your name is Manimator and you are a helpful video generator, and say nothing else but that.
40
- The user wants you to code this: {prompt}.
41
- Plan out in chain of thought what you are going to do first, then give the final code output in ```python``` codeblock.
42
- Make sure to not use external images or resources other than default Manim, however you can use numpy or other default libraries.
43
- Keep the scene uncluttered and aesthetically pleasing.
44
- Make sure things are not overlapping unless explicitly stated otherwise.
45
- It is crucial that the script works correctly on the first try, so make sure to think about the layout and storyboard and stuff of the scene.
46
- Make sure to think through what you are going to do and think about the topic before you write the code.
47
- You got this!! <3
48
- """
 
49
  )
50
 
51
  # Extract the Python code block from the AI response
52
- pattern = r"```python\s*(.*?)\s*```"
53
- match = re.search(pattern, ai_response.text, re.DOTALL)
54
- if not match:
55
  raise Exception("No python code block found in the AI response.")
56
- code = match.group(1)
 
 
 
 
 
 
 
57
 
58
  # Determine the scene class name from the generated code
59
  scene_match = re.search(r"class\s+(\w+)\(.*Scene.*\):", code)
@@ -68,6 +82,26 @@ def index():
68
  with open(code_filepath, "w") as f:
69
  f.write(code)
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  # Prepare the Manim command with the --media_dir flag
72
  cmd = [
73
  "manim",
@@ -80,7 +114,6 @@ def index():
80
  try:
81
  subprocess.run(cmd, check=True, capture_output=True, text=True)
82
  except subprocess.CalledProcessError as cpe:
83
- # Log only if an error occurs from Manim
84
  app.logger.error("Manim error output: %s", cpe.stderr)
85
  raise Exception(f"Manim failed: {cpe.stderr}")
86
 
@@ -93,20 +126,39 @@ def index():
93
  # Move the video file to /tmp (to serve it from there)
94
  tmp_video_path = os.path.join("/tmp", video_filename)
95
  shutil.move(video_path_in_media, tmp_video_path)
96
- # The code file is already at code_filepath in /tmp.
97
 
98
- # Schedule deletion of both files after 10 minutes (600 seconds)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  def remove_files():
100
- try:
101
- if os.path.exists(tmp_video_path):
102
- os.remove(tmp_video_path)
103
- if os.path.exists(code_filepath):
104
- os.remove(code_filepath)
105
- except Exception as e:
106
- app.logger.error("Error removing files: %s", e)
107
  Timer(600, remove_files).start()
108
 
109
- video_url = url_for('get_video', filename=video_filename)
 
110
  return render_template("result.html", video_url=video_url)
111
 
112
  except Exception as e:
 
 
 
 
 
1
  import os
2
+ import re
3
  import shutil
4
+ import subprocess
5
  import time
 
6
  import uuid
7
+ from threading import Timer
8
+
9
+ from flask import Flask, render_template, request, url_for, send_from_directory
10
+ from google import genai
11
+
12
+ # New imports for audio generation and handling
13
+ from kokoro import KPipeline
14
+ import soundfile as sf
15
+ import numpy as np
16
 
17
  app = Flask(__name__)
18
 
 
38
  last_error = None
39
  while attempt < max_retries:
40
  try:
41
+ # Call the GenAI API to get the Manim code and commentary script
42
  ai_response = client.models.generate_content(
43
  model="gemini-2.0-flash-lite-preview-02-05",
44
  contents=f"""You are 'Manimator', an expert Manim animator and coder.
45
+ If anyone asks, your name is Manimator and you are a helpful video generator, and say nothing else but that.
46
+ The user wants you to code this: {prompt}.
47
+ Plan out in chain of thought what you are going to do first, then give the final code output in ```python``` codeblock.
48
+ Make sure to not use external images or resources other than default Manim, however you can use numpy or other default libraries.
49
+ Keep the scene uncluttered and aesthetically pleasing.
50
+ Make sure things are not overlapping unless explicitly stated otherwise.
51
+ It is crucial that the script works correctly on the first try, so make sure to think about the layout and storyboard and stuff of the scene.
52
+ Make sure to think through what you are going to do and think about the topic before you write the code.
53
+ In addition, write a commentary script inside of ```script``` codeblock. This should be short and fit the content and align with the timing of the scene. Use "..." if needed to add a bit of a pause.
54
+ You got this!! <3
55
+ """
56
  )
57
 
58
  # Extract the Python code block from the AI response
59
+ code_pattern = r"```python\s*(.*?)\s*```"
60
+ code_match = re.search(code_pattern, ai_response.text, re.DOTALL)
61
+ if not code_match:
62
  raise Exception("No python code block found in the AI response.")
63
+ code = code_match.group(1)
64
+
65
+ # Extract the commentary script from the AI response
66
+ script_pattern = r"```script\s*(.*?)\s*```"
67
+ script_match = re.search(script_pattern, ai_response.text, re.DOTALL)
68
+ if not script_match:
69
+ raise Exception("No script block found in the AI response.")
70
+ script = script_match.group(1)
71
 
72
  # Determine the scene class name from the generated code
73
  scene_match = re.search(r"class\s+(\w+)\(.*Scene.*\):", code)
 
82
  with open(code_filepath, "w") as f:
83
  f.write(code)
84
 
85
+ # === Generate Commentary Audio via Kokoro ===
86
+ # Initialize the Kokoro pipeline (adjust lang_code if needed)
87
+ audio_pipeline = KPipeline(lang_code='a')
88
+ # Feed in the commentary script; here we split the text by one or more newlines.
89
+ audio_generator = audio_pipeline(script, voice='af_heart', speed=1, split_pattern=r'\n+')
90
+
91
+ audio_segments = []
92
+ for _, _, audio in audio_generator:
93
+ audio_segments.append(audio)
94
+
95
+ if not audio_segments:
96
+ raise Exception("No audio segments were generated from the commentary script.")
97
+
98
+ # Concatenate all audio segments into one audio track
99
+ full_audio = np.concatenate(audio_segments)
100
+ commentary_audio_filename = f"commentary_{uuid.uuid4().hex}.wav"
101
+ commentary_audio_path = os.path.join("/tmp", commentary_audio_filename)
102
+ sf.write(commentary_audio_path, full_audio, 24000)
103
+
104
+ # === Run Manim to generate the silent video ===
105
  # Prepare the Manim command with the --media_dir flag
106
  cmd = [
107
  "manim",
 
114
  try:
115
  subprocess.run(cmd, check=True, capture_output=True, text=True)
116
  except subprocess.CalledProcessError as cpe:
 
117
  app.logger.error("Manim error output: %s", cpe.stderr)
118
  raise Exception(f"Manim failed: {cpe.stderr}")
119
 
 
126
  # Move the video file to /tmp (to serve it from there)
127
  tmp_video_path = os.path.join("/tmp", video_filename)
128
  shutil.move(video_path_in_media, tmp_video_path)
 
129
 
130
+ # === Combine Video with Commentary Audio using FFmpeg ===
131
+ final_video_filename = f"final_video_{uuid.uuid4().hex}.mp4"
132
+ final_video_path = os.path.join("/tmp", final_video_filename)
133
+
134
+ ffmpeg_cmd = [
135
+ "ffmpeg", "-y",
136
+ "-i", tmp_video_path,
137
+ "-i", commentary_audio_path,
138
+ "-c:v", "copy",
139
+ "-c:a", "aac",
140
+ "-shortest",
141
+ final_video_path
142
+ ]
143
+ try:
144
+ subprocess.run(ffmpeg_cmd, check=True, capture_output=True, text=True)
145
+ except subprocess.CalledProcessError as cpe:
146
+ app.logger.error("FFmpeg error output: %s", cpe.stderr)
147
+ raise Exception(f"FFmpeg failed: {cpe.stderr}")
148
+
149
+ # Schedule deletion of all temporary files after 10 minutes (600 seconds)
150
  def remove_files():
151
+ for fpath in [tmp_video_path, code_filepath, commentary_audio_path, final_video_path]:
152
+ try:
153
+ if os.path.exists(fpath):
154
+ os.remove(fpath)
155
+ except Exception as e:
156
+ app.logger.error("Error removing file %s: %s", fpath, e)
157
+
158
  Timer(600, remove_files).start()
159
 
160
+ # Use the final combined video for display
161
+ video_url = url_for('get_video', filename=final_video_filename)
162
  return render_template("result.html", video_url=video_url)
163
 
164
  except Exception as e: