Spaces:
Running
Running
File size: 7,496 Bytes
3689207 12e6c81 e2f218a 3689207 05c2df9 3689207 e2f218a 3f1ca1c e2f218a 3689207 12e6c81 3689207 990badf 3689207 e2f218a 3f1ca1c e2f218a 3f1ca1c e2f218a 3f1ca1c e2f218a 3f1ca1c 3689207 12e6c81 e2f218a 3f1ca1c 3689207 e2f218a 3f1ca1c e2f218a 3f1ca1c e2f218a 3f1ca1c e2f218a 1b5bfb7 e2f218a 3689207 05c2df9 3689207 e2f218a 3689207 e2f218a 3f1ca1c e2f218a 3f1ca1c e2f218a 3689207 e2f218a e6d9ed9 3689207 e2f218a 3689207 990badf 8bd2e7e e2f218a 8bd2e7e 990badf e2f218a 8bd2e7e e2f218a 990badf e2f218a e6d9ed9 e2f218a 990badf e2f218a 3f1ca1c e2f218a 990badf e2f218a 990badf e2f218a 3689207 | 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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | 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) |