Update app.py
Browse files
app.py
CHANGED
@@ -26,11 +26,6 @@ from threading import Timer
|
|
26 |
from flask import Flask, render_template, request, url_for, send_from_directory
|
27 |
from google import genai
|
28 |
|
29 |
-
# New imports for audio generation and handling
|
30 |
-
from kokoro import KPipeline
|
31 |
-
import soundfile as sf
|
32 |
-
import numpy as np
|
33 |
-
|
34 |
app = Flask(__name__)
|
35 |
|
36 |
# Load API key from environment variable
|
@@ -67,11 +62,7 @@ Keep the scene uncluttered and aesthetically pleasing.
|
|
67 |
Make sure things are not overlapping unless explicitly stated otherwise.
|
68 |
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.
|
69 |
Make sure to think through what you are going to do and think about the topic before you write the code.
|
70 |
-
|
71 |
-
Don't describe music or sound effects in your script, only what the text to speech model will say.
|
72 |
-
Make sure that the script is short and don't do an intro and outro, simply start it. Additionally, keep it short enough so that it aligns with the video timing.
|
73 |
-
You can always adjust the self.wait(x) or run_time=x inside the code to wait for the speech if needed.
|
74 |
-
Make sure to think about the script timing before you write the code so that it doesnt cut off the speech or anything and the video and speech are synced
|
75 |
You got this!! <3
|
76 |
"""
|
77 |
)
|
@@ -82,13 +73,7 @@ You got this!! <3
|
|
82 |
if not code_match:
|
83 |
raise Exception("No python code block found in the AI response.")
|
84 |
code = code_match.group(1)
|
85 |
-
|
86 |
-
# Extract the commentary script from the AI response
|
87 |
-
script_pattern = r"```script\s*(.*?)\s*```"
|
88 |
-
script_match = re.search(script_pattern, ai_response.text, re.DOTALL)
|
89 |
-
if not script_match:
|
90 |
-
raise Exception("No script block found in the AI response.")
|
91 |
-
script = script_match.group(1)
|
92 |
|
93 |
# Determine the scene class name from the generated code
|
94 |
scene_match = re.search(r"class\s+(\w+)\(.*Scene.*\):", code)
|
@@ -103,24 +88,6 @@ You got this!! <3
|
|
103 |
with open(code_filepath, "w") as f:
|
104 |
f.write(code)
|
105 |
|
106 |
-
# === Generate Commentary Audio via Kokoro ===
|
107 |
-
# Initialize the Kokoro pipeline (adjust lang_code if needed)
|
108 |
-
audio_pipeline = KPipeline(lang_code='a')
|
109 |
-
# Feed in the commentary script; here we split the text by one or more newlines.
|
110 |
-
audio_generator = audio_pipeline(script, voice='af_heart', speed=1, split_pattern=r'\n+')
|
111 |
-
|
112 |
-
audio_segments = []
|
113 |
-
for _, _, audio in audio_generator:
|
114 |
-
audio_segments.append(audio)
|
115 |
-
|
116 |
-
if not audio_segments:
|
117 |
-
raise Exception("No audio segments were generated from the commentary script.")
|
118 |
-
|
119 |
-
# Concatenate all audio segments into one audio track
|
120 |
-
full_audio = np.concatenate(audio_segments)
|
121 |
-
commentary_audio_filename = f"commentary_{uuid.uuid4().hex}.wav"
|
122 |
-
commentary_audio_path = os.path.join("/tmp", commentary_audio_filename)
|
123 |
-
sf.write(commentary_audio_path, full_audio, 24000)
|
124 |
|
125 |
# === Run Manim to generate the silent video ===
|
126 |
# Prepare the Manim command with the --media_dir flag
|
@@ -147,26 +114,7 @@ You got this!! <3
|
|
147 |
# Move the video file to /tmp (to serve it from there)
|
148 |
tmp_video_path = os.path.join("/tmp", video_filename)
|
149 |
shutil.move(video_path_in_media, tmp_video_path)
|
150 |
-
|
151 |
-
# === Combine Video with Commentary Audio using FFmpeg ===
|
152 |
-
final_video_filename = f"final_video_{uuid.uuid4().hex}.mp4"
|
153 |
-
final_video_path = os.path.join("/tmp", final_video_filename)
|
154 |
-
|
155 |
-
ffmpeg_cmd = [
|
156 |
-
"ffmpeg", "-y",
|
157 |
-
"-i", tmp_video_path,
|
158 |
-
"-i", commentary_audio_path,
|
159 |
-
"-c:v", "copy",
|
160 |
-
"-c:a", "aac",
|
161 |
-
"-shortest",
|
162 |
-
final_video_path
|
163 |
-
]
|
164 |
-
try:
|
165 |
-
subprocess.run(ffmpeg_cmd, check=True, capture_output=True, text=True)
|
166 |
-
except subprocess.CalledProcessError as cpe:
|
167 |
-
app.logger.error("FFmpeg error output: %s", cpe.stderr)
|
168 |
-
raise Exception(f"FFmpeg failed: {cpe.stderr}")
|
169 |
-
|
170 |
# Schedule deletion of all temporary files after 10 minutes (600 seconds)
|
171 |
def remove_files():
|
172 |
for fpath in [tmp_video_path, code_filepath, commentary_audio_path, final_video_path]:
|
@@ -179,7 +127,7 @@ You got this!! <3
|
|
179 |
Timer(600, remove_files).start()
|
180 |
|
181 |
# Use the final combined video for display
|
182 |
-
video_url = url_for('get_video', filename=
|
183 |
return render_template("result.html", video_url=video_url)
|
184 |
|
185 |
except Exception as e:
|
|
|
26 |
from flask import Flask, render_template, request, url_for, send_from_directory
|
27 |
from google import genai
|
28 |
|
|
|
|
|
|
|
|
|
|
|
29 |
app = Flask(__name__)
|
30 |
|
31 |
# Load API key from environment variable
|
|
|
62 |
Make sure things are not overlapping unless explicitly stated otherwise.
|
63 |
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.
|
64 |
Make sure to think through what you are going to do and think about the topic before you write the code.
|
65 |
+
Use self.wait at the end to give the user enough time to process the frame.
|
|
|
|
|
|
|
|
|
66 |
You got this!! <3
|
67 |
"""
|
68 |
)
|
|
|
73 |
if not code_match:
|
74 |
raise Exception("No python code block found in the AI response.")
|
75 |
code = code_match.group(1)
|
76 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
# Determine the scene class name from the generated code
|
79 |
scene_match = re.search(r"class\s+(\w+)\(.*Scene.*\):", code)
|
|
|
88 |
with open(code_filepath, "w") as f:
|
89 |
f.write(code)
|
90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
|
92 |
# === Run Manim to generate the silent video ===
|
93 |
# Prepare the Manim command with the --media_dir flag
|
|
|
114 |
# Move the video file to /tmp (to serve it from there)
|
115 |
tmp_video_path = os.path.join("/tmp", video_filename)
|
116 |
shutil.move(video_path_in_media, tmp_video_path)
|
117 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
# Schedule deletion of all temporary files after 10 minutes (600 seconds)
|
119 |
def remove_files():
|
120 |
for fpath in [tmp_video_path, code_filepath, commentary_audio_path, final_video_path]:
|
|
|
127 |
Timer(600, remove_files).start()
|
128 |
|
129 |
# Use the final combined video for display
|
130 |
+
video_url = url_for('get_video', filename=tmp_video_path)
|
131 |
return render_template("result.html", video_url=video_url)
|
132 |
|
133 |
except Exception as e:
|