|
|
import os |
|
|
import tempfile |
|
|
import subprocess |
|
|
from pathlib import Path |
|
|
from fastapi import UploadFile |
|
|
|
|
|
_QUALITY = {"l": "l", "m": "m", "h": "h", "u": "u"} |
|
|
|
|
|
def _save_upload(tmpdir: Path, file: UploadFile) -> Path: |
|
|
ext = Path(file.filename or "file").suffix.lower() |
|
|
out = tmpdir / f"input{ext}" |
|
|
with out.open("wb") as f: |
|
|
f.write(file.file.read()) |
|
|
return out |
|
|
|
|
|
def _gen_scene_py(path: Path, anim: str, duration: float, bg_color: str) -> Path: |
|
|
is_svg = path.suffix.lower() == ".svg" |
|
|
safe_path = repr(str(path)) |
|
|
|
|
|
if anim == "draw" and not is_svg: |
|
|
raise ValueError("Draw modu yalnızca SVG kabul eder.") |
|
|
|
|
|
head = f"""from manim import * |
|
|
|
|
|
class LogoScene(Scene): |
|
|
def construct(self): |
|
|
self.camera.background_color = "{bg_color}" |
|
|
""" |
|
|
|
|
|
if anim == "draw": |
|
|
|
|
|
body = f""" |
|
|
m = SVGMobject({safe_path}, should_center=True).set_height(5).move_to(ORIGIN) |
|
|
|
|
|
# family_members_with_points: gerçekten çizilebilir alt-objeler |
|
|
for sm in m.family_members_with_points(): |
|
|
# fill kapalı başlasın ki Create sırasında sadece çizgi görünsün |
|
|
sm.set_fill(opacity=0) |
|
|
# stroke yoksa otomatik ver (birçok SVG'de yalnızca fill var) |
|
|
if sm.get_stroke_width() == 0: |
|
|
sm.set_stroke(color=WHITE, width=3, opacity=1) |
|
|
|
|
|
# parçalı çizim (birbirinin üstüne binecek şekilde) |
|
|
self.play( |
|
|
LaggedStartMap(Create, m.family_members_with_points(), lag_ratio=0.04, rate_func=linear), |
|
|
run_time={float(duration)} |
|
|
) |
|
|
|
|
|
# çizim bittikten sonra dolguları geri getir |
|
|
self.play(m.animate.set_fill(opacity=1), run_time=0.6) |
|
|
self.wait(0.2) |
|
|
""" |
|
|
else: |
|
|
loader = ( |
|
|
f'SVGMobject({safe_path}, should_center=True).set_height(5)' |
|
|
if is_svg else |
|
|
f'ImageMobject({safe_path}).scale(0.9)' |
|
|
) |
|
|
body_start = f"""m = {loader} |
|
|
m.move_to(ORIGIN) |
|
|
""" |
|
|
|
|
|
if anim == "fade": |
|
|
body_anim = f""" |
|
|
self.play(FadeIn(m), run_time=max(0.3, {float(duration)}*0.5)) |
|
|
self.wait(0.1) |
|
|
self.play(FadeOut(m), run_time=max(0.2, {float(duration)}*0.3)) |
|
|
""" |
|
|
elif anim == "spin": |
|
|
body_anim = f""" |
|
|
self.play(FadeIn(m, scale=0.8), run_time=max(0.2, {float(duration)}*0.2)) |
|
|
self.play(Rotate(m, angle=2*PI), rate_func=linear, run_time=max(0.4, {float(duration)}*0.6)) |
|
|
self.play(FadeOut(m), run_time=max(0.1, {float(duration)}*0.2)) |
|
|
""" |
|
|
elif anim == "bounce": |
|
|
body_anim = f""" |
|
|
from manim import UP, DOWN, there_and_back |
|
|
self.play(FadeIn(m, shift=UP*3), run_time=max(0.2, {float(duration)}*0.25)) |
|
|
self.play(m.animate.shift(DOWN*3).scale(1.02), rate_func=there_and_back, run_time=max(0.4, {float(duration)}*0.55)) |
|
|
self.play(FadeOut(m), run_time=max(0.15, {float(duration)}*0.2)) |
|
|
""" |
|
|
else: |
|
|
raise ValueError("Geçersiz animasyon tipi.") |
|
|
|
|
|
body = body_start + body_anim |
|
|
|
|
|
code = head + body + "\n" |
|
|
scene_py = path.parent / "logo_scene.py" |
|
|
scene_py.write_text(code, encoding="utf-8") |
|
|
return scene_py |
|
|
|
|
|
def _run_manim(scene_py: Path, quality: str) -> Path: |
|
|
q = _QUALITY.get(quality, "m") |
|
|
tmpdir = scene_py.parent |
|
|
out_name = "out.mp4" |
|
|
|
|
|
cmd = [ |
|
|
"manim", "-q", q, |
|
|
str(scene_py), "LogoScene", |
|
|
"-o", out_name, |
|
|
"--format=mp4", |
|
|
"--media_dir", str(tmpdir), |
|
|
] |
|
|
|
|
|
proc = subprocess.run(cmd, cwd=tmpdir, capture_output=True, text=True) |
|
|
if proc.returncode != 0: |
|
|
raise RuntimeError(f"Manim failed: {proc.stderr or proc.stdout}") |
|
|
|
|
|
for root, _, files in os.walk(tmpdir): |
|
|
if out_name in files: |
|
|
return Path(root) / out_name |
|
|
|
|
|
raise RuntimeError("Çıktı mp4 bulunamadı.") |
|
|
|
|
|
async def render_logo(file: UploadFile, animation: str, duration: float, bg_color: str, quality: str) -> Path: |
|
|
with tempfile.TemporaryDirectory() as tdir: |
|
|
tdir = Path(tdir) |
|
|
input_path = _save_upload(tdir, file) |
|
|
scene_py = _gen_scene_py(input_path, animation, duration, bg_color) |
|
|
mp4 = _run_manim(scene_py, quality) |
|
|
final_dir = Path("/tmp/manim_out") |
|
|
final_dir.mkdir(parents=True, exist_ok=True) |
|
|
final_path = final_dir / mp4.name |
|
|
final_path.write_bytes(mp4.read_bytes()) |
|
|
return final_path |