Bandrik0 commited on
Commit
f2a8d14
·
1 Parent(s): 8fd6f6f

tweak(draw): fill off + auto-stroke + lagged Create

Browse files
Files changed (1) hide show
  1. backend/renderer.py +43 -54
backend/renderer.py CHANGED
@@ -2,10 +2,8 @@ import os
2
  import tempfile
3
  import subprocess
4
  from pathlib import Path
5
- from typing import Tuple
6
  from fastapi import UploadFile
7
 
8
- # Manim kalite kısayolları
9
  _QUALITY = {"l": "l", "m": "m", "h": "h", "u": "u"}
10
 
11
  def _save_upload(tmpdir: Path, file: UploadFile) -> Path:
@@ -15,63 +13,72 @@ def _save_upload(tmpdir: Path, file: UploadFile) -> Path:
15
  f.write(file.file.read())
16
  return out
17
 
18
- def _gen_scene_py(
19
- path: Path, anim: str, duration: float, bg_color: str
20
- ) -> Path:
21
- """
22
- logo_scene.py dosyasını üretir ve yolunu döner.
23
- 'draw' yalnızca SVG kabul eder.
24
- """
25
  is_svg = path.suffix.lower() == ".svg"
26
- safe_path = repr(str(path)) # path'i güvenli biçimde göm
27
 
28
  if anim == "draw" and not is_svg:
29
- raise ValueError("Draw animasyonu yalnızca SVG ile çalışır.")
30
 
31
- # Ortak başlangıç
32
  head = f"""from manim import *
33
 
34
  class LogoScene(Scene):
35
  def construct(self):
36
  self.camera.background_color = "{bg_color}"
37
- """
38
 
39
  if anim == "draw":
 
40
  body = f"""
41
- m = SVGMobject({safe_path}).set_height(5).move_to(ORIGIN)
42
- self.play(Create(m), run_time={float(duration)}, rate_func=linear)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  self.wait(0.2)
44
  """
45
  else:
46
- # Raster ve SVG ikisi de desteklensin
47
  loader = (
48
- f'SVGMobject({safe_path}).set_height(5)'
49
  if is_svg else
50
  f'ImageMobject({safe_path}).scale(0.9)'
51
  )
52
  body_start = f"""m = {loader}
53
- m.move_to(ORIGIN)
54
- """
55
 
56
  if anim == "fade":
57
  body_anim = f"""
58
- self.play(FadeIn(m), run_time=max(0.3, {float(duration)}*0.5))
59
- self.wait(0.1)
60
- self.play(FadeOut(m), run_time=max(0.2, {float(duration)}*0.3))
61
- """
62
  elif anim == "spin":
63
  body_anim = f"""
64
- self.play(FadeIn(m, scale=0.8), run_time=max(0.2, {float(duration)}*0.2))
65
- self.play(Rotate(m, angle=2*PI), rate_func=linear, run_time=max(0.4, {float(duration)}*0.6))
66
- self.play(FadeOut(m), run_time=max(0.1, {float(duration)}*0.2))
67
- """
68
  elif anim == "bounce":
69
  body_anim = f"""
70
- from manim import UP, DOWN, there_and_back
71
- self.play(FadeIn(m, shift=UP*3), run_time=max(0.2, {float(duration)}*0.25))
72
- self.play(m.animate.shift(DOWN*3).scale(1.02), rate_func=there_and_back, run_time=max(0.4, {float(duration)}*0.55))
73
- self.play(FadeOut(m), run_time=max(0.15, {float(duration)}*0.2))
74
- """
75
  else:
76
  raise ValueError("Geçersiz animasyon tipi.")
77
 
@@ -83,52 +90,34 @@ class LogoScene(Scene):
83
  return scene_py
84
 
85
  def _run_manim(scene_py: Path, quality: str) -> Path:
86
- """
87
- Manim'i çalıştırır ve üretilen mp4 yolunu döndürür.
88
- """
89
  q = _QUALITY.get(quality, "m")
90
  tmpdir = scene_py.parent
91
  out_name = "out.mp4"
92
 
93
  cmd = [
94
- "manim",
95
- "-q", q,
96
- str(scene_py),
97
- "LogoScene",
98
  "-o", out_name,
99
  "--format=mp4",
100
  "--media_dir", str(tmpdir),
101
  ]
102
 
103
- proc = subprocess.run(
104
- cmd, cwd=tmpdir, capture_output=True, text=True
105
- )
106
  if proc.returncode != 0:
107
  raise RuntimeError(f"Manim failed: {proc.stderr or proc.stdout}")
108
 
109
- # media_dir altında mp4'ü bul
110
  for root, _, files in os.walk(tmpdir):
111
  if out_name in files:
112
  return Path(root) / out_name
113
 
114
  raise RuntimeError("Çıktı mp4 bulunamadı.")
115
 
116
- async def render_logo(
117
- file: UploadFile,
118
- animation: str,
119
- duration: float,
120
- bg_color: str,
121
- quality: str,
122
- ) -> Path:
123
- """
124
- FastAPI endpoint'inin kullandığı ana fonksiyon.
125
- """
126
  with tempfile.TemporaryDirectory() as tdir:
127
  tdir = Path(tdir)
128
  input_path = _save_upload(tdir, file)
129
  scene_py = _gen_scene_py(input_path, animation, duration, bg_color)
130
  mp4 = _run_manim(scene_py, quality)
131
- # mp4’ü kalıcı path’e taşı (tmp kapanınca silinmesin diye)
132
  final_dir = Path("/tmp/manim_out")
133
  final_dir.mkdir(parents=True, exist_ok=True)
134
  final_path = final_dir / mp4.name
 
2
  import tempfile
3
  import subprocess
4
  from pathlib import Path
 
5
  from fastapi import UploadFile
6
 
 
7
  _QUALITY = {"l": "l", "m": "m", "h": "h", "u": "u"}
8
 
9
  def _save_upload(tmpdir: Path, file: UploadFile) -> Path:
 
13
  f.write(file.file.read())
14
  return out
15
 
16
+ def _gen_scene_py(path: Path, anim: str, duration: float, bg_color: str) -> Path:
 
 
 
 
 
 
17
  is_svg = path.suffix.lower() == ".svg"
18
+ safe_path = repr(str(path))
19
 
20
  if anim == "draw" and not is_svg:
21
+ raise ValueError("Draw modu yalnızca SVG kabul eder.")
22
 
 
23
  head = f"""from manim import *
24
 
25
  class LogoScene(Scene):
26
  def construct(self):
27
  self.camera.background_color = "{bg_color}"
28
+ """
29
 
30
  if anim == "draw":
31
+ # Fill'leri kapat + stroke yoksa hafif bir stroke ata + parçalı çiz
32
  body = f"""
33
+ m = SVGMobject({safe_path}, should_center=True).set_height(5).move_to(ORIGIN)
34
+
35
+ # family_members_with_points: gerçekten çizilebilir alt-objeler
36
+ for sm in m.family_members_with_points():
37
+ # fill kapalı başlasın ki Create sırasında sadece çizgi görünsün
38
+ sm.set_fill(opacity=0)
39
+ # stroke yoksa otomatik ver (birçok SVG'de yalnızca fill var)
40
+ if sm.get_stroke_width() == 0:
41
+ sm.set_stroke(color=WHITE, width=3, opacity=1)
42
+
43
+ # parçalı çizim (birbirinin üstüne binecek şekilde)
44
+ self.play(
45
+ LaggedStartMap(Create, m.family_members_with_points(), lag_ratio=0.04, rate_func=linear),
46
+ run_time={float(duration)}
47
+ )
48
+
49
+ # çizim bittikten sonra dolguları geri getir
50
+ self.play(m.animate.set_fill(opacity=1), run_time=0.6)
51
  self.wait(0.2)
52
  """
53
  else:
 
54
  loader = (
55
+ f'SVGMobject({safe_path}, should_center=True).set_height(5)'
56
  if is_svg else
57
  f'ImageMobject({safe_path}).scale(0.9)'
58
  )
59
  body_start = f"""m = {loader}
60
+ m.move_to(ORIGIN)
61
+ """
62
 
63
  if anim == "fade":
64
  body_anim = f"""
65
+ self.play(FadeIn(m), run_time=max(0.3, {float(duration)}*0.5))
66
+ self.wait(0.1)
67
+ self.play(FadeOut(m), run_time=max(0.2, {float(duration)}*0.3))
68
+ """
69
  elif anim == "spin":
70
  body_anim = f"""
71
+ self.play(FadeIn(m, scale=0.8), run_time=max(0.2, {float(duration)}*0.2))
72
+ self.play(Rotate(m, angle=2*PI), rate_func=linear, run_time=max(0.4, {float(duration)}*0.6))
73
+ self.play(FadeOut(m), run_time=max(0.1, {float(duration)}*0.2))
74
+ """
75
  elif anim == "bounce":
76
  body_anim = f"""
77
+ from manim import UP, DOWN, there_and_back
78
+ self.play(FadeIn(m, shift=UP*3), run_time=max(0.2, {float(duration)}*0.25))
79
+ self.play(m.animate.shift(DOWN*3).scale(1.02), rate_func=there_and_back, run_time=max(0.4, {float(duration)}*0.55))
80
+ self.play(FadeOut(m), run_time=max(0.15, {float(duration)}*0.2))
81
+ """
82
  else:
83
  raise ValueError("Geçersiz animasyon tipi.")
84
 
 
90
  return scene_py
91
 
92
  def _run_manim(scene_py: Path, quality: str) -> Path:
 
 
 
93
  q = _QUALITY.get(quality, "m")
94
  tmpdir = scene_py.parent
95
  out_name = "out.mp4"
96
 
97
  cmd = [
98
+ "manim", "-q", q,
99
+ str(scene_py), "LogoScene",
 
 
100
  "-o", out_name,
101
  "--format=mp4",
102
  "--media_dir", str(tmpdir),
103
  ]
104
 
105
+ proc = subprocess.run(cmd, cwd=tmpdir, capture_output=True, text=True)
 
 
106
  if proc.returncode != 0:
107
  raise RuntimeError(f"Manim failed: {proc.stderr or proc.stdout}")
108
 
 
109
  for root, _, files in os.walk(tmpdir):
110
  if out_name in files:
111
  return Path(root) / out_name
112
 
113
  raise RuntimeError("Çıktı mp4 bulunamadı.")
114
 
115
+ async def render_logo(file: UploadFile, animation: str, duration: float, bg_color: str, quality: str) -> Path:
 
 
 
 
 
 
 
 
 
116
  with tempfile.TemporaryDirectory() as tdir:
117
  tdir = Path(tdir)
118
  input_path = _save_upload(tdir, file)
119
  scene_py = _gen_scene_py(input_path, animation, duration, bg_color)
120
  mp4 = _run_manim(scene_py, quality)
 
121
  final_dir = Path("/tmp/manim_out")
122
  final_dir.mkdir(parents=True, exist_ok=True)
123
  final_path = final_dir / mp4.name