Spaces:
Sleeping
Sleeping
Update utils/render.py
Browse files- utils/render.py +23 -19
utils/render.py
CHANGED
|
@@ -1,38 +1,48 @@
|
|
| 1 |
import os
|
|
|
|
| 2 |
os.environ["IMAGEMAGICK_BINARY"] = "/usr/bin/convert"
|
| 3 |
|
| 4 |
from moviepy import VideoFileClip, TextClip, CompositeVideoClip
|
| 5 |
from moviepy.video.fx import Resize
|
| 6 |
from .logger import logger
|
| 7 |
|
|
|
|
|
|
|
|
|
|
| 8 |
def render_tiktok_video(video_path, highlight_frames, output_path):
|
| 9 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
clip = VideoFileClip(video_path)
|
| 11 |
w, h = clip.size
|
| 12 |
final_clips = [clip]
|
| 13 |
|
| 14 |
-
# Use a standard font name that ImageMagick always knows
|
| 15 |
-
font_to_use = "Arial-Bold"
|
| 16 |
-
|
| 17 |
for frame in highlight_frames:
|
| 18 |
words = frame["full_frame_text"]
|
| 19 |
active_idx = frame["active_word_index"]
|
| 20 |
duration = frame["end"] - frame["start"]
|
| 21 |
-
|
| 22 |
if duration <= 0: continue
|
| 23 |
|
| 24 |
-
# Build the full sentence for this frame
|
| 25 |
full_text = " ".join(words)
|
| 26 |
|
| 27 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
base_txt = (TextClip(
|
| 29 |
text=full_text,
|
| 30 |
-
font=
|
| 31 |
font_size=80,
|
| 32 |
color='white',
|
| 33 |
stroke_color='black',
|
| 34 |
stroke_width=2,
|
| 35 |
-
method='caption',
|
| 36 |
size=(w * 0.85, None),
|
| 37 |
text_align='center'
|
| 38 |
)
|
|
@@ -40,14 +50,12 @@ def render_tiktok_video(video_path, highlight_frames, output_path):
|
|
| 40 |
.with_duration(duration)
|
| 41 |
.with_position(('center', h * 0.72)))
|
| 42 |
|
| 43 |
-
# 2.
|
| 44 |
-
# Since Pango failed, we highlight the entire chunk in yellow
|
| 45 |
-
# for the active word's duration. This still looks very "TikTok".
|
| 46 |
hi_txt = (TextClip(
|
| 47 |
text=full_text,
|
| 48 |
-
font=
|
| 49 |
font_size=80,
|
| 50 |
-
color='yellow',
|
| 51 |
stroke_color='black',
|
| 52 |
stroke_width=2,
|
| 53 |
method='caption',
|
|
@@ -57,18 +65,14 @@ def render_tiktok_video(video_path, highlight_frames, output_path):
|
|
| 57 |
.with_start(frame["start"])
|
| 58 |
.with_duration(duration)
|
| 59 |
.with_position(('center', h * 0.72)))
|
| 60 |
-
|
| 61 |
-
# Apply the Bounce "Pop" Effect to the Highlight
|
| 62 |
-
def bounce_effect(t):
|
| 63 |
-
return 1.15 if t < 0.08 else 1.0
|
| 64 |
|
|
|
|
| 65 |
hi_txt = Resize(lambda t: bounce_effect(t)).apply(hi_txt)
|
| 66 |
|
| 67 |
-
# Add both to the stack
|
| 68 |
final_clips.append(base_txt)
|
| 69 |
final_clips.append(hi_txt)
|
| 70 |
|
| 71 |
-
# Rendering
|
| 72 |
final_video = CompositeVideoClip(final_clips)
|
| 73 |
final_video.write_videofile(
|
| 74 |
output_path,
|
|
|
|
| 1 |
import os
|
| 2 |
+
# Force ImageMagick path for Linux environments
|
| 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 |
+
# Get the absolute path to your bundled font
|
| 10 |
+
FONT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "fonts", "TikTok-Bold.ttf"))
|
| 11 |
+
|
| 12 |
def render_tiktok_video(video_path, highlight_frames, output_path):
|
| 13 |
try:
|
| 14 |
+
# Final sanity check: does the font actually exist?
|
| 15 |
+
if not os.path.exists(FONT_PATH):
|
| 16 |
+
logger.warning(f"Bundled font not found at {FONT_PATH}, falling back to generic.")
|
| 17 |
+
current_font = "sans-serif"
|
| 18 |
+
else:
|
| 19 |
+
current_font = FONT_PATH
|
| 20 |
+
|
| 21 |
clip = VideoFileClip(video_path)
|
| 22 |
w, h = clip.size
|
| 23 |
final_clips = [clip]
|
| 24 |
|
|
|
|
|
|
|
|
|
|
| 25 |
for frame in highlight_frames:
|
| 26 |
words = frame["full_frame_text"]
|
| 27 |
active_idx = frame["active_word_index"]
|
| 28 |
duration = frame["end"] - frame["start"]
|
|
|
|
| 29 |
if duration <= 0: continue
|
| 30 |
|
|
|
|
| 31 |
full_text = " ".join(words)
|
| 32 |
|
| 33 |
+
# Define the "Bounce" effect (Standard TikTok Pop)
|
| 34 |
+
def bounce_effect(t):
|
| 35 |
+
return 1.15 if t < 0.08 else 1.0
|
| 36 |
+
|
| 37 |
+
# 1. Base White Layer
|
| 38 |
base_txt = (TextClip(
|
| 39 |
text=full_text,
|
| 40 |
+
font=current_font,
|
| 41 |
font_size=80,
|
| 42 |
color='white',
|
| 43 |
stroke_color='black',
|
| 44 |
stroke_width=2,
|
| 45 |
+
method='caption',
|
| 46 |
size=(w * 0.85, None),
|
| 47 |
text_align='center'
|
| 48 |
)
|
|
|
|
| 50 |
.with_duration(duration)
|
| 51 |
.with_position(('center', h * 0.72)))
|
| 52 |
|
| 53 |
+
# 2. Yellow Highlight Layer
|
|
|
|
|
|
|
| 54 |
hi_txt = (TextClip(
|
| 55 |
text=full_text,
|
| 56 |
+
font=current_font,
|
| 57 |
font_size=80,
|
| 58 |
+
color='yellow',
|
| 59 |
stroke_color='black',
|
| 60 |
stroke_width=2,
|
| 61 |
method='caption',
|
|
|
|
| 65 |
.with_start(frame["start"])
|
| 66 |
.with_duration(duration)
|
| 67 |
.with_position(('center', h * 0.72)))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
+
# Apply pop effect to the yellow layer
|
| 70 |
hi_txt = Resize(lambda t: bounce_effect(t)).apply(hi_txt)
|
| 71 |
|
|
|
|
| 72 |
final_clips.append(base_txt)
|
| 73 |
final_clips.append(hi_txt)
|
| 74 |
|
| 75 |
+
# Rendering with MoviePy v2.x
|
| 76 |
final_video = CompositeVideoClip(final_clips)
|
| 77 |
final_video.write_videofile(
|
| 78 |
output_path,
|