basyx commited on
Commit
bf1fd55
·
verified ·
1 Parent(s): f852696

Update utils/render.py

Browse files
Files changed (1) hide show
  1. utils/render.py +80 -57
utils/render.py CHANGED
@@ -1,75 +1,98 @@
1
- import uuid
2
- from moviepy.editor import (
3
- VideoFileClip,
4
- TextClip,
5
- CompositeVideoClip
6
- )
 
7
 
8
  # ---------------------------------------------------
9
  # FONT CONFIG
10
  # ---------------------------------------------------
11
- FONT_PATH = "fonts/TikTok-Bold.ttf"
12
-
13
 
14
  # ---------------------------------------------------
15
- # CREATE TIKTOK STYLE SUBTITLE
16
  # ---------------------------------------------------
17
- def create_text_clip(word, start, end, video_w, video_h):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- txt_clip = (
20
- TextClip(
21
- txt=word.upper(),
22
- fontsize=72,
23
- font=FONT_PATH,
24
- color="white",
25
- stroke_color="black",
26
- stroke_width=6,
27
- method="caption", # REQUIRED
28
- size=(int(video_w * 0.85), None)
29
- )
30
- .set_start(start)
31
- .set_end(end)
32
- .set_position(("center", int(video_h * 0.75)))
33
- )
34
-
35
- return txt_clip
36
 
 
 
 
 
 
 
37
 
38
- # ---------------------------------------------------
39
- # MAIN RENDER FUNCTION
40
- # ---------------------------------------------------
41
- def burn_subtitles(video_path, words):
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- output_path = f"/tmp/render_{uuid.uuid4().hex}.mp4"
 
 
44
 
45
- video = VideoFileClip(video_path)
 
46
 
47
- subtitle_clips = []
 
48
 
49
- for w in words:
50
- subtitle_clips.append(
51
- create_text_clip(
52
- w["word"],
53
- w["start"],
54
- w["end"],
55
- video.w,
56
- video.h
57
- )
 
58
  )
59
 
60
- final = CompositeVideoClip([video, *subtitle_clips])
61
-
62
- final.write_videofile(
63
- output_path,
64
- codec="libx264",
65
- audio_codec="aac",
66
- fps=video.fps,
67
- preset="veryfast",
68
- threads=2,
69
- logger=None
70
- )
71
 
72
- video.close()
73
- final.close()
74
 
75
- return output_path
 
 
 
 
 
1
+ import os, uuid
2
+ # Force ImageMagick path for Linux
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
  # ---------------------------------------------------
10
  # FONT CONFIG
11
  # ---------------------------------------------------
12
+ FONT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "fonts", "TikTok-Bold.ttf"))
 
13
 
14
  # ---------------------------------------------------
15
+ # MAIN RENDER FUNCTION
16
  # ---------------------------------------------------
17
+ def burn_subtitles(video_path, highlight_frames):
18
+ """
19
+ Renders TikTok-style captions.
20
+ highlight_frames: List of dicts with 'full_frame_text', 'active_word_index', 'start', 'end'
21
+ """
22
+ original_cwd = os.getcwd()
23
+ os.chdir("/tmp") # Permission fix for HF Spaces
24
+
25
+ try:
26
+ output_path = os.path.abspath(f"/tmp/render_{uuid.uuid4().hex}.mp4")
27
+ video = VideoFileClip(video_path)
28
+ w, h = int(video.w), int(video.h)
29
+
30
+ # Verify font exists
31
+ current_font = FONT_PATH if os.path.exists(FONT_PATH) else "sans-serif"
32
+
33
+ subtitle_clips = []
34
 
35
+ for frame in highlight_frames:
36
+ words = frame["full_frame_text"]
37
+ active_idx = frame["active_word_index"]
38
+ duration = frame["end"] - frame["start"]
39
+
40
+ if duration <= 0: continue
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ # Build Pango Markup for single-word yellow highlight
43
+ markup_parts = [
44
+ f"<span foreground='yellow'>{word}</span>" if i == active_idx else word
45
+ for i, word in enumerate(words)
46
+ ]
47
+ pango_markup = " ".join(markup_parts)
48
 
49
+ # Create the TextClip with Size 30
50
+ txt_clip = (
51
+ TextClip(
52
+ text=pango_markup,
53
+ font=current_font,
54
+ font_size=30, # Set to 30 as requested
55
+ color="white",
56
+ stroke_color="black",
57
+ stroke_width=1.0, # Scaled down for size 30
58
+ method="pango", # Changed to pango for the highlight logic
59
+ size=(int(w * 0.80), None),
60
+ text_align="center"
61
+ )
62
+ .with_start(frame["start"])
63
+ .with_duration(duration)
64
+ .with_position(("center", int(h * 0.80))) # Lowered for minimalist look
65
+ )
66
 
67
+ # Rhythmic 'Pop' Effect
68
+ txt_clip = Resize(lambda t: 1.10 if t < 0.08 else 1.0).apply(txt_clip)
69
+ subtitle_clips.append(txt_clip)
70
 
71
+ # Composite and Export
72
+ final = CompositeVideoClip([video, *subtitle_clips], size=(w, h))
73
 
74
+ # Explicit Temp Audio Config to prevent "Invalid Stream" errors
75
+ temp_audio = os.path.join("/tmp", f"audio_tmp_{uuid.uuid4().hex}.m4a")
76
 
77
+ final.write_videofile(
78
+ output_path,
79
+ codec="libx264",
80
+ audio_codec="aac",
81
+ fps=video.fps,
82
+ preset="ultrafast", # Faster for HF CPU
83
+ threads=4,
84
+ temp_audiofile=temp_audio,
85
+ remove_temp=True,
86
+ logger=None
87
  )
88
 
89
+ video.close()
90
+ final.close()
 
 
 
 
 
 
 
 
 
91
 
92
+ return output_path
 
93
 
94
+ except Exception as e:
95
+ logger.error(f"Render Engine Error: {e}")
96
+ raise e
97
+ finally:
98
+ os.chdir(original_cwd)