auto-sync: 2026-04-30 02:10:01
This commit is contained in:
@@ -82,6 +82,69 @@ def _draw_text_centered(image: Image.Image, text: str,
|
||||
image.paste(overlay.convert("RGB"), (0, 0), overlay)
|
||||
|
||||
|
||||
def draw_karaoke_line(frame: Image.Image, text: str, words: list,
|
||||
t: float, font, y_ratio: float = 0.82):
|
||||
"""Рисует karaoke-строку: каждое слово своим цветом по таймингам.
|
||||
|
||||
Слова до текущего момента → жёлтые (255, 220, 0)
|
||||
Текущее слово → жёлтое + glow (чуть крупнее, ярче)
|
||||
Слова после → белые (255, 255, 255)
|
||||
"""
|
||||
if not words:
|
||||
# Fallback: нет word-timestamps — рисуем всю строку жёлтым
|
||||
_draw_text_centered(frame, text, font, font, True, 255, y_ratio=y_ratio, alpha=255)
|
||||
return
|
||||
|
||||
draw = ImageDraw.Draw(frame)
|
||||
|
||||
# Измеряем общую ширину
|
||||
total_width = sum(draw.textlength(w["word"] + " ", font=font) for w in words)
|
||||
x = (WIDTH - total_width) // 2
|
||||
y = int(HEIGHT * y_ratio)
|
||||
|
||||
COLOR_DONE = (255, 220, 0, 255) # уже пропето — жёлтый
|
||||
COLOR_WHITE = (255, 255, 255, 200) # ещё не пропето — белый
|
||||
COLOR_SHADOW = (0, 0, 0, 180)
|
||||
|
||||
overlay = Image.new("RGBA", (WIDTH, HEIGHT), (0, 0, 0, 0))
|
||||
od = ImageDraw.Draw(overlay)
|
||||
|
||||
# Глоу-шрифт (чуть крупнее для текущего слова)
|
||||
try:
|
||||
glow_font = ImageFont.truetype(FONT_ACTIVE, FONT_SIZE + 4)
|
||||
except Exception:
|
||||
glow_font = font
|
||||
|
||||
for w in words:
|
||||
word_text = w["word"] + " "
|
||||
ww = draw.textlength(word_text, font=font)
|
||||
|
||||
if t >= w["end"]:
|
||||
color = COLOR_DONE
|
||||
use_font = font
|
||||
elif t >= w["start"]:
|
||||
# Текущее слово — жёлтый + glow
|
||||
color = (255, 235, 50, 255) # чуть ярче
|
||||
use_font = glow_font
|
||||
# Glow: рисуем ещё раз с небольшим сдвигом и полупрозрачностью
|
||||
od.text((x - 1, y - 1), word_text, font=glow_font, fill=(255, 220, 0, 80))
|
||||
od.text((x + 1, y + 1), word_text, font=glow_font, fill=(255, 220, 0, 80))
|
||||
else:
|
||||
color = COLOR_WHITE
|
||||
use_font = font
|
||||
|
||||
# Тень
|
||||
od.text((x + 2, y + 2), word_text, font=use_font, fill=COLOR_SHADOW)
|
||||
# Основной текст
|
||||
od.text((x, y), word_text, font=use_font, fill=color)
|
||||
x = int(x + ww)
|
||||
|
||||
if frame.mode == "RGBA":
|
||||
frame.alpha_composite(overlay)
|
||||
else:
|
||||
frame.paste(overlay.convert("RGB"), (0, 0), overlay)
|
||||
|
||||
|
||||
def draw_text_with_alpha(image: Image.Image, text: str,
|
||||
font_active, font_inactive,
|
||||
alpha: int = 255, active: bool = True,
|
||||
@@ -308,15 +371,20 @@ def render_with_bg(segments: list[dict], audio_path: str, bg_video: str,
|
||||
gap = (next_seg["start"] - t) if next_seg else 999
|
||||
|
||||
if active_seg:
|
||||
# Активный сегмент — рисуем с fade in/out
|
||||
frames_from_start = int((t - active_seg["start"]) * fps)
|
||||
fade_alpha = min(255, int(255 * frames_from_start / max(FADE_FRAMES, 1)))
|
||||
frames_to_end = int((active_seg["end"] - t) * fps)
|
||||
fade_alpha = min(fade_alpha, int(255 * frames_to_end / max(FADE_FRAMES, 1)))
|
||||
# Активный сегмент — karaoke-эффект если есть word-timestamps
|
||||
has_words = isinstance(active_seg.get("words"), list) and len(active_seg.get("words", [])) > 0
|
||||
if has_words:
|
||||
draw_karaoke_line(frame, active_seg["text"], active_seg["words"], t, font_active)
|
||||
else:
|
||||
# Fallback без word-timestamps — старый fade in/out
|
||||
frames_from_start = int((t - active_seg["start"]) * fps)
|
||||
fade_alpha = min(255, int(255 * frames_from_start / max(FADE_FRAMES, 1)))
|
||||
frames_to_end = int((active_seg["end"] - t) * fps)
|
||||
fade_alpha = min(fade_alpha, int(255 * frames_to_end / max(FADE_FRAMES, 1)))
|
||||
|
||||
_draw_text_centered(frame, active_seg["text"],
|
||||
font_active, font_inactive,
|
||||
True, max(fade_alpha, 128))
|
||||
_draw_text_centered(frame, active_seg["text"],
|
||||
font_active, font_inactive,
|
||||
True, max(fade_alpha, 128))
|
||||
|
||||
elif gap > 20 and next_seg:
|
||||
# Длинная пауза (>20s) — показываем прогресс-бар
|
||||
|
||||
Reference in New Issue
Block a user