basyx commited on
Commit
5bbf36c
·
verified ·
1 Parent(s): a31916d

Update utils/render.py

Browse files
Files changed (1) hide show
  1. utils/render.py +38 -45
utils/render.py CHANGED
@@ -1,94 +1,87 @@
1
  import os
 
 
 
2
  from moviepy import VideoFileClip, TextClip, CompositeVideoClip
 
3
  from .logger import logger
4
 
5
  def render_tiktok_video(video_path, highlight_frames, output_path):
6
- """
7
- Renders TikTok-style captions with word-level highlighting and bounce effects.
8
- Uses Pango markup for efficient styling.
9
- """
10
  try:
11
  clip = VideoFileClip(video_path)
12
  w, h = clip.size
13
  final_clips = [clip]
14
 
15
- # Font fallback for Linux/Slim environments
16
- font_to_use = "DejaVu-Sans-Bold"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  for frame in highlight_frames:
19
  words = frame["full_frame_text"]
20
  active_idx = frame["active_word_index"]
21
  duration = frame["end"] - frame["start"]
22
 
23
- if duration <= 0:
24
- continue
25
 
26
- # --- PANGO MARKUP CONSTRUCTION ---
27
- # We build a string like: WHITE <span foreground='yellow'>ACTIVE</span> WHITE
28
- markup_parts = []
29
- for i, word in enumerate(words):
30
- if i == active_idx:
31
- markup_parts.append(f"<span foreground='yellow'>{word}</span>")
32
- else:
33
- markup_parts.append(word)
34
-
35
  pango_markup = " ".join(markup_parts)
36
 
37
- # --- BOUNCE EFFECT LOGIC ---
38
- # We use a simple scale function: start at 1.15x and settle to 1.0x quickly
39
  def bounce_effect(t):
40
- if t < 0.08: # Rapid scale up/down in first 80ms
41
- return 1.15
42
- return 1.0
43
 
44
- # --- CREATE TEXT CLIP ---
45
  txt_clip = (TextClip(
46
  text=pango_markup,
47
- font=font_to_use,
48
  font_size=80,
49
- color='white', # Base color
50
  stroke_color='black',
51
  stroke_width=2,
52
- method='pango', # CRITICAL: Enables the span tags
53
  size=(w * 0.85, None),
54
  text_align='center'
55
  )
56
  .with_start(frame["start"])
57
  .with_duration(duration)
58
- .with_position(('center', h * 0.72))
59
- # Applying the "Pop" bounce effect
60
- .multiply_speed(1.0) # Reset internal clock
61
- .with_effects([lambda c: c.transform(lambda get_frame, t:
62
- c.get_frame(t), apply_to=[],
63
- # Note: In MoviePy v2, complex resizing is often best
64
- # handled via the Resize effect directly.
65
- )])
66
- )
67
 
68
- # Simple Resize pop for v2.0
69
- from moviepy.video.fx import Resize
70
  txt_clip = Resize(lambda t: bounce_effect(t)).apply(txt_clip)
71
-
72
  final_clips.append(txt_clip)
73
 
74
- # --- COMPOSITE & EXPORT ---
75
- logger.info(f"Exporting video to {output_path}...")
76
  final_video = CompositeVideoClip(final_clips)
77
-
78
  final_video.write_videofile(
79
  output_path,
80
  codec="libx264",
81
  audio_codec="aac",
82
  fps=clip.fps,
83
- preset="ultrafast", # Necessary for CPU-limited HF Spaces
84
  threads=4,
85
- logger=None # Prevent console spamming
86
  )
87
 
88
- # Cleanup memory
89
  clip.close()
90
  final_video.close()
91
-
92
  return output_path
93
 
94
  except Exception as e:
 
1
  import os
2
+ # Explicitly set the binary path
3
+ os.environ["IMAGEMAGICK_BINARY"] = "/usr/bin/convert"
4
+
5
  from moviepy import VideoFileClip, TextClip, CompositeVideoClip
6
+ from moviepy.video.fx import Resize
7
  from .logger import logger
8
 
9
  def render_tiktok_video(video_path, highlight_frames, output_path):
 
 
 
 
10
  try:
11
  clip = VideoFileClip(video_path)
12
  w, h = clip.size
13
  final_clips = [clip]
14
 
15
+ # --- SMART FONT SELECTION ---
16
+ # We try explicit paths first, then generic names
17
+ potential_fonts = [
18
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
19
+ "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf",
20
+ "DejaVu-Sans-Bold",
21
+ "Arial",
22
+ "sans-serif"
23
+ ]
24
+
25
+ font_to_use = "sans-serif" # Absolute fallback
26
+ for f in potential_fonts:
27
+ # If it's a path, check if it exists
28
+ if f.startswith("/") and os.path.exists(f):
29
+ font_to_use = f
30
+ break
31
+ # Otherwise, we just hope the name works
32
+ font_to_use = f
33
 
34
  for frame in highlight_frames:
35
  words = frame["full_frame_text"]
36
  active_idx = frame["active_word_index"]
37
  duration = frame["end"] - frame["start"]
38
 
39
+ if duration <= 0: continue
 
40
 
41
+ # Pango Markup
42
+ markup_parts = [
43
+ f"<span foreground='yellow'>{word}</span>" if i == active_idx else word
44
+ for i, word in enumerate(words)
45
+ ]
 
 
 
 
46
  pango_markup = " ".join(markup_parts)
47
 
 
 
48
  def bounce_effect(t):
49
+ return 1.15 if t < 0.08 else 1.0
 
 
50
 
51
+ # Create TextClip
52
  txt_clip = (TextClip(
53
  text=pango_markup,
54
+ font=font_to_use, # Use our discovered font
55
  font_size=80,
56
+ color='white',
57
  stroke_color='black',
58
  stroke_width=2,
59
+ method='pango',
60
  size=(w * 0.85, None),
61
  text_align='center'
62
  )
63
  .with_start(frame["start"])
64
  .with_duration(duration)
65
+ .with_position(('center', h * 0.72)))
 
 
 
 
 
 
 
 
66
 
67
+ # Apply Bounce
 
68
  txt_clip = Resize(lambda t: bounce_effect(t)).apply(txt_clip)
 
69
  final_clips.append(txt_clip)
70
 
71
+ # Composite and Export
 
72
  final_video = CompositeVideoClip(final_clips)
 
73
  final_video.write_videofile(
74
  output_path,
75
  codec="libx264",
76
  audio_codec="aac",
77
  fps=clip.fps,
78
+ preset="ultrafast",
79
  threads=4,
80
+ logger=None
81
  )
82
 
 
83
  clip.close()
84
  final_video.close()
 
85
  return output_path
86
 
87
  except Exception as e: