Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -26,6 +26,10 @@ import pandas as pd
|
|
| 26 |
import plotly.express as px
|
| 27 |
import markdown
|
| 28 |
import zipfile
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
# Set up enhanced logging
|
| 31 |
logging.basicConfig(
|
|
@@ -63,7 +67,6 @@ def ensure_packages():
|
|
| 63 |
'matplotlib': '3.5.0', # For Python script runner
|
| 64 |
'seaborn': '0.11.2', # For enhanced visualizations
|
| 65 |
'scipy': '1.7.3', # For scientific computations
|
| 66 |
-
'accelerate': '0.20.0',
|
| 67 |
}
|
| 68 |
|
| 69 |
with st.spinner("Checking required packages..."):
|
|
@@ -302,7 +305,7 @@ QUALITY_PRESETS = {
|
|
| 302 |
"720p": {"resolution": "720p", "fps": "30"},
|
| 303 |
"1080p": {"resolution": "1080p", "fps": "60"},
|
| 304 |
"4K": {"resolution": "2160p", "fps": "60"},
|
| 305 |
-
"8K": {"resolution": "4320p", "fps": "60"}
|
| 306 |
}
|
| 307 |
|
| 308 |
# Animation speeds
|
|
@@ -510,7 +513,7 @@ def generate_manim_video(python_code, format_type, quality_preset, animation_spe
|
|
| 510 |
"720p": "-qm", # Medium quality
|
| 511 |
"1080p": "-qh", # High quality
|
| 512 |
"4K": "-qk", # 4K quality
|
| 513 |
-
"8K": "-
|
| 514 |
}
|
| 515 |
quality_flag = quality_map.get(quality_preset, "-qm")
|
| 516 |
|
|
@@ -1170,7 +1173,7 @@ def create_timeline_editor(code):
|
|
| 1170 |
animation_steps = df.to_dict('records')
|
| 1171 |
new_code = generate_code_from_timeline(animation_steps, code)
|
| 1172 |
|
| 1173 |
-
st.success("Timeline updated!
|
| 1174 |
return new_code
|
| 1175 |
|
| 1176 |
# Visual keyframe editor
|
|
@@ -1467,32 +1470,6 @@ def export_to_educational_format(video_data, format_type, animation_title, expla
|
|
| 1467 |
logger.error(traceback.format_exc())
|
| 1468 |
return None, None
|
| 1469 |
|
| 1470 |
-
def process_multiple_images(uploaded_images):
|
| 1471 |
-
"""Process multiple uploaded images and return information about them"""
|
| 1472 |
-
processed_images = []
|
| 1473 |
-
|
| 1474 |
-
# Create a unique image directory if it doesn't exist
|
| 1475 |
-
image_dir = os.path.join(os.getcwd(), "manim_assets", "images")
|
| 1476 |
-
os.makedirs(image_dir, exist_ok=True)
|
| 1477 |
-
|
| 1478 |
-
for img in uploaded_images:
|
| 1479 |
-
# Generate a unique filename and save the image
|
| 1480 |
-
file_extension = img.name.split(".")[-1]
|
| 1481 |
-
unique_filename = f"image_{int(time.time())}_{len(processed_images)}.{file_extension}"
|
| 1482 |
-
image_path = os.path.join(image_dir, unique_filename)
|
| 1483 |
-
|
| 1484 |
-
with open(image_path, "wb") as f:
|
| 1485 |
-
f.write(img.getvalue())
|
| 1486 |
-
|
| 1487 |
-
# Store image info
|
| 1488 |
-
processed_images.append({
|
| 1489 |
-
"name": img.name,
|
| 1490 |
-
"path": image_path,
|
| 1491 |
-
"preview": img
|
| 1492 |
-
})
|
| 1493 |
-
|
| 1494 |
-
return processed_images
|
| 1495 |
-
|
| 1496 |
def main():
|
| 1497 |
# Initialize session state variables if they don't exist
|
| 1498 |
if 'init' not in st.session_state:
|
|
@@ -1511,13 +1488,12 @@ def main():
|
|
| 1511 |
st.session_state.custom_library_result = ""
|
| 1512 |
st.session_state.python_script = "import matplotlib.pyplot as plt\nimport numpy as np\n\n# Example: Create a simple plot\nx = np.linspace(0, 10, 100)\ny = np.sin(x)\n\nplt.figure(figsize=(10, 6))\nplt.plot(x, y, 'b-', label='sin(x)')\nplt.title('Sine Wave')\nplt.xlabel('x')\nplt.ylabel('sin(x)')\nplt.grid(True)\nplt.legend()\n"
|
| 1513 |
st.session_state.python_result = None
|
|
|
|
| 1514 |
st.session_state.settings = {
|
| 1515 |
"quality": "720p",
|
| 1516 |
"format_type": "mp4",
|
| 1517 |
"animation_speed": "Normal"
|
| 1518 |
}
|
| 1519 |
-
st.session_state.pending_changes = False # Track if there are changes that need refresh
|
| 1520 |
-
st.session_state.pending_temp_code = "" # Store code changes before refresh
|
| 1521 |
|
| 1522 |
# Page configuration with improved layout
|
| 1523 |
st.set_page_config(
|
|
@@ -1625,41 +1601,6 @@ def main():
|
|
| 1625 |
margin-top: 1rem;
|
| 1626 |
border-left: 4px solid #ef4444;
|
| 1627 |
}
|
| 1628 |
-
|
| 1629 |
-
.refresh-button {
|
| 1630 |
-
background-color: #4CAF50;
|
| 1631 |
-
color: white;
|
| 1632 |
-
padding: 10px 20px;
|
| 1633 |
-
border: none;
|
| 1634 |
-
border-radius: 5px;
|
| 1635 |
-
cursor: pointer;
|
| 1636 |
-
font-weight: bold;
|
| 1637 |
-
margin: 10px 0;
|
| 1638 |
-
}
|
| 1639 |
-
|
| 1640 |
-
.refresh-button:hover {
|
| 1641 |
-
background-color: #45a049;
|
| 1642 |
-
}
|
| 1643 |
-
|
| 1644 |
-
.image-grid {
|
| 1645 |
-
display: grid;
|
| 1646 |
-
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
| 1647 |
-
gap: 10px;
|
| 1648 |
-
margin-top: 10px;
|
| 1649 |
-
}
|
| 1650 |
-
|
| 1651 |
-
.image-item {
|
| 1652 |
-
border: 1px solid #ddd;
|
| 1653 |
-
border-radius: 5px;
|
| 1654 |
-
padding: 5px;
|
| 1655 |
-
text-align: center;
|
| 1656 |
-
}
|
| 1657 |
-
|
| 1658 |
-
.image-item img {
|
| 1659 |
-
max-width: 100%;
|
| 1660 |
-
height: auto;
|
| 1661 |
-
border-radius: 3px;
|
| 1662 |
-
}
|
| 1663 |
</style>
|
| 1664 |
""", unsafe_allow_html=True)
|
| 1665 |
|
|
@@ -1684,18 +1625,11 @@ def main():
|
|
| 1684 |
st.session_state.ai_models = init_ai_models()
|
| 1685 |
|
| 1686 |
# Create main tabs
|
| 1687 |
-
|
|
|
|
| 1688 |
|
| 1689 |
-
# Sidebar for rendering settings
|
| 1690 |
with st.sidebar:
|
| 1691 |
-
# Add refresh button at the top of the sidebar
|
| 1692 |
-
if st.button("🔄 Refresh App", key="refresh_button", help="Refresh the app to apply all changes", use_container_width=True):
|
| 1693 |
-
st.success("App refreshed!")
|
| 1694 |
-
if st.session_state.pending_changes:
|
| 1695 |
-
st.session_state.code = st.session_state.pending_temp_code
|
| 1696 |
-
st.session_state.temp_code = st.session_state.pending_temp_code
|
| 1697 |
-
st.session_state.pending_changes = False
|
| 1698 |
-
|
| 1699 |
# Rendering settings section
|
| 1700 |
st.markdown("## ⚙️ Rendering Settings")
|
| 1701 |
|
|
@@ -1775,36 +1709,31 @@ def main():
|
|
| 1775 |
if uploaded_file:
|
| 1776 |
code_content = uploaded_file.getvalue().decode("utf-8")
|
| 1777 |
if code_content.strip(): # Only update if file has content
|
| 1778 |
-
st.session_state.
|
| 1779 |
-
st.session_state.
|
| 1780 |
-
st.info("Code loaded from file. Click 'Refresh App' to apply changes.")
|
| 1781 |
|
| 1782 |
# Code editor
|
| 1783 |
if ACE_EDITOR_AVAILABLE:
|
| 1784 |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else ""
|
| 1785 |
-
temp_code = st_ace(
|
| 1786 |
value=current_code,
|
| 1787 |
language="python",
|
| 1788 |
theme="monokai",
|
| 1789 |
min_lines=20,
|
| 1790 |
key="ace_editor"
|
| 1791 |
)
|
| 1792 |
-
if temp_code != current_code:
|
| 1793 |
-
st.session_state.pending_temp_code = temp_code
|
| 1794 |
-
st.session_state.pending_changes = True
|
| 1795 |
-
st.info("Code changes detected. Click 'Refresh App' to apply changes.")
|
| 1796 |
else:
|
| 1797 |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else ""
|
| 1798 |
-
temp_code = st.text_area(
|
| 1799 |
"Manim Python Code",
|
| 1800 |
value=current_code,
|
| 1801 |
height=400,
|
| 1802 |
key="code_textarea"
|
| 1803 |
)
|
| 1804 |
-
|
| 1805 |
-
|
| 1806 |
-
|
| 1807 |
-
|
| 1808 |
|
| 1809 |
# Generate button (use a form to prevent page reloads)
|
| 1810 |
generate_btn = st.button("🚀 Generate Animation", use_container_width=True, key="generate_btn")
|
|
@@ -1825,9 +1754,8 @@ class MyScene(Scene):
|
|
| 1825 |
self.wait(2)
|
| 1826 |
"""
|
| 1827 |
st.session_state.code += default_scene
|
| 1828 |
-
st.session_state.
|
| 1829 |
-
st.
|
| 1830 |
-
st.warning("No scene class found. Added a default scene. Click 'Refresh App' to see changes.")
|
| 1831 |
|
| 1832 |
with st.spinner("Generating animation..."):
|
| 1833 |
video_data, status = generate_manim_video(
|
|
@@ -1915,7 +1843,7 @@ class MyScene(Scene):
|
|
| 1915 |
1. **Syntax Errors**: Check your Python code for any syntax issues
|
| 1916 |
2. **Missing Scene Class**: Ensure your code contains a scene class that extends Scene
|
| 1917 |
3. **High Resolution Issues**: Try a lower quality preset for complex animations
|
| 1918 |
-
4. **Memory Issues**: For 4K
|
| 1919 |
5. **Format Issues**: Some formats require specific Manim configurations
|
| 1920 |
6. **GIF Generation**: If GIF doesn't work, try MP4 and we'll convert it automatically
|
| 1921 |
|
|
@@ -1987,9 +1915,11 @@ class MyScene(Scene):
|
|
| 1987 |
col_ai1, col_ai2 = st.columns(2)
|
| 1988 |
with col_ai1:
|
| 1989 |
if st.button("Use This Code", key="use_gen_code"):
|
| 1990 |
-
st.session_state.
|
| 1991 |
-
st.session_state.
|
| 1992 |
-
|
|
|
|
|
|
|
| 1993 |
|
| 1994 |
with col_ai2:
|
| 1995 |
if st.button("Render Preview", key="render_preview"):
|
|
@@ -2032,9 +1962,8 @@ class MyScene(Scene):
|
|
| 2032 |
key="latex_input"
|
| 2033 |
)
|
| 2034 |
|
| 2035 |
-
# Update session state
|
| 2036 |
-
|
| 2037 |
-
st.session_state.latex_formula = latex_input
|
| 2038 |
|
| 2039 |
# Common LaTeX formulas library
|
| 2040 |
st.markdown("#### Formula Library")
|
|
@@ -2082,9 +2011,10 @@ class MyScene(Scene):
|
|
| 2082 |
with formula_tabs[i]:
|
| 2083 |
for formula in formulas:
|
| 2084 |
if st.button(formula["name"], key=f"latex_{formula['name']}"):
|
|
|
|
| 2085 |
st.session_state.latex_formula = formula["latex"]
|
| 2086 |
-
#
|
| 2087 |
-
st.
|
| 2088 |
|
| 2089 |
# LaTeX code snippet
|
| 2090 |
st.markdown("#### Manim Code Snippet")
|
|
@@ -2122,10 +2052,13 @@ self.wait(2)
|
|
| 2122 |
# If we didn't find content, append to the end with default indentation
|
| 2123 |
lines.append(" " + "\n ".join(manim_latex_code.strip().split("\n")))
|
| 2124 |
|
| 2125 |
-
|
| 2126 |
-
st.session_state.
|
| 2127 |
-
st.
|
| 2128 |
-
|
|
|
|
|
|
|
|
|
|
| 2129 |
else:
|
| 2130 |
st.warning("Could not find 'construct' method in your code. Please add a scene class first.")
|
| 2131 |
else:
|
|
@@ -2139,9 +2072,13 @@ class LatexScene(Scene):
|
|
| 2139 |
self.play(Write(formula))
|
| 2140 |
self.wait(2)
|
| 2141 |
"""
|
| 2142 |
-
st.session_state.
|
| 2143 |
-
st.session_state.
|
| 2144 |
-
st.success("Created new scene with your LaTeX formula
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2145 |
|
| 2146 |
with col_latex2:
|
| 2147 |
# LaTeX preview
|
|
@@ -2183,79 +2120,102 @@ class LatexScene(Scene):
|
|
| 2183 |
st.markdown("#### 📸 Image Assets")
|
| 2184 |
st.markdown("Upload images to use in your animations:")
|
| 2185 |
|
| 2186 |
-
#
|
| 2187 |
-
uploaded_images = st.file_uploader(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2188 |
|
| 2189 |
if uploaded_images:
|
| 2190 |
-
#
|
| 2191 |
-
|
| 2192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2193 |
|
| 2194 |
-
|
|
|
|
|
|
|
|
|
|
| 2195 |
if "image_paths" not in st.session_state:
|
| 2196 |
st.session_state.image_paths = []
|
| 2197 |
|
| 2198 |
-
|
| 2199 |
-
|
| 2200 |
-
|
| 2201 |
-
|
|
|
|
|
|
|
|
|
|
| 2202 |
|
| 2203 |
-
|
| 2204 |
-
|
| 2205 |
-
|
| 2206 |
-
|
| 2207 |
-
|
| 2208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2209 |
|
| 2210 |
-
|
| 2211 |
-
|
| 2212 |
-
|
| 2213 |
-
|
| 2214 |
-
|
| 2215 |
-
|
| 2216 |
-
|
| 2217 |
-
image{i+1} = ImageMobject(r"{img_info['path']}")
|
| 2218 |
-
image{i+1}.scale(2) # Adjust size as needed
|
| 2219 |
-
self.play(FadeIn(image{i+1}))
|
| 2220 |
self.wait(1)
|
| 2221 |
"""
|
| 2222 |
-
|
| 2223 |
-
|
| 2224 |
-
all_images_code = "\n".join(image_code_samples)
|
| 2225 |
-
st.code(all_images_code, language="python")
|
| 2226 |
-
|
| 2227 |
-
if st.button("Insert All Images Code", key="insert_images_code_btn"):
|
| 2228 |
-
if not st.session_state.code:
|
| 2229 |
-
base_code = """from manim import *
|
| 2230 |
|
| 2231 |
class ImageScene(Scene):
|
| 2232 |
def construct(self):
|
| 2233 |
"""
|
| 2234 |
-
|
| 2235 |
-
|
| 2236 |
-
|
| 2237 |
-
|
| 2238 |
-
|
| 2239 |
-
|
| 2240 |
-
|
| 2241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2242 |
|
| 2243 |
# Display previously uploaded images
|
| 2244 |
if st.session_state.image_paths:
|
| 2245 |
-
st.
|
| 2246 |
-
|
| 2247 |
-
|
| 2248 |
-
|
| 2249 |
-
|
| 2250 |
-
|
| 2251 |
-
|
| 2252 |
-
|
| 2253 |
-
|
| 2254 |
-
|
| 2255 |
-
|
| 2256 |
-
|
| 2257 |
-
|
| 2258 |
-
|
|
|
|
| 2259 |
|
| 2260 |
with asset_col2:
|
| 2261 |
# Audio uploader section
|
|
@@ -2341,8 +2301,8 @@ class YourScene(Scene):
|
|
| 2341 |
|
| 2342 |
# If code was modified by the timeline editor, update the session state
|
| 2343 |
if updated_code != st.session_state.code:
|
| 2344 |
-
st.session_state.
|
| 2345 |
-
st.session_state.
|
| 2346 |
|
| 2347 |
# EDUCATIONAL EXPORT TAB
|
| 2348 |
with tabs[5]:
|
|
@@ -2626,11 +2586,13 @@ plt.legend()
|
|
| 2626 |
|
| 2627 |
# Python code editor
|
| 2628 |
if selected_example != "Select an example..." and selected_example in example_scripts:
|
| 2629 |
-
|
|
|
|
|
|
|
| 2630 |
|
| 2631 |
if ACE_EDITOR_AVAILABLE:
|
| 2632 |
python_code = st_ace(
|
| 2633 |
-
value=
|
| 2634 |
language="python",
|
| 2635 |
theme="monokai",
|
| 2636 |
min_lines=15,
|
|
@@ -2639,12 +2601,12 @@ plt.legend()
|
|
| 2639 |
else:
|
| 2640 |
python_code = st.text_area(
|
| 2641 |
"Python Code",
|
| 2642 |
-
value=
|
| 2643 |
height=400,
|
| 2644 |
key="python_textarea"
|
| 2645 |
)
|
| 2646 |
|
| 2647 |
-
#
|
| 2648 |
st.session_state.python_script = python_code
|
| 2649 |
|
| 2650 |
# Check for input() calls
|
|
@@ -2707,20 +2669,25 @@ self.wait(1)
|
|
| 2707 |
"""
|
| 2708 |
# Insert into editor code
|
| 2709 |
if st.session_state.code:
|
| 2710 |
-
|
| 2711 |
-
st.session_state.
|
| 2712 |
-
st.
|
| 2713 |
-
|
|
|
|
|
|
|
| 2714 |
else:
|
| 2715 |
basic_scene = f"""from manim import *
|
| 2716 |
|
| 2717 |
class PlotScene(Scene):
|
| 2718 |
def construct(self):
|
| 2719 |
-
{plot_code}
|
| 2720 |
"""
|
| 2721 |
-
st.session_state.
|
| 2722 |
-
st.session_state.
|
| 2723 |
-
st.success(f"Created new scene with Plot {i+1}!
|
|
|
|
|
|
|
|
|
|
| 2724 |
|
| 2725 |
# Provide option to save the script
|
| 2726 |
if st.button("📄 Save This Script", key="save_script_btn"):
|
|
@@ -2795,6 +2762,13 @@ class PlotScene(Scene):
|
|
| 2795 |
self.wait(2)
|
| 2796 |
```
|
| 2797 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2798 |
|
| 2799 |
if __name__ == "__main__":
|
| 2800 |
main()
|
|
|
|
| 26 |
import plotly.express as px
|
| 27 |
import markdown
|
| 28 |
import zipfile
|
| 29 |
+
import contextlib
|
| 30 |
+
import threading
|
| 31 |
+
import traceback
|
| 32 |
+
from io import StringIO, BytesIO
|
| 33 |
|
| 34 |
# Set up enhanced logging
|
| 35 |
logging.basicConfig(
|
|
|
|
| 67 |
'matplotlib': '3.5.0', # For Python script runner
|
| 68 |
'seaborn': '0.11.2', # For enhanced visualizations
|
| 69 |
'scipy': '1.7.3', # For scientific computations
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
with st.spinner("Checking required packages..."):
|
|
|
|
| 305 |
"720p": {"resolution": "720p", "fps": "30"},
|
| 306 |
"1080p": {"resolution": "1080p", "fps": "60"},
|
| 307 |
"4K": {"resolution": "2160p", "fps": "60"},
|
| 308 |
+
"8K": {"resolution": "4320p", "fps": "60"} # Added 8K option
|
| 309 |
}
|
| 310 |
|
| 311 |
# Animation speeds
|
|
|
|
| 513 |
"720p": "-qm", # Medium quality
|
| 514 |
"1080p": "-qh", # High quality
|
| 515 |
"4K": "-qk", # 4K quality
|
| 516 |
+
"8K": "-qp" # 8K quality (production quality)
|
| 517 |
}
|
| 518 |
quality_flag = quality_map.get(quality_preset, "-qm")
|
| 519 |
|
|
|
|
| 1173 |
animation_steps = df.to_dict('records')
|
| 1174 |
new_code = generate_code_from_timeline(animation_steps, code)
|
| 1175 |
|
| 1176 |
+
st.success("Timeline updated! Code has been regenerated.")
|
| 1177 |
return new_code
|
| 1178 |
|
| 1179 |
# Visual keyframe editor
|
|
|
|
| 1470 |
logger.error(traceback.format_exc())
|
| 1471 |
return None, None
|
| 1472 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1473 |
def main():
|
| 1474 |
# Initialize session state variables if they don't exist
|
| 1475 |
if 'init' not in st.session_state:
|
|
|
|
| 1488 |
st.session_state.custom_library_result = ""
|
| 1489 |
st.session_state.python_script = "import matplotlib.pyplot as plt\nimport numpy as np\n\n# Example: Create a simple plot\nx = np.linspace(0, 10, 100)\ny = np.sin(x)\n\nplt.figure(figsize=(10, 6))\nplt.plot(x, y, 'b-', label='sin(x)')\nplt.title('Sine Wave')\nplt.xlabel('x')\nplt.ylabel('sin(x)')\nplt.grid(True)\nplt.legend()\n"
|
| 1490 |
st.session_state.python_result = None
|
| 1491 |
+
st.session_state.active_tab = 0 # Track currently active tab
|
| 1492 |
st.session_state.settings = {
|
| 1493 |
"quality": "720p",
|
| 1494 |
"format_type": "mp4",
|
| 1495 |
"animation_speed": "Normal"
|
| 1496 |
}
|
|
|
|
|
|
|
| 1497 |
|
| 1498 |
# Page configuration with improved layout
|
| 1499 |
st.set_page_config(
|
|
|
|
| 1601 |
margin-top: 1rem;
|
| 1602 |
border-left: 4px solid #ef4444;
|
| 1603 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1604 |
</style>
|
| 1605 |
""", unsafe_allow_html=True)
|
| 1606 |
|
|
|
|
| 1625 |
st.session_state.ai_models = init_ai_models()
|
| 1626 |
|
| 1627 |
# Create main tabs
|
| 1628 |
+
tab_names = ["✨ Editor", "🤖 AI Assistant", "📚 LaTeX Formulas", "🎨 Assets", "🎞️ Timeline", "🎓 Educational Export", "🐍 Python Runner"]
|
| 1629 |
+
tabs = st.tabs(tab_names)
|
| 1630 |
|
| 1631 |
+
# Sidebar for rendering settings and custom libraries
|
| 1632 |
with st.sidebar:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1633 |
# Rendering settings section
|
| 1634 |
st.markdown("## ⚙️ Rendering Settings")
|
| 1635 |
|
|
|
|
| 1709 |
if uploaded_file:
|
| 1710 |
code_content = uploaded_file.getvalue().decode("utf-8")
|
| 1711 |
if code_content.strip(): # Only update if file has content
|
| 1712 |
+
st.session_state.code = code_content
|
| 1713 |
+
st.session_state.temp_code = code_content
|
|
|
|
| 1714 |
|
| 1715 |
# Code editor
|
| 1716 |
if ACE_EDITOR_AVAILABLE:
|
| 1717 |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else ""
|
| 1718 |
+
st.session_state.temp_code = st_ace(
|
| 1719 |
value=current_code,
|
| 1720 |
language="python",
|
| 1721 |
theme="monokai",
|
| 1722 |
min_lines=20,
|
| 1723 |
key="ace_editor"
|
| 1724 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1725 |
else:
|
| 1726 |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else ""
|
| 1727 |
+
st.session_state.temp_code = st.text_area(
|
| 1728 |
"Manim Python Code",
|
| 1729 |
value=current_code,
|
| 1730 |
height=400,
|
| 1731 |
key="code_textarea"
|
| 1732 |
)
|
| 1733 |
+
|
| 1734 |
+
# Update code in session state if it changed
|
| 1735 |
+
if st.session_state.temp_code != st.session_state.code:
|
| 1736 |
+
st.session_state.code = st.session_state.temp_code
|
| 1737 |
|
| 1738 |
# Generate button (use a form to prevent page reloads)
|
| 1739 |
generate_btn = st.button("🚀 Generate Animation", use_container_width=True, key="generate_btn")
|
|
|
|
| 1754 |
self.wait(2)
|
| 1755 |
"""
|
| 1756 |
st.session_state.code += default_scene
|
| 1757 |
+
st.session_state.temp_code = st.session_state.code
|
| 1758 |
+
st.warning("No scene class found. Added a default scene.")
|
|
|
|
| 1759 |
|
| 1760 |
with st.spinner("Generating animation..."):
|
| 1761 |
video_data, status = generate_manim_video(
|
|
|
|
| 1843 |
1. **Syntax Errors**: Check your Python code for any syntax issues
|
| 1844 |
2. **Missing Scene Class**: Ensure your code contains a scene class that extends Scene
|
| 1845 |
3. **High Resolution Issues**: Try a lower quality preset for complex animations
|
| 1846 |
+
4. **Memory Issues**: For 4K animations, reduce complexity or try again
|
| 1847 |
5. **Format Issues**: Some formats require specific Manim configurations
|
| 1848 |
6. **GIF Generation**: If GIF doesn't work, try MP4 and we'll convert it automatically
|
| 1849 |
|
|
|
|
| 1915 |
col_ai1, col_ai2 = st.columns(2)
|
| 1916 |
with col_ai1:
|
| 1917 |
if st.button("Use This Code", key="use_gen_code"):
|
| 1918 |
+
st.session_state.code = st.session_state.generated_code
|
| 1919 |
+
st.session_state.temp_code = st.session_state.generated_code
|
| 1920 |
+
# Switch to Editor tab
|
| 1921 |
+
st.session_state.active_tab = 0
|
| 1922 |
+
st.rerun()
|
| 1923 |
|
| 1924 |
with col_ai2:
|
| 1925 |
if st.button("Render Preview", key="render_preview"):
|
|
|
|
| 1962 |
key="latex_input"
|
| 1963 |
)
|
| 1964 |
|
| 1965 |
+
# Update session state
|
| 1966 |
+
st.session_state.latex_formula = latex_input
|
|
|
|
| 1967 |
|
| 1968 |
# Common LaTeX formulas library
|
| 1969 |
st.markdown("#### Formula Library")
|
|
|
|
| 2011 |
with formula_tabs[i]:
|
| 2012 |
for formula in formulas:
|
| 2013 |
if st.button(formula["name"], key=f"latex_{formula['name']}"):
|
| 2014 |
+
# Insert formula into the text area
|
| 2015 |
st.session_state.latex_formula = formula["latex"]
|
| 2016 |
+
# Rerun to update the text area and preview
|
| 2017 |
+
st.rerun()
|
| 2018 |
|
| 2019 |
# LaTeX code snippet
|
| 2020 |
st.markdown("#### Manim Code Snippet")
|
|
|
|
| 2052 |
# If we didn't find content, append to the end with default indentation
|
| 2053 |
lines.append(" " + "\n ".join(manim_latex_code.strip().split("\n")))
|
| 2054 |
|
| 2055 |
+
st.session_state.code = "\n".join(lines)
|
| 2056 |
+
st.session_state.temp_code = st.session_state.code
|
| 2057 |
+
st.success("LaTeX formula inserted into the editor!")
|
| 2058 |
+
|
| 2059 |
+
# Switch to Editor tab
|
| 2060 |
+
st.session_state.active_tab = 0
|
| 2061 |
+
st.rerun()
|
| 2062 |
else:
|
| 2063 |
st.warning("Could not find 'construct' method in your code. Please add a scene class first.")
|
| 2064 |
else:
|
|
|
|
| 2072 |
self.play(Write(formula))
|
| 2073 |
self.wait(2)
|
| 2074 |
"""
|
| 2075 |
+
st.session_state.code = basic_scene
|
| 2076 |
+
st.session_state.temp_code = basic_scene
|
| 2077 |
+
st.success("Created new scene with your LaTeX formula!")
|
| 2078 |
+
|
| 2079 |
+
# Switch to Editor tab
|
| 2080 |
+
st.session_state.active_tab = 0
|
| 2081 |
+
st.rerun()
|
| 2082 |
|
| 2083 |
with col_latex2:
|
| 2084 |
# LaTeX preview
|
|
|
|
| 2120 |
st.markdown("#### 📸 Image Assets")
|
| 2121 |
st.markdown("Upload images to use in your animations:")
|
| 2122 |
|
| 2123 |
+
# Allow multiple image uploads
|
| 2124 |
+
uploaded_images = st.file_uploader(
|
| 2125 |
+
"Upload Images",
|
| 2126 |
+
type=["jpg", "png", "jpeg", "svg"],
|
| 2127 |
+
accept_multiple_files=True,
|
| 2128 |
+
key="image_uploader_tab"
|
| 2129 |
+
)
|
| 2130 |
|
| 2131 |
if uploaded_images:
|
| 2132 |
+
# Create a unique image directory if it doesn't exist
|
| 2133 |
+
image_dir = os.path.join(os.getcwd(), "manim_assets", "images")
|
| 2134 |
+
os.makedirs(image_dir, exist_ok=True)
|
| 2135 |
+
|
| 2136 |
+
# Process each uploaded image
|
| 2137 |
+
for uploaded_image in uploaded_images:
|
| 2138 |
+
# Generate a unique filename and save the image
|
| 2139 |
+
file_extension = uploaded_image.name.split(".")[-1]
|
| 2140 |
+
unique_filename = f"image_{int(time.time())}_{uuid.uuid4().hex[:8]}.{file_extension}"
|
| 2141 |
+
image_path = os.path.join(image_dir, unique_filename)
|
| 2142 |
|
| 2143 |
+
with open(image_path, "wb") as f:
|
| 2144 |
+
f.write(uploaded_image.getvalue())
|
| 2145 |
+
|
| 2146 |
+
# Store the path in session state
|
| 2147 |
if "image_paths" not in st.session_state:
|
| 2148 |
st.session_state.image_paths = []
|
| 2149 |
|
| 2150 |
+
# Check if this image was already added
|
| 2151 |
+
# Check if this image was already added
|
| 2152 |
+
image_already_added = False
|
| 2153 |
+
for img in st.session_state.image_paths:
|
| 2154 |
+
if img["name"] == uploaded_image.name:
|
| 2155 |
+
image_already_added = True
|
| 2156 |
+
break
|
| 2157 |
|
| 2158 |
+
if not image_already_added:
|
| 2159 |
+
st.session_state.image_paths.append({
|
| 2160 |
+
"name": uploaded_image.name,
|
| 2161 |
+
"path": image_path
|
| 2162 |
+
})
|
| 2163 |
+
|
| 2164 |
+
# Display uploaded images in a grid
|
| 2165 |
+
st.markdown("##### Uploaded Images:")
|
| 2166 |
+
image_cols = st.columns(3)
|
| 2167 |
+
|
| 2168 |
+
for i, img_info in enumerate(st.session_state.image_paths[-len(uploaded_images):]):
|
| 2169 |
+
with image_cols[i % 3]:
|
| 2170 |
+
try:
|
| 2171 |
+
img = Image.open(img_info["path"])
|
| 2172 |
+
st.image(img, caption=img_info["name"], width=150)
|
| 2173 |
|
| 2174 |
+
# Show code snippet for this specific image
|
| 2175 |
+
if st.button(f"Use {img_info['name']}", key=f"use_img_{i}"):
|
| 2176 |
+
image_code = f"""
|
| 2177 |
+
# Load and display image
|
| 2178 |
+
image = ImageMobject(r"{img_info['path']}")
|
| 2179 |
+
image.scale(2) # Adjust size as needed
|
| 2180 |
+
self.play(FadeIn(image))
|
|
|
|
|
|
|
|
|
|
| 2181 |
self.wait(1)
|
| 2182 |
"""
|
| 2183 |
+
if not st.session_state.code:
|
| 2184 |
+
base_code = """from manim import *
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2185 |
|
| 2186 |
class ImageScene(Scene):
|
| 2187 |
def construct(self):
|
| 2188 |
"""
|
| 2189 |
+
st.session_state.code = base_code + "\n " + image_code.replace("\n", "\n ")
|
| 2190 |
+
else:
|
| 2191 |
+
st.session_state.code += "\n" + image_code
|
| 2192 |
+
|
| 2193 |
+
st.session_state.temp_code = st.session_state.code
|
| 2194 |
+
st.success(f"Added {img_info['name']} to your code!")
|
| 2195 |
+
|
| 2196 |
+
# Switch to Editor tab
|
| 2197 |
+
st.session_state.active_tab = 0
|
| 2198 |
+
st.rerun()
|
| 2199 |
+
except Exception as e:
|
| 2200 |
+
st.error(f"Error loading image {img_info['name']}: {e}")
|
| 2201 |
|
| 2202 |
# Display previously uploaded images
|
| 2203 |
if st.session_state.image_paths:
|
| 2204 |
+
with st.expander("Previously Uploaded Images"):
|
| 2205 |
+
# Group images by 3 in each row
|
| 2206 |
+
for i in range(0, len(st.session_state.image_paths), 3):
|
| 2207 |
+
prev_cols = st.columns(3)
|
| 2208 |
+
for j in range(3):
|
| 2209 |
+
if i+j < len(st.session_state.image_paths):
|
| 2210 |
+
img_info = st.session_state.image_paths[i+j]
|
| 2211 |
+
with prev_cols[j]:
|
| 2212 |
+
try:
|
| 2213 |
+
img = Image.open(img_info["path"])
|
| 2214 |
+
st.image(img, caption=img_info["name"], width=100)
|
| 2215 |
+
st.markdown(f"<div class='small-text'>Path: {img_info['path']}</div>", unsafe_allow_html=True)
|
| 2216 |
+
except:
|
| 2217 |
+
st.markdown(f"**{img_info['name']}**")
|
| 2218 |
+
st.markdown(f"<div class='small-text'>Path: {img_info['path']}</div>", unsafe_allow_html=True)
|
| 2219 |
|
| 2220 |
with asset_col2:
|
| 2221 |
# Audio uploader section
|
|
|
|
| 2301 |
|
| 2302 |
# If code was modified by the timeline editor, update the session state
|
| 2303 |
if updated_code != st.session_state.code:
|
| 2304 |
+
st.session_state.code = updated_code
|
| 2305 |
+
st.session_state.temp_code = updated_code
|
| 2306 |
|
| 2307 |
# EDUCATIONAL EXPORT TAB
|
| 2308 |
with tabs[5]:
|
|
|
|
| 2586 |
|
| 2587 |
# Python code editor
|
| 2588 |
if selected_example != "Select an example..." and selected_example in example_scripts:
|
| 2589 |
+
python_code = example_scripts[selected_example]
|
| 2590 |
+
else:
|
| 2591 |
+
python_code = st.session_state.python_script
|
| 2592 |
|
| 2593 |
if ACE_EDITOR_AVAILABLE:
|
| 2594 |
python_code = st_ace(
|
| 2595 |
+
value=python_code,
|
| 2596 |
language="python",
|
| 2597 |
theme="monokai",
|
| 2598 |
min_lines=15,
|
|
|
|
| 2601 |
else:
|
| 2602 |
python_code = st.text_area(
|
| 2603 |
"Python Code",
|
| 2604 |
+
value=python_code,
|
| 2605 |
height=400,
|
| 2606 |
key="python_textarea"
|
| 2607 |
)
|
| 2608 |
|
| 2609 |
+
# Store script in session state (without clearing existing code)
|
| 2610 |
st.session_state.python_script = python_code
|
| 2611 |
|
| 2612 |
# Check for input() calls
|
|
|
|
| 2669 |
"""
|
| 2670 |
# Insert into editor code
|
| 2671 |
if st.session_state.code:
|
| 2672 |
+
st.session_state.code += "\n" + plot_code
|
| 2673 |
+
st.session_state.temp_code = st.session_state.code
|
| 2674 |
+
st.success(f"Plot {i+1} added to your animation code!")
|
| 2675 |
+
# Switch to Editor tab
|
| 2676 |
+
st.session_state.active_tab = 0
|
| 2677 |
+
st.rerun()
|
| 2678 |
else:
|
| 2679 |
basic_scene = f"""from manim import *
|
| 2680 |
|
| 2681 |
class PlotScene(Scene):
|
| 2682 |
def construct(self):
|
| 2683 |
+
{plot_code}
|
| 2684 |
"""
|
| 2685 |
+
st.session_state.code = basic_scene
|
| 2686 |
+
st.session_state.temp_code = basic_scene
|
| 2687 |
+
st.success(f"Created new scene with Plot {i+1}!")
|
| 2688 |
+
# Switch to Editor tab
|
| 2689 |
+
st.session_state.active_tab = 0
|
| 2690 |
+
st.rerun()
|
| 2691 |
|
| 2692 |
# Provide option to save the script
|
| 2693 |
if st.button("📄 Save This Script", key="save_script_btn"):
|
|
|
|
| 2762 |
self.wait(2)
|
| 2763 |
```
|
| 2764 |
""")
|
| 2765 |
+
|
| 2766 |
+
# Restore active tab if specified
|
| 2767 |
+
if st.session_state.active_tab is not None and st.session_state.active_tab < len(tabs):
|
| 2768 |
+
for i, tab in enumerate(tabs):
|
| 2769 |
+
if i == st.session_state.active_tab:
|
| 2770 |
+
tab.active = True
|
| 2771 |
+
break
|
| 2772 |
|
| 2773 |
if __name__ == "__main__":
|
| 2774 |
main()
|