memex-in commited on
Commit
ee20022
·
verified ·
1 Parent(s): ff21202

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -18
app.py CHANGED
@@ -2,7 +2,7 @@ import asyncio
2
  import os
3
  import shutil
4
  import subprocess
5
- import tempfile
6
  from typing import List
7
  import logging
8
 
@@ -95,15 +95,21 @@ def generate_video_outline(concept: str) -> VideoOutline:
95
 
96
  def create_video_from_code(code: str, chapter_number: int) -> str:
97
  """Creates a video from Manim code and returns the video file path using subprocess.Popen."""
98
- with open("temp.py", "w") as temp_file:
 
 
99
  temp_file.write(code)
100
  temp_file_name = temp_file.name
101
 
102
  process = None
103
  try:
 
 
 
104
  command = ["manim", temp_file_name, "-ql", "--disable_caching"]
 
105
  process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True)
106
- stdout, stderr = process.communicate(timeout=60) # Add a timeout to prevent indefinite blocking
107
 
108
  if process.returncode == 0:
109
  logging.info(f"Manim execution successful for chapter {chapter_number}.")
@@ -123,17 +129,38 @@ def create_video_from_code(code: str, chapter_number: int) -> str:
123
  logging.error("Error: The 'manim' command was not found. Ensure Manim is installed and in your system's PATH.")
124
  raise
125
  finally:
126
- pass
127
- # if os.path.exists(temp_file_name):
128
- # os.remove(temp_file_name)
 
129
 
130
  # Construct the video file name. Manim names the file based on the class name.
131
- # Extract the class name from the python code.
132
  match = re.search(r"class\s+(\w+)\(Scene\):", code)
133
  if match:
134
  class_name = match.group(1)
135
  video_file_name = f"{class_name}.mp4"
136
- return os.path.join("./media/videos/temp/480p15/", video_file_name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  else:
138
  raise ValueError(f"Could not extract class name from Manim code for chapter {chapter_number}")
139
 
@@ -155,6 +182,7 @@ async def generate_video(concept: str):
155
 
156
  while attempts < max_attempts and not success:
157
  try:
 
158
  video_file = create_video_from_code(manim_code, i + 1)
159
  video_files.append(video_file)
160
  logging.info(f"Video file created for chapter {i + 1}: {video_file}")
@@ -162,19 +190,25 @@ async def generate_video(concept: str):
162
  except subprocess.CalledProcessError as e:
163
  attempts += 1
164
  logging.error(f"Manim execution failed for chapter {i + 1} (Attempt {attempts}): {e}")
165
- logging.info(f"Attempting to fix the code...")
166
  manim_code = fix_manim_code(str(e), manim_code)
167
  logging.debug(f"Fixed Manim code (Attempt {attempts}):\n{manim_code}")
168
  except ValueError as e:
169
  logging.error(f"Error processing Manim code for chapter {i + 1}: {e}")
170
  st.error(f"Error processing chapter {i + 1}: {e}")
171
  return None # Stop processing if a critical error occurs with code structure
172
- except FileNotFoundError:
173
- logging.error("Manim not found. Please ensure it's installed and in your PATH.")
174
- st.error("Manim not found. Please ensure it's installed and in your PATH.")
 
 
 
 
175
  return None
176
  except subprocess.TimeoutExpired:
177
- logging.error(f"Manim process timed out for chapter {i + 1}. Attempting to fix...")
 
 
178
  manim_code = fix_manim_code(f"Manim process timed out.", manim_code)
179
  logging.debug(f"Fixed Manim code (Attempt {attempts}):\n{manim_code}")
180
 
@@ -187,8 +221,16 @@ async def generate_video(concept: str):
187
  final_video_path = None
188
  if video_files:
189
  logging.info("Combining video files...")
 
190
  try:
191
- clips = [VideoFileClip(vf) for vf in video_files if os.path.exists(vf)]
 
 
 
 
 
 
 
192
  if clips:
193
  final_video_path = f"final_video.mp4"
194
  final_clip = concatenate_videoclips(clips)
@@ -198,12 +240,13 @@ async def generate_video(concept: str):
198
  st.success("Video generation complete!")
199
  else:
200
  logging.warning("No valid video files to combine.")
201
- st.warning("No valid video files were generated.")
202
  except Exception as e:
203
  logging.error(f"Error combining video files: {e}")
204
  st.error(f"Error combining video files: {e}")
205
 
206
- # Clean up intermediate video files
 
207
  for video_file in video_files:
208
  try:
209
  if os.path.exists(video_file):
@@ -211,9 +254,16 @@ async def generate_video(concept: str):
211
  logging.info(f"Deleted intermediate video file: {video_file}")
212
  except Exception as e:
213
  logging.error(f"Error deleting intermediate video file {video_file}: {e}")
 
 
 
 
 
 
 
214
  else:
215
  logging.warning("No video files to combine.")
216
- st.warning("No video files were generated.")
217
 
218
  return final_video_path
219
 
@@ -227,8 +277,16 @@ def main():
227
  final_video_file = asyncio.run(generate_video(concept))
228
  if final_video_file and os.path.exists(final_video_file):
229
  st.video(final_video_file)
 
 
 
 
 
 
 
 
230
  elif final_video_file:
231
- st.error("Error: Final video file not found.")
232
  else:
233
  st.info("Video generation process completed without creating a final video.")
234
  else:
 
2
  import os
3
  import shutil
4
  import subprocess
5
+ import tempfile # Import the tempfile module
6
  from typing import List
7
  import logging
8
 
 
95
 
96
  def create_video_from_code(code: str, chapter_number: int) -> str:
97
  """Creates a video from Manim code and returns the video file path using subprocess.Popen."""
98
+ # Use tempfile to create a temporary Python file
99
+ # This ensures that the script has write permissions and handles cleanup
100
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as temp_file:
101
  temp_file.write(code)
102
  temp_file_name = temp_file.name
103
 
104
  process = None
105
  try:
106
+ # Manim needs the class name from the code to correctly generate the video file name.
107
+ # It also implicitly determines the output path based on the current working directory
108
+ # unless specified. We will get the output path later.
109
  command = ["manim", temp_file_name, "-ql", "--disable_caching"]
110
+ logging.info(f"Executing Manim command: {' '.join(command)}")
111
  process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True)
112
+ stdout, stderr = process.communicate(timeout=120) # Increased timeout for potentially longer renders
113
 
114
  if process.returncode == 0:
115
  logging.info(f"Manim execution successful for chapter {chapter_number}.")
 
129
  logging.error("Error: The 'manim' command was not found. Ensure Manim is installed and in your system's PATH.")
130
  raise
131
  finally:
132
+ # Ensure the temporary file is deleted after Manim tries to use it
133
+ if os.path.exists(temp_file_name):
134
+ os.remove(temp_file_name)
135
+ logging.info(f"Deleted temporary Manim script: {temp_file_name}")
136
 
137
  # Construct the video file name. Manim names the file based on the class name.
138
+ # The output directory for Manim in this configuration is ./media/videos/temp/480p15/
139
  match = re.search(r"class\s+(\w+)\(Scene\):", code)
140
  if match:
141
  class_name = match.group(1)
142
  video_file_name = f"{class_name}.mp4"
143
+ # Manim renders to a specific default output path if not specified
144
+ # Adjust this path if your Manim configuration or command differs
145
+ manim_output_dir = "./media/videos/temp/480p15/"
146
+ # Ensure the output directory exists
147
+ os.makedirs(manim_output_dir, exist_ok=True)
148
+ video_file_path = os.path.join(manim_output_dir, video_file_name)
149
+
150
+ # In some cases, Manim might output to the current directory before moving it.
151
+ # We need to make sure we're checking the correct path where Manim places the final video.
152
+ # A more robust solution might involve parsing Manim's stdout for the actual path.
153
+ # For now, let's assume the default output path.
154
+ if not os.path.exists(video_file_path):
155
+ logging.warning(f"Expected Manim output at {video_file_path} but not found. Checking current directory for {video_file_name}.")
156
+ # Fallback check in current directory, although not typical for default Manim behavior
157
+ if os.path.exists(video_file_name):
158
+ video_file_path = video_file_name
159
+ logging.info(f"Found video in current directory: {video_file_name}")
160
+ else:
161
+ raise FileNotFoundError(f"Manim output video file '{video_file_name}' not found at '{video_file_path}' or current directory after successful execution.")
162
+
163
+ return video_file_path
164
  else:
165
  raise ValueError(f"Could not extract class name from Manim code for chapter {chapter_number}")
166
 
 
182
 
183
  while attempts < max_attempts and not success:
184
  try:
185
+ st.info(f"Attempting to render chapter {i + 1}: {chapter.title} (Attempt {attempts + 1}/{max_attempts})")
186
  video_file = create_video_from_code(manim_code, i + 1)
187
  video_files.append(video_file)
188
  logging.info(f"Video file created for chapter {i + 1}: {video_file}")
 
190
  except subprocess.CalledProcessError as e:
191
  attempts += 1
192
  logging.error(f"Manim execution failed for chapter {i + 1} (Attempt {attempts}): {e}")
193
+ st.warning(f"Manim rendering failed for chapter {i + 1}. Attempting to fix code...")
194
  manim_code = fix_manim_code(str(e), manim_code)
195
  logging.debug(f"Fixed Manim code (Attempt {attempts}):\n{manim_code}")
196
  except ValueError as e:
197
  logging.error(f"Error processing Manim code for chapter {i + 1}: {e}")
198
  st.error(f"Error processing chapter {i + 1}: {e}")
199
  return None # Stop processing if a critical error occurs with code structure
200
+ except FileNotFoundError as e:
201
+ if "Manim output video file" in str(e):
202
+ logging.error(f"Manim rendering failed to produce expected output file for chapter {i + 1}: {e}")
203
+ st.error(f"Manim rendering failed to produce video for chapter {i + 1}. Please check Manim installation and configuration.")
204
+ else:
205
+ logging.error(f"Manim not found or output path issue: {e}")
206
+ st.error("Manim not found. Please ensure it's installed and in your PATH, or check Manim's output configuration.")
207
  return None
208
  except subprocess.TimeoutExpired:
209
+ attempts += 1
210
+ logging.error(f"Manim process timed out for chapter {i + 1}. Attempt {attempts}. Attempting to fix...")
211
+ st.warning(f"Manim rendering timed out for chapter {i + 1}. Attempting to fix code...")
212
  manim_code = fix_manim_code(f"Manim process timed out.", manim_code)
213
  logging.debug(f"Fixed Manim code (Attempt {attempts}):\n{manim_code}")
214
 
 
221
  final_video_path = None
222
  if video_files:
223
  logging.info("Combining video files...")
224
+ st.info("Combining all chapter videos...")
225
  try:
226
+ clips = []
227
+ for vf in video_files:
228
+ if os.path.exists(vf):
229
+ clips.append(VideoFileClip(vf))
230
+ else:
231
+ logging.warning(f"Skipping non-existent video file: {vf}")
232
+ st.warning(f"Skipping missing chapter video: {vf.split('/')[-1]}")
233
+
234
  if clips:
235
  final_video_path = f"final_video.mp4"
236
  final_clip = concatenate_videoclips(clips)
 
240
  st.success("Video generation complete!")
241
  else:
242
  logging.warning("No valid video files to combine.")
243
+ st.warning("No valid video files were generated to combine.")
244
  except Exception as e:
245
  logging.error(f"Error combining video files: {e}")
246
  st.error(f"Error combining video files: {e}")
247
 
248
+ # Clean up intermediate video files and the temporary Manim output directory if it's no longer needed
249
+ temp_manim_output_dir = "./media/videos/temp/480p15/"
250
  for video_file in video_files:
251
  try:
252
  if os.path.exists(video_file):
 
254
  logging.info(f"Deleted intermediate video file: {video_file}")
255
  except Exception as e:
256
  logging.error(f"Error deleting intermediate video file {video_file}: {e}")
257
+ # Optionally, remove the entire temp Manim output directory if it's empty
258
+ try:
259
+ if os.path.exists(temp_manim_output_dir) and not os.listdir(temp_manim_output_dir):
260
+ os.rmdir(temp_manim_output_dir)
261
+ logging.info(f"Deleted empty Manim temp output directory: {temp_manim_output_dir}")
262
+ except Exception as e:
263
+ logging.error(f"Error deleting empty Manim temp output directory {temp_manim_output_dir}: {e}")
264
  else:
265
  logging.warning("No video files to combine.")
266
+ st.info("No video files were successfully generated.")
267
 
268
  return final_video_path
269
 
 
277
  final_video_file = asyncio.run(generate_video(concept))
278
  if final_video_file and os.path.exists(final_video_file):
279
  st.video(final_video_file)
280
+ # Provide a download button for the video
281
+ with open(final_video_file, "rb") as file:
282
+ st.download_button(
283
+ label="Download Video",
284
+ data=file,
285
+ file_name=os.path.basename(final_video_file),
286
+ mime="video/mp4"
287
+ )
288
  elif final_video_file:
289
+ st.error("Error: Final video file not found after generation.")
290
  else:
291
  st.info("Video generation process completed without creating a final video.")
292
  else: