sourize
commited on
Commit
Β·
722a506
1
Parent(s):
f0e2e50
Updated Code
Browse files- app.py +19 -14
- config/constants.py +30 -36
- config/settings.py +0 -28
- config/space_config.py +17 -5
- core/generator.py +0 -16
- utils/metrics_collector.py +4 -1
- utils/performance_optimizer.py +26 -21
- utils/ui_components.py +10 -9
app.py
CHANGED
|
@@ -38,9 +38,7 @@ class ManimVideoGenerator:
|
|
| 38 |
self.client = Groq(api_key=groq_api_key)
|
| 39 |
self.available_models = {
|
| 40 |
"llama-3.3-70b-versatile": "Llama 3.3 70B",
|
| 41 |
-
"llama3-8b-8192": "Llama 3 8B"
|
| 42 |
-
"llama-3.1-8b-instant": "Llama 3.1 8B Instant",
|
| 43 |
-
"gemma2-9b-it": "Gemma 2 9B"
|
| 44 |
}
|
| 45 |
self.temp_dir = None
|
| 46 |
self.metrics = MetricsCollector()
|
|
@@ -550,6 +548,14 @@ def process_video_generation(generator, user_prompt, selected_model, video_quali
|
|
| 550 |
if logs and st.checkbox("Show error details"):
|
| 551 |
st.text_area("Error Details", logs, height=200)
|
| 552 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 553 |
except Exception as e:
|
| 554 |
error_info = ErrorHandler.get_user_friendly_error(type(e).__name__, str(e))
|
| 555 |
st.markdown(f'<div class="error-box">β {error_info["user_message"]}</div>',
|
|
@@ -562,6 +568,14 @@ def process_video_generation(generator, user_prompt, selected_model, video_quali
|
|
| 562 |
st.markdown(f"β’ {suggestion}")
|
| 563 |
|
| 564 |
logger.error(f"Application error: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
|
| 566 |
def main():
|
| 567 |
initialize_app()
|
|
@@ -597,18 +611,9 @@ def main():
|
|
| 597 |
generator = ManimVideoGenerator(groq_api_key)
|
| 598 |
|
| 599 |
# Render main interface
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
# Handle example selection or user input
|
| 603 |
-
if isinstance(interface_result[0], str) and not interface_result[1]:
|
| 604 |
-
# Example was selected
|
| 605 |
-
user_prompt = interface_result[0]
|
| 606 |
-
generate_button = True
|
| 607 |
-
else:
|
| 608 |
-
# Regular user input
|
| 609 |
-
user_prompt, generate_button = interface_result
|
| 610 |
|
| 611 |
-
# Process generation if
|
| 612 |
if generate_button and user_prompt and user_prompt.strip():
|
| 613 |
process_video_generation(generator, user_prompt, selected_model, video_quality)
|
| 614 |
generator.cleanup()
|
|
|
|
| 38 |
self.client = Groq(api_key=groq_api_key)
|
| 39 |
self.available_models = {
|
| 40 |
"llama-3.3-70b-versatile": "Llama 3.3 70B",
|
| 41 |
+
"llama3-8b-8192": "Llama 3 8B"
|
|
|
|
|
|
|
| 42 |
}
|
| 43 |
self.temp_dir = None
|
| 44 |
self.metrics = MetricsCollector()
|
|
|
|
| 548 |
if logs and st.checkbox("Show error details"):
|
| 549 |
st.text_area("Error Details", logs, height=200)
|
| 550 |
|
| 551 |
+
# Cleanup after everything is done
|
| 552 |
+
if temp_dir and os.path.exists(temp_dir):
|
| 553 |
+
try:
|
| 554 |
+
shutil.rmtree(temp_dir)
|
| 555 |
+
logger.info(f"Cleaned up temporary directory: {temp_dir}")
|
| 556 |
+
except Exception as e:
|
| 557 |
+
logger.error(f"Error cleaning up temporary directory: {e}")
|
| 558 |
+
|
| 559 |
except Exception as e:
|
| 560 |
error_info = ErrorHandler.get_user_friendly_error(type(e).__name__, str(e))
|
| 561 |
st.markdown(f'<div class="error-box">β {error_info["user_message"]}</div>',
|
|
|
|
| 568 |
st.markdown(f"β’ {suggestion}")
|
| 569 |
|
| 570 |
logger.error(f"Application error: {e}")
|
| 571 |
+
|
| 572 |
+
# Cleanup in case of error
|
| 573 |
+
if 'temp_dir' in locals() and temp_dir and os.path.exists(temp_dir):
|
| 574 |
+
try:
|
| 575 |
+
shutil.rmtree(temp_dir)
|
| 576 |
+
logger.info(f"Cleaned up temporary directory after error: {temp_dir}")
|
| 577 |
+
except Exception as cleanup_error:
|
| 578 |
+
logger.error(f"Error cleaning up temporary directory after error: {cleanup_error}")
|
| 579 |
|
| 580 |
def main():
|
| 581 |
initialize_app()
|
|
|
|
| 611 |
generator = ManimVideoGenerator(groq_api_key)
|
| 612 |
|
| 613 |
# Render main interface
|
| 614 |
+
user_prompt, generate_button = render_main_interface()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
|
| 616 |
+
# Process generation only if button is clicked and there's a prompt
|
| 617 |
if generate_button and user_prompt and user_prompt.strip():
|
| 618 |
process_video_generation(generator, user_prompt, selected_model, video_quality)
|
| 619 |
generator.cleanup()
|
config/constants.py
CHANGED
|
@@ -1,24 +1,23 @@
|
|
| 1 |
-
"""Application constants and enums."""
|
| 2 |
-
|
| 3 |
from enum import Enum, auto
|
| 4 |
from typing import Dict, List, Tuple
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
class VideoQuality(Enum):
|
| 7 |
-
"""Video quality levels."""
|
| 8 |
LOW = "low_quality"
|
| 9 |
MEDIUM = "medium_quality"
|
| 10 |
-
HIGH = "high_quality"
|
| 11 |
-
PRODUCTION = "production_quality"
|
| 12 |
|
| 13 |
class ModelType(Enum):
|
| 14 |
-
"""Available AI models."""
|
| 15 |
LLAMA_70B = "llama-3.3-70b-versatile"
|
| 16 |
LLAMA_8B = "llama3-8b-8192"
|
| 17 |
-
MIXTRAL = "mixtral-8x7b-32768"
|
| 18 |
-
GEMMA = "gemma2-9b-it"
|
| 19 |
|
| 20 |
class AnimationCategory(Enum):
|
| 21 |
-
"""Categories of mathematical animations."""
|
| 22 |
ALGEBRA = "algebra"
|
| 23 |
GEOMETRY = "geometry"
|
| 24 |
CALCULUS = "calculus"
|
|
@@ -28,7 +27,6 @@ class AnimationCategory(Enum):
|
|
| 28 |
OTHER = "other"
|
| 29 |
|
| 30 |
class ErrorType(Enum):
|
| 31 |
-
"""Types of errors that can occur."""
|
| 32 |
RENDERING_ERROR = auto()
|
| 33 |
CODE_GENERATION_ERROR = auto()
|
| 34 |
PROMPT_ERROR = auto()
|
|
@@ -64,26 +62,6 @@ FILE_CONSTANTS = {
|
|
| 64 |
"LOG_FILE_NAME": "manimate.log"
|
| 65 |
}
|
| 66 |
|
| 67 |
-
# Example Prompts by Category
|
| 68 |
-
EXAMPLE_PROMPTS: Dict[AnimationCategory, List[str]] = {
|
| 69 |
-
AnimationCategory.ALGEBRA: [
|
| 70 |
-
"Visualize solving xΒ² + 3x - 4 = 0 using the quadratic formula",
|
| 71 |
-
"Show function transformations with f(x) = xΒ² shifting and scaling"
|
| 72 |
-
],
|
| 73 |
-
AnimationCategory.GEOMETRY: [
|
| 74 |
-
"Demonstrate the Pythagorean theorem with squares on triangle sides",
|
| 75 |
-
"Animate the construction of a regular pentagon using compass and straightedge"
|
| 76 |
-
],
|
| 77 |
-
AnimationCategory.CALCULUS: [
|
| 78 |
-
"Show the concept of limits with a function approaching a value",
|
| 79 |
-
"Visualize area under curve using Riemann sums with rectangles"
|
| 80 |
-
],
|
| 81 |
-
AnimationCategory.STATISTICS: [
|
| 82 |
-
"Animate the Central Limit Theorem with multiple distributions",
|
| 83 |
-
"Show correlation vs causation with scatter plot examples"
|
| 84 |
-
]
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
# CSS Classes
|
| 88 |
CSS_CLASSES = {
|
| 89 |
"MAIN_HEADER": "main-header",
|
|
@@ -91,7 +69,19 @@ CSS_CLASSES = {
|
|
| 91 |
"SUCCESS_BOX": "success-box",
|
| 92 |
"ERROR_BOX": "error-box",
|
| 93 |
"INFO_BOX": "info-box",
|
| 94 |
-
"METRIC_BOX": "metric-box"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
}
|
| 96 |
|
| 97 |
# CSS Styles
|
|
@@ -125,14 +115,18 @@ STATUS_MESSAGES = {
|
|
| 125 |
"ERROR": "β Error occurred during generation"
|
| 126 |
}
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
def get_css_class(class_name: str) -> str:
|
| 133 |
-
"""Get CSS class name by key."""
|
| 134 |
return CSS_CLASSES.get(class_name, "")
|
| 135 |
|
| 136 |
def get_status_message(key: str) -> str:
|
| 137 |
-
"""Get status message by key."""
|
| 138 |
return STATUS_MESSAGES.get(key, "")
|
|
|
|
|
|
|
|
|
|
| 1 |
from enum import Enum, auto
|
| 2 |
from typing import Dict, List, Tuple
|
| 3 |
|
| 4 |
+
# Quality levels
|
| 5 |
+
LOW = "low_quality"
|
| 6 |
+
MEDIUM = "medium_quality"
|
| 7 |
+
|
| 8 |
+
# Model identifiers
|
| 9 |
+
LLAMA_70B = "llama-3.3-70b-versatile"
|
| 10 |
+
LLAMA_8B = "llama3-8b-8192"
|
| 11 |
+
|
| 12 |
class VideoQuality(Enum):
|
|
|
|
| 13 |
LOW = "low_quality"
|
| 14 |
MEDIUM = "medium_quality"
|
|
|
|
|
|
|
| 15 |
|
| 16 |
class ModelType(Enum):
|
|
|
|
| 17 |
LLAMA_70B = "llama-3.3-70b-versatile"
|
| 18 |
LLAMA_8B = "llama3-8b-8192"
|
|
|
|
|
|
|
| 19 |
|
| 20 |
class AnimationCategory(Enum):
|
|
|
|
| 21 |
ALGEBRA = "algebra"
|
| 22 |
GEOMETRY = "geometry"
|
| 23 |
CALCULUS = "calculus"
|
|
|
|
| 27 |
OTHER = "other"
|
| 28 |
|
| 29 |
class ErrorType(Enum):
|
|
|
|
| 30 |
RENDERING_ERROR = auto()
|
| 31 |
CODE_GENERATION_ERROR = auto()
|
| 32 |
PROMPT_ERROR = auto()
|
|
|
|
| 62 |
"LOG_FILE_NAME": "manimate.log"
|
| 63 |
}
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
# CSS Classes
|
| 66 |
CSS_CLASSES = {
|
| 67 |
"MAIN_HEADER": "main-header",
|
|
|
|
| 69 |
"SUCCESS_BOX": "success-box",
|
| 70 |
"ERROR_BOX": "error-box",
|
| 71 |
"INFO_BOX": "info-box",
|
| 72 |
+
"METRIC_BOX": "metric-box",
|
| 73 |
+
"header": """
|
| 74 |
+
text-align: center;
|
| 75 |
+
padding: 1rem 0;
|
| 76 |
+
background: linear-gradient(90deg, #667eea 7%, #764ba2 100%);
|
| 77 |
+
color: white;
|
| 78 |
+
border-radius: 10px;
|
| 79 |
+
""",
|
| 80 |
+
"container": """
|
| 81 |
+
max-width: 1200px;
|
| 82 |
+
margin: 0 auto;
|
| 83 |
+
padding: 2rem;
|
| 84 |
+
"""
|
| 85 |
}
|
| 86 |
|
| 87 |
# CSS Styles
|
|
|
|
| 115 |
"ERROR": "β Error occurred during generation"
|
| 116 |
}
|
| 117 |
|
| 118 |
+
# Application constants
|
| 119 |
+
APP_CONSTANTS = {
|
| 120 |
+
"MAX_RENDER_TIME": 300, # seconds
|
| 121 |
+
"MAX_PROMPT_LENGTH": 1000,
|
| 122 |
+
"MIN_PROMPT_LENGTH": 10,
|
| 123 |
+
"DEFAULT_VIDEO_QUALITY": VideoQuality.MEDIUM.value,
|
| 124 |
+
"TEMP_FILE_PREFIX": "manim_",
|
| 125 |
+
"LOG_FILE_NAME": "manimate.log"
|
| 126 |
+
}
|
| 127 |
|
| 128 |
def get_css_class(class_name: str) -> str:
|
|
|
|
| 129 |
return CSS_CLASSES.get(class_name, "")
|
| 130 |
|
| 131 |
def get_status_message(key: str) -> str:
|
|
|
|
| 132 |
return STATUS_MESSAGES.get(key, "")
|
config/settings.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
"""Application settings and configuration."""
|
| 2 |
-
|
| 3 |
from pathlib import Path
|
| 4 |
from typing import Dict, Any
|
| 5 |
|
|
@@ -29,18 +27,6 @@ AVAILABLE_MODELS = {
|
|
| 29 |
"description": "Fast and efficient for simpler animations",
|
| 30 |
"max_tokens": 8192,
|
| 31 |
"temperature": 0.7
|
| 32 |
-
},
|
| 33 |
-
"mixtral-8x7b-32768": {
|
| 34 |
-
"name": "Mixtral 8x7B",
|
| 35 |
-
"description": "Creative model with large context window",
|
| 36 |
-
"max_tokens": 32768,
|
| 37 |
-
"temperature": 0.8
|
| 38 |
-
},
|
| 39 |
-
"gemma2-9b-it": {
|
| 40 |
-
"name": "Gemma 2 9B",
|
| 41 |
-
"description": "Efficient model for basic animations",
|
| 42 |
-
"max_tokens": 8192,
|
| 43 |
-
"temperature": 0.7
|
| 44 |
}
|
| 45 |
}
|
| 46 |
|
|
@@ -57,18 +43,6 @@ QUALITY_SETTINGS = {
|
|
| 57 |
"description": "Medium Quality (Balanced)",
|
| 58 |
"estimated_time": 180, # seconds
|
| 59 |
"resolution": "720p"
|
| 60 |
-
},
|
| 61 |
-
"high_quality": {
|
| 62 |
-
"flag": "-qh",
|
| 63 |
-
"description": "High Quality (Detailed)",
|
| 64 |
-
"estimated_time": 300, # seconds
|
| 65 |
-
"resolution": "1080p"
|
| 66 |
-
},
|
| 67 |
-
"production_quality": {
|
| 68 |
-
"flag": "-qk",
|
| 69 |
-
"description": "Production Quality (Best)",
|
| 70 |
-
"estimated_time": 600, # seconds
|
| 71 |
-
"resolution": "4K"
|
| 72 |
}
|
| 73 |
}
|
| 74 |
|
|
@@ -98,7 +72,6 @@ UI_SETTINGS = {
|
|
| 98 |
}
|
| 99 |
|
| 100 |
def get_setting(key: str, default: Any = None) -> Any:
|
| 101 |
-
"""Get a setting value by key with optional default."""
|
| 102 |
if key in APP_SETTINGS:
|
| 103 |
return APP_SETTINGS[key]
|
| 104 |
if key in UI_SETTINGS:
|
|
@@ -106,7 +79,6 @@ def get_setting(key: str, default: Any = None) -> Any:
|
|
| 106 |
return default
|
| 107 |
|
| 108 |
def update_setting(key: str, value: Any) -> None:
|
| 109 |
-
"""Update a setting value."""
|
| 110 |
if key in APP_SETTINGS:
|
| 111 |
APP_SETTINGS[key] = value
|
| 112 |
elif key in UI_SETTINGS:
|
|
|
|
|
|
|
|
|
|
| 1 |
from pathlib import Path
|
| 2 |
from typing import Dict, Any
|
| 3 |
|
|
|
|
| 27 |
"description": "Fast and efficient for simpler animations",
|
| 28 |
"max_tokens": 8192,
|
| 29 |
"temperature": 0.7
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
}
|
| 31 |
}
|
| 32 |
|
|
|
|
| 43 |
"description": "Medium Quality (Balanced)",
|
| 44 |
"estimated_time": 180, # seconds
|
| 45 |
"resolution": "720p"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
}
|
| 48 |
|
|
|
|
| 72 |
}
|
| 73 |
|
| 74 |
def get_setting(key: str, default: Any = None) -> Any:
|
|
|
|
| 75 |
if key in APP_SETTINGS:
|
| 76 |
return APP_SETTINGS[key]
|
| 77 |
if key in UI_SETTINGS:
|
|
|
|
| 79 |
return default
|
| 80 |
|
| 81 |
def update_setting(key: str, value: Any) -> None:
|
|
|
|
| 82 |
if key in APP_SETTINGS:
|
| 83 |
APP_SETTINGS[key] = value
|
| 84 |
elif key in UI_SETTINGS:
|
config/space_config.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
"""Configuration settings for Hugging Face Spaces deployment."""
|
| 2 |
-
|
| 3 |
import os
|
| 4 |
|
| 5 |
# Space-specific settings
|
|
@@ -8,9 +6,7 @@ SPACE_CONFIG = {
|
|
| 8 |
"max_video_size_mb": 50, # Maximum video size in MB
|
| 9 |
"allowed_models": [
|
| 10 |
"llama-3.3-70b-versatile",
|
| 11 |
-
"llama3-8b-8192"
|
| 12 |
-
"mixtral-8x7b-32768",
|
| 13 |
-
"gemma2-9b-it"
|
| 14 |
],
|
| 15 |
"default_quality": "medium_quality",
|
| 16 |
"cache_ttl": 3600, # Cache time-to-live in seconds
|
|
@@ -58,6 +54,22 @@ UI_CONFIG = {
|
|
| 58 |
"enable_error_reporting": True
|
| 59 |
}
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
def get_space_config():
|
| 62 |
"""Get the current space configuration."""
|
| 63 |
return {
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
|
| 3 |
# Space-specific settings
|
|
|
|
| 6 |
"max_video_size_mb": 50, # Maximum video size in MB
|
| 7 |
"allowed_models": [
|
| 8 |
"llama-3.3-70b-versatile",
|
| 9 |
+
"llama3-8b-8192"
|
|
|
|
|
|
|
| 10 |
],
|
| 11 |
"default_quality": "medium_quality",
|
| 12 |
"cache_ttl": 3600, # Cache time-to-live in seconds
|
|
|
|
| 54 |
"enable_error_reporting": True
|
| 55 |
}
|
| 56 |
|
| 57 |
+
AVAILABLE_MODELS = [
|
| 58 |
+
"llama-3.3-70b-versatile",
|
| 59 |
+
"llama3-8b-8192"
|
| 60 |
+
]
|
| 61 |
+
|
| 62 |
+
QUALITY_SETTINGS = {
|
| 63 |
+
"low_quality": {
|
| 64 |
+
"resolution": "480p",
|
| 65 |
+
"fps": 30
|
| 66 |
+
},
|
| 67 |
+
"medium_quality": {
|
| 68 |
+
"resolution": "720p",
|
| 69 |
+
"fps": 30
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
def get_space_config():
|
| 74 |
"""Get the current space configuration."""
|
| 75 |
return {
|
core/generator.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
"""Core video generation functionality."""
|
| 2 |
-
|
| 3 |
import os
|
| 4 |
import logging
|
| 5 |
import tempfile
|
|
@@ -31,10 +29,8 @@ from utils.metrics_collector import MetricsCollector
|
|
| 31 |
logger = logging.getLogger(__name__)
|
| 32 |
|
| 33 |
class ManimVideoGenerator:
|
| 34 |
-
"""Main class for generating Manim videos from prompts."""
|
| 35 |
|
| 36 |
def __init__(self, groq_api_key: str):
|
| 37 |
-
"""Initialize the Manim Video Generator with Groq API key."""
|
| 38 |
self.client = Groq(api_key=groq_api_key)
|
| 39 |
self.available_models = AVAILABLE_MODELS
|
| 40 |
self.temp_dir = None
|
|
@@ -43,7 +39,6 @@ class ManimVideoGenerator:
|
|
| 43 |
self.code_validator = CodeValidator()
|
| 44 |
|
| 45 |
def enhance_prompt(self, user_prompt: str, model: str = DEFAULT_MODEL) -> str:
|
| 46 |
-
"""Enhance the user prompt for better Manim code generation."""
|
| 47 |
try:
|
| 48 |
model_config = self.available_models[model]
|
| 49 |
return self.prompt_manager.enhance_prompt(
|
|
@@ -59,7 +54,6 @@ class ManimVideoGenerator:
|
|
| 59 |
return user_prompt
|
| 60 |
|
| 61 |
def generate_manim_code(self, enhanced_prompt: str, model: str = DEFAULT_MODEL) -> str:
|
| 62 |
-
"""Generate Manim code from the enhanced prompt."""
|
| 63 |
try:
|
| 64 |
model_config = self.available_models[model]
|
| 65 |
return self.prompt_manager.generate_code(
|
|
@@ -75,12 +69,9 @@ class ManimVideoGenerator:
|
|
| 75 |
raise
|
| 76 |
|
| 77 |
def process_and_validate_code(self, raw_code: str) -> str:
|
| 78 |
-
"""Process and validate the generated code."""
|
| 79 |
try:
|
| 80 |
-
# Clean and validate code
|
| 81 |
cleaned_code = self.code_validator.fix_common_issues(raw_code)
|
| 82 |
|
| 83 |
-
# Validate syntax and structure
|
| 84 |
validation_result = self.code_validator.validate_code(cleaned_code)
|
| 85 |
if not validation_result["is_valid"]:
|
| 86 |
raise ValueError(validation_result["error_message"])
|
|
@@ -92,7 +83,6 @@ class ManimVideoGenerator:
|
|
| 92 |
raise
|
| 93 |
|
| 94 |
def create_temp_directory(self) -> str:
|
| 95 |
-
"""Create a temporary directory for Manim operations."""
|
| 96 |
if self.temp_dir and os.path.exists(self.temp_dir):
|
| 97 |
shutil.rmtree(self.temp_dir)
|
| 98 |
|
|
@@ -103,7 +93,6 @@ class ManimVideoGenerator:
|
|
| 103 |
return self.temp_dir
|
| 104 |
|
| 105 |
def save_code_to_file(self, code: str, temp_dir: str) -> str:
|
| 106 |
-
"""Save the Manim code to a Python file."""
|
| 107 |
code_file = os.path.join(temp_dir, FILE_CONSTANTS["SCENE_FILE_NAME"])
|
| 108 |
try:
|
| 109 |
with open(code_file, 'w', encoding='utf-8') as f:
|
|
@@ -120,7 +109,6 @@ class ManimVideoGenerator:
|
|
| 120 |
temp_dir: str,
|
| 121 |
quality: str = VideoQuality.MEDIUM.value
|
| 122 |
) -> Tuple[bool, str, str]:
|
| 123 |
-
"""Render the Manim video."""
|
| 124 |
from utils.renderer import VideoRenderer
|
| 125 |
|
| 126 |
try:
|
|
@@ -139,7 +127,6 @@ class ManimVideoGenerator:
|
|
| 139 |
return False, str(e), ""
|
| 140 |
|
| 141 |
def cleanup(self) -> None:
|
| 142 |
-
"""Clean up temporary files."""
|
| 143 |
if self.temp_dir and os.path.exists(self.temp_dir):
|
| 144 |
try:
|
| 145 |
shutil.rmtree(self.temp_dir)
|
|
@@ -149,12 +136,10 @@ class ManimVideoGenerator:
|
|
| 149 |
ErrorHandler.handle_error(ErrorType.SYSTEM_ERROR, str(e))
|
| 150 |
|
| 151 |
def get_metrics_summary(self) -> Dict[str, Any]:
|
| 152 |
-
"""Get a summary of generation metrics."""
|
| 153 |
return self.metrics.get_summary()
|
| 154 |
|
| 155 |
@property
|
| 156 |
def model_options(self) -> Dict[str, str]:
|
| 157 |
-
"""Get formatted model options for UI display."""
|
| 158 |
return {
|
| 159 |
model_id: f"{config['name']} ({config['description']})"
|
| 160 |
for model_id, config in self.available_models.items()
|
|
@@ -162,7 +147,6 @@ class ManimVideoGenerator:
|
|
| 162 |
|
| 163 |
@property
|
| 164 |
def quality_options(self) -> Dict[str, str]:
|
| 165 |
-
"""Get formatted quality options for UI display."""
|
| 166 |
return {
|
| 167 |
quality_id: config["description"]
|
| 168 |
for quality_id, config in QUALITY_SETTINGS.items()
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import logging
|
| 3 |
import tempfile
|
|
|
|
| 29 |
logger = logging.getLogger(__name__)
|
| 30 |
|
| 31 |
class ManimVideoGenerator:
|
|
|
|
| 32 |
|
| 33 |
def __init__(self, groq_api_key: str):
|
|
|
|
| 34 |
self.client = Groq(api_key=groq_api_key)
|
| 35 |
self.available_models = AVAILABLE_MODELS
|
| 36 |
self.temp_dir = None
|
|
|
|
| 39 |
self.code_validator = CodeValidator()
|
| 40 |
|
| 41 |
def enhance_prompt(self, user_prompt: str, model: str = DEFAULT_MODEL) -> str:
|
|
|
|
| 42 |
try:
|
| 43 |
model_config = self.available_models[model]
|
| 44 |
return self.prompt_manager.enhance_prompt(
|
|
|
|
| 54 |
return user_prompt
|
| 55 |
|
| 56 |
def generate_manim_code(self, enhanced_prompt: str, model: str = DEFAULT_MODEL) -> str:
|
|
|
|
| 57 |
try:
|
| 58 |
model_config = self.available_models[model]
|
| 59 |
return self.prompt_manager.generate_code(
|
|
|
|
| 69 |
raise
|
| 70 |
|
| 71 |
def process_and_validate_code(self, raw_code: str) -> str:
|
|
|
|
| 72 |
try:
|
|
|
|
| 73 |
cleaned_code = self.code_validator.fix_common_issues(raw_code)
|
| 74 |
|
|
|
|
| 75 |
validation_result = self.code_validator.validate_code(cleaned_code)
|
| 76 |
if not validation_result["is_valid"]:
|
| 77 |
raise ValueError(validation_result["error_message"])
|
|
|
|
| 83 |
raise
|
| 84 |
|
| 85 |
def create_temp_directory(self) -> str:
|
|
|
|
| 86 |
if self.temp_dir and os.path.exists(self.temp_dir):
|
| 87 |
shutil.rmtree(self.temp_dir)
|
| 88 |
|
|
|
|
| 93 |
return self.temp_dir
|
| 94 |
|
| 95 |
def save_code_to_file(self, code: str, temp_dir: str) -> str:
|
|
|
|
| 96 |
code_file = os.path.join(temp_dir, FILE_CONSTANTS["SCENE_FILE_NAME"])
|
| 97 |
try:
|
| 98 |
with open(code_file, 'w', encoding='utf-8') as f:
|
|
|
|
| 109 |
temp_dir: str,
|
| 110 |
quality: str = VideoQuality.MEDIUM.value
|
| 111 |
) -> Tuple[bool, str, str]:
|
|
|
|
| 112 |
from utils.renderer import VideoRenderer
|
| 113 |
|
| 114 |
try:
|
|
|
|
| 127 |
return False, str(e), ""
|
| 128 |
|
| 129 |
def cleanup(self) -> None:
|
|
|
|
| 130 |
if self.temp_dir and os.path.exists(self.temp_dir):
|
| 131 |
try:
|
| 132 |
shutil.rmtree(self.temp_dir)
|
|
|
|
| 136 |
ErrorHandler.handle_error(ErrorType.SYSTEM_ERROR, str(e))
|
| 137 |
|
| 138 |
def get_metrics_summary(self) -> Dict[str, Any]:
|
|
|
|
| 139 |
return self.metrics.get_summary()
|
| 140 |
|
| 141 |
@property
|
| 142 |
def model_options(self) -> Dict[str, str]:
|
|
|
|
| 143 |
return {
|
| 144 |
model_id: f"{config['name']} ({config['description']})"
|
| 145 |
for model_id, config in self.available_models.items()
|
|
|
|
| 147 |
|
| 148 |
@property
|
| 149 |
def quality_options(self) -> Dict[str, str]:
|
|
|
|
| 150 |
return {
|
| 151 |
quality_id: config["description"]
|
| 152 |
for quality_id, config in QUALITY_SETTINGS.items()
|
utils/metrics_collector.py
CHANGED
|
@@ -20,7 +20,10 @@ class MetricsCollector:
|
|
| 20 |
self.failed_renders = 0
|
| 21 |
self.render_times: List[float] = []
|
| 22 |
self.error_counts = defaultdict(int)
|
| 23 |
-
self.quality_usage =
|
|
|
|
|
|
|
|
|
|
| 24 |
self.scene_types = defaultdict(int)
|
| 25 |
self.start_time = time.time()
|
| 26 |
|
|
|
|
| 20 |
self.failed_renders = 0
|
| 21 |
self.render_times: List[float] = []
|
| 22 |
self.error_counts = defaultdict(int)
|
| 23 |
+
self.quality_usage = {
|
| 24 |
+
VideoQuality.LOW.value: 0,
|
| 25 |
+
VideoQuality.MEDIUM.value: 0
|
| 26 |
+
}
|
| 27 |
self.scene_types = defaultdict(int)
|
| 28 |
self.start_time = time.time()
|
| 29 |
|
utils/performance_optimizer.py
CHANGED
|
@@ -10,31 +10,29 @@ class PerformanceOptimizer:
|
|
| 10 |
|
| 11 |
QUALITY_SETTINGS = {
|
| 12 |
"low_quality": {
|
| 13 |
-
"
|
| 14 |
-
"
|
| 15 |
-
"
|
| 16 |
-
"description": "480p, 15fps - Fast rendering"
|
| 17 |
},
|
| 18 |
"medium_quality": {
|
| 19 |
-
"
|
| 20 |
-
"
|
| 21 |
-
"
|
| 22 |
-
"description": "720p, 30fps - Balanced"
|
| 23 |
-
},
|
| 24 |
-
"high_quality": {
|
| 25 |
-
"flag": "-qh",
|
| 26 |
-
"resolution": "1920x1080",
|
| 27 |
-
"frame_rate": 60,
|
| 28 |
-
"description": "1080p, 60fps - High quality"
|
| 29 |
-
},
|
| 30 |
-
"production_quality": {
|
| 31 |
-
"flag": "-qp",
|
| 32 |
-
"resolution": "3840x2160",
|
| 33 |
-
"frame_rate": 60,
|
| 34 |
-
"description": "4K, 60fps - Production ready"
|
| 35 |
}
|
| 36 |
}
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
@classmethod
|
| 39 |
def estimate_render_time(cls, quality: str, complexity: str = "medium") -> int:
|
| 40 |
"""Estimate rendering time in seconds"""
|
|
@@ -162,4 +160,11 @@ class PerformanceOptimizer:
|
|
| 162 |
tips.append("Close other applications to free up system resources")
|
| 163 |
tips.append("Ensure sufficient disk space for temporary files")
|
| 164 |
|
| 165 |
-
return tips
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
QUALITY_SETTINGS = {
|
| 12 |
"low_quality": {
|
| 13 |
+
"resolution": "480p",
|
| 14 |
+
"fps": 30,
|
| 15 |
+
"description": "480p, 30fps - Fast rendering"
|
|
|
|
| 16 |
},
|
| 17 |
"medium_quality": {
|
| 18 |
+
"resolution": "720p",
|
| 19 |
+
"fps": 30,
|
| 20 |
+
"description": "720p, 30fps - Balanced quality"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
}
|
| 23 |
|
| 24 |
+
# Performance thresholds
|
| 25 |
+
RENDER_TIME_THRESHOLDS = {
|
| 26 |
+
"low_quality": 60,
|
| 27 |
+
"medium_quality": 180
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
# Memory usage thresholds (in GB)
|
| 31 |
+
MEMORY_THRESHOLDS = {
|
| 32 |
+
"low_quality": 4.0,
|
| 33 |
+
"medium_quality": 6.0
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
@classmethod
|
| 37 |
def estimate_render_time(cls, quality: str, complexity: str = "medium") -> int:
|
| 38 |
"""Estimate rendering time in seconds"""
|
|
|
|
| 160 |
tips.append("Close other applications to free up system resources")
|
| 161 |
tips.append("Ensure sufficient disk space for temporary files")
|
| 162 |
|
| 163 |
+
return tips
|
| 164 |
+
|
| 165 |
+
def optimize_quality(self, available_memory: float, estimated_render_time: float) -> str:
|
| 166 |
+
if available_memory < self.MEMORY_THRESHOLDS["low_quality"]:
|
| 167 |
+
return "low_quality"
|
| 168 |
+
if estimated_render_time > self.RENDER_TIME_THRESHOLDS["medium_quality"]:
|
| 169 |
+
return "low_quality"
|
| 170 |
+
return "medium_quality"
|
utils/ui_components.py
CHANGED
|
@@ -18,7 +18,7 @@ def initialize_app():
|
|
| 18 |
.main-header {
|
| 19 |
text-align: center;
|
| 20 |
padding: 1rem 0;
|
| 21 |
-
background: linear-gradient(
|
| 22 |
color: white;
|
| 23 |
border-radius: 10px;
|
| 24 |
margin-bottom: 2rem;
|
|
@@ -60,7 +60,7 @@ def initialize_app():
|
|
| 60 |
def render_header():
|
| 61 |
st.markdown("""
|
| 62 |
<div class="main-header">
|
| 63 |
-
<h1>π¬
|
| 64 |
<p>Transform your ideas into mathematical animations</p>
|
| 65 |
</div>
|
| 66 |
""", unsafe_allow_html=True)
|
|
@@ -75,10 +75,8 @@ def render_sidebar():
|
|
| 75 |
st.write(f"{status} {key.replace('_', ' ').title()}: {value}")
|
| 76 |
|
| 77 |
model_options = {
|
| 78 |
-
"llama-3.3-70b-versatile": "Llama 3.3 70B (
|
| 79 |
-
"llama3-8b-8192": "Llama 3 8B (Fast)"
|
| 80 |
-
"mixtral-8x7b-32768": "Mixtral 8x7B (Creative)",
|
| 81 |
-
"gemma2-9b-it": "Gemma 2 9B (Efficient)"
|
| 82 |
}
|
| 83 |
selected_model = st.selectbox(
|
| 84 |
"AI Model",
|
|
@@ -143,9 +141,11 @@ def render_main_interface():
|
|
| 143 |
user_prompt = st.text_area(
|
| 144 |
"Describe the mathematical animation you want to create:",
|
| 145 |
height=150,
|
| 146 |
-
placeholder="Example: Create an animation showing the
|
| 147 |
help="Be as descriptive as possible for best results. Mention specific mathematical concepts, colors, and visual elements."
|
| 148 |
)
|
|
|
|
|
|
|
| 149 |
if user_prompt:
|
| 150 |
category = PromptTemplates.detect_category(user_prompt)
|
| 151 |
complexity = "complex" if len(user_prompt) > 100 else "medium"
|
|
@@ -154,10 +154,11 @@ def render_main_interface():
|
|
| 154 |
st.info(f"π Detected category: **{category.title()}**")
|
| 155 |
with col_info2:
|
| 156 |
st.info(f"π― Complexity: **{complexity.title()}**")
|
|
|
|
|
|
|
| 157 |
generate_button = st.button("π Generate Animation", type="primary", disabled=not user_prompt.strip())
|
| 158 |
|
| 159 |
-
|
| 160 |
-
return user_prompt, generate_button, None
|
| 161 |
|
| 162 |
def render_footer(generator=None):
|
| 163 |
st.markdown("---")
|
|
|
|
| 18 |
.main-header {
|
| 19 |
text-align: center;
|
| 20 |
padding: 1rem 0;
|
| 21 |
+
background: linear-gradient(70deg, #667eea 47%, #764ba2 100%);
|
| 22 |
color: white;
|
| 23 |
border-radius: 10px;
|
| 24 |
margin-bottom: 2rem;
|
|
|
|
| 60 |
def render_header():
|
| 61 |
st.markdown("""
|
| 62 |
<div class="main-header">
|
| 63 |
+
<h1>π¬ Manimate: Video Generator</h1>
|
| 64 |
<p>Transform your ideas into mathematical animations</p>
|
| 65 |
</div>
|
| 66 |
""", unsafe_allow_html=True)
|
|
|
|
| 75 |
st.write(f"{status} {key.replace('_', ' ').title()}: {value}")
|
| 76 |
|
| 77 |
model_options = {
|
| 78 |
+
"llama-3.3-70b-versatile": "Llama 3.3 70B (Most Capable)",
|
| 79 |
+
"llama3-8b-8192": "Llama 3 8B (Fast)"
|
|
|
|
|
|
|
| 80 |
}
|
| 81 |
selected_model = st.selectbox(
|
| 82 |
"AI Model",
|
|
|
|
| 141 |
user_prompt = st.text_area(
|
| 142 |
"Describe the mathematical animation you want to create:",
|
| 143 |
height=150,
|
| 144 |
+
placeholder="Example: Create an animation showing the derivation of Pythagorean theorem, with smooth transitions and clear labeling",
|
| 145 |
help="Be as descriptive as possible for best results. Mention specific mathematical concepts, colors, and visual elements."
|
| 146 |
)
|
| 147 |
+
|
| 148 |
+
# Show category and complexity info if there's input
|
| 149 |
if user_prompt:
|
| 150 |
category = PromptTemplates.detect_category(user_prompt)
|
| 151 |
complexity = "complex" if len(user_prompt) > 100 else "medium"
|
|
|
|
| 154 |
st.info(f"π Detected category: **{category.title()}**")
|
| 155 |
with col_info2:
|
| 156 |
st.info(f"π― Complexity: **{complexity.title()}**")
|
| 157 |
+
|
| 158 |
+
# Add a separate Generate button
|
| 159 |
generate_button = st.button("π Generate Animation", type="primary", disabled=not user_prompt.strip())
|
| 160 |
|
| 161 |
+
return user_prompt, generate_button
|
|
|
|
| 162 |
|
| 163 |
def render_footer(generator=None):
|
| 164 |
st.markdown("---")
|