manimate / utils /renderer.py
sourize
Updated Code
46f75eb
Raw History Blame Contribute Delete
4.8 kB
"""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"]