manimate / utils /renderer.py
sourize
Updated Code
46f75eb
"""Video rendering utilities."""
import os
import time
import logging
import subprocess
from pathlib import Path
from typing import Tuple, Optional, Any
from config.settings import QUALITY_SETTINGS, APP_SETTINGS
from config.constants import (
ErrorType,
FILE_CONSTANTS,
VideoQuality
)
from utils.error_handler import ErrorHandler
logger = logging.getLogger(__name__)
class VideoRenderer:
"""Handles Manim video rendering."""
def __init__(
self,
code_file: str,
temp_dir: str,
quality: str = VideoQuality.MEDIUM.value,
metrics: Optional[Any] = None
):
"""Initialize the video renderer.
Args:
code_file: Path to the Python file containing the Manim scene
temp_dir: Directory to store temporary files
quality: Video quality setting
metrics: Optional metrics collector instance
"""
self.code_file = code_file
self.temp_dir = temp_dir
self.quality = quality
self.metrics = metrics
self.start_time = None
def _get_quality_settings(self) -> dict:
"""Get quality settings for the current quality level."""
return QUALITY_SETTINGS.get(
self.quality,
QUALITY_SETTINGS[VideoQuality.MEDIUM.value]
)
def _build_manim_command(self) -> list:
"""Build the Manim command with appropriate flags."""
quality_settings = self._get_quality_settings()
return [
"manim",
"render",
quality_settings["flag"],
self.code_file,
"GeneratedScene",
"--output_file", FILE_CONSTANTS["OUTPUT_FILE_NAME"],
"--media_dir", self.temp_dir
]
def _find_output_video(self) -> Optional[str]:
"""Find the generated video file."""
video_files = list(Path(self.temp_dir).rglob(f"*{FILE_CONSTANTS['VIDEO_EXTENSION']}"))
return str(video_files[0]) if video_files else None
def _record_metrics(self, success: bool, error_type: Optional[str] = None) -> None:
"""Record rendering metrics if metrics collector is available."""
if self.metrics and self.start_time:
render_time = time.time() - self.start_time
if success:
self.metrics.record_successful_render(
render_time,
"unknown", # TODO: Add scene type detection
self.quality
)
else:
self.metrics.record_failed_render(error_type or "UnknownError")
def render(self) -> Tuple[bool, str, str]:
"""Render the Manim video.
Returns:
Tuple containing:
- Success flag
- Result message or error message
- Log output
"""
self.start_time = time.time()
try:
# Build and run command
cmd = self._build_manim_command()
logger.info(f"Running command: {' '.join(cmd)}")
process = subprocess.run(
cmd,
cwd=self.temp_dir,
capture_output=True,
text=True,
timeout=APP_SETTINGS["max_render_time"]
)
if process.returncode == 0:
# Find and verify video file
video_file = self._find_output_video()
if video_file:
self._record_metrics(True)
return True, video_file, process.stdout
else:
self._record_metrics(False, "NoVideoFile")
return False, "Rendering completed but no video file was found", ""
else:
self._record_metrics(False, "RenderingFailed")
return False, "Rendering failed. Please check your scene code.", ""
except subprocess.TimeoutExpired:
self._record_metrics(False, "TimeoutError")
return False, "Rendering took too long and was stopped", ""
except Exception as e:
logger.error(f"Error rendering video: {e}")
self._record_metrics(False, "RenderingError")
ErrorHandler.handle_error(ErrorType.RENDERING_ERROR, str(e))
return False, "An error occurred during rendering", ""
@property
def estimated_render_time(self) -> int:
"""Get estimated render time in seconds."""
return self._get_quality_settings()["estimated_time"]
@property
def resolution(self) -> str:
"""Get video resolution for current quality setting."""
return self._get_quality_settings()["resolution"]