File size: 4,798 Bytes
f0e2e50
 
 
 
 
 
 
5535d80
f0e2e50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50544a2
f0e2e50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46f75eb
f0e2e50
 
46f75eb
f0e2e50
 
 
46f75eb
f0e2e50
 
 
 
 
46f75eb
f0e2e50
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
"""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"]