AnimateMyImages / app.py
devendergarg14's picture
Update app.py
990badf verified
import gradio as gr
import os
import subprocess
import time
import shutil
import glob
import re
# ---------------------------------------------------------
# 1. Helper Functions
# ---------------------------------------------------------
def cleanup_media_directory():
"""Removes the old media directory to prevent clutter."""
media_dir = 'media'
if os.path.exists(media_dir):
try:
shutil.rmtree(media_dir)
except OSError:
pass
def make_even(n):
return int(n) if int(n) % 2 == 0 else int(n) + 1
def get_resolution_flags(orientation, quality):
"""Calculates exact width and height based on orientation and quality."""
if orientation == "Carousel Portrait (4:5)":
res_map = {
"Preview (360p)": (288, 360),
"480p": (384, 480),
"720p": (576, 720),
"1080p": (1080, 1350), # Exactly 1080x1350 for 4:5 carousels
"4k": (2160, 2700)
}
elif orientation == "Portrait (9:16)":
res_map = {
"Preview (360p)": (360, 640),
"480p": (480, 854),
"720p": (720, 1280),
"1080p": (1080, 1920),
"4k": (2160, 3840)
}
else: # Landscape (16:9)
res_map = {
"Preview (360p)": (640, 360),
"480p": (854, 480),
"720p": (1280, 720),
"1080p": (1920, 1080),
"4k": (3840, 2160)
}
w, h = res_map.get(quality, (1080, 1350))
return f"{make_even(w)},{make_even(h)}"
def get_scene_names_in_order(code_str):
"""Parses the code to find the order of Scene classes defined."""
pattern = r"class\s+([A-Za-z0-9_]+)\s*\([^)]*\):"
return re.findall(pattern, code_str)
def run_manim(code_str, orientation, quality):
"""
Executes Manim to render all Scene classes as separate PNG images.
"""
print(f"🎨 Starting Carousel Render: {orientation} @ {quality}...", flush=True)
with open("scene.py", "w", encoding="utf-8") as f:
f.write(code_str)
res_str = get_resolution_flags(orientation, quality)
# -a renders all scenes in the file
# -s instructs Manim to save the last frame of each scene as an image
cmd = [
"manim",
"--resolution", res_str,
"--disable_caching",
"--progress_bar", "none",
"scene.py",
"-a", "-s"
]
full_logs = ""
try:
process = subprocess.run(cmd, capture_output=True, timeout=60, check=False)
stdout_log = process.stdout.decode('utf-8', 'ignore')
stderr_log = process.stderr.decode('utf-8', 'ignore')
full_logs = f"--- MANIM STDOUT ---\n{stdout_log}\n\n--- MANIM STDERR ---\n{stderr_log}"
if process.returncode != 0:
print(f"❌ Render Failed (Process Error). Return Code: {process.returncode}", flush=True)
except subprocess.TimeoutExpired:
print("⌛ Render timed out.", flush=True)
return [], "❌ Failure: Render Timed Out.", False
# Manim saves images into the media/images/scene folder
media_image_base = os.path.join("media", "images", "scene")
generated_images = []
if os.path.exists(media_image_base):
# Extract scene names in the exact order they were written in the code
expected_scenes = get_scene_names_in_order(code_str)
for scene_name in expected_scenes:
expected_img_path = os.path.join(media_image_base, f"{scene_name}.png")
if os.path.exists(expected_img_path):
generated_images.append(expected_img_path)
# Fallback: if regex missed anything, grab whatever is left in the folder
all_pngs = glob.glob(os.path.join(media_image_base, "*.png"))
for png in all_pngs:
if png not in generated_images:
generated_images.append(png)
if generated_images:
print(f"✅ Generated {len(generated_images)} images.", flush=True)
return generated_images, f"✅ Rendering Successful. Generated {len(generated_images)} slides.\n\n{full_logs}", True
return [], f"❌ Failure: No images were created.\n\n{full_logs}", False
# ---------------------------------------------------------
# 2. Main API Function
# ---------------------------------------------------------
def render_carousel_from_code(code, orientation, quality):
try:
cleanup_media_directory()
if not code or "from manim import" not in code:
return [], "Error: No valid Manim code provided."
images, logs, success = run_manim(code, orientation, quality)
return images, logs
except Exception as e:
return [], f"Rendering failed: {str(e)}"
# ---------------------------------------------------------
# 3. Gradio Interface
# ---------------------------------------------------------
# Carousel Example Code specifically tailored to look good on 4:5 1080x1350
DEFAULT_CODE = """from manim import *
class Slide1_Title(Scene):
def construct(self):
title = Text("Manim Carousel", font_size=64, color=YELLOW, weight=BOLD)
subtitle = Text("Swipe to see more ->", font_size=32).next_to(title, DOWN, buff=0.5)
# Self.add puts it on the screen immediately for the static PNG export
self.add(title, subtitle)
class Slide2_Shapes(Scene):
def construct(self):
circle = Circle(color=BLUE, fill_opacity=0.5, radius=2)
text = Text("High Quality Shapes", font_size=48).next_to(circle, DOWN, buff=1)
self.add(circle, text)
class Slide3_Conclusion(Scene):
def construct(self):
square = Square(color=RED, fill_opacity=0.5, side_length=4)
text = Text("Perfect 1080x1350 Output", font_size=48).next_to(square, DOWN, buff=1)
self.add(square, text)
"""
with gr.Blocks(title="Manim Carousel Generator") as demo:
gr.Markdown("## 🎠 Manim Carousel Image Generator")
gr.Markdown("Define **multiple `Scene` classes** in your code. Manim will render the final frame of *each* class and display them sequentially. Select `Carousel Portrait (4:5)` and `1080p` for exact **1080x1350** dimensions.")
with gr.Row():
with gr.Column(scale=1):
code_input = gr.Code(label="Python Code", language="python", value=DEFAULT_CODE)
orientation_opt = gr.Radio(
choices=["Landscape (16:9)", "Portrait (9:16)", "Carousel Portrait (4:5)"],
value="Carousel Portrait (4:5)", # Defaulted to Carousel 4:5
label="Orientation"
)
quality_opt = gr.Dropdown(
choices=["Preview (360p)", "480p", "720p", "1080p", "4k"],
value="1080p", # Defaulted to 1080p for 1080x1350 output
label="Quality"
)
render_btn = gr.Button("Generate Carousel", variant="primary")
with gr.Column(scale=1):
gallery_output = gr.Gallery(label="Carousel Output", columns=2, object_fit="contain", height="auto")
status_output = gr.Textbox(label="Status/Logs", lines=10)
render_btn.click(
fn=render_carousel_from_code,
inputs=[code_input, orientation_opt, quality_opt],
outputs=[gallery_output, status_output],
api_name="render_carousel"
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)