Spaces:
Running
Running
| import cv2 | |
| import numpy as np | |
| from PIL import Image, ImageDraw, ImageFont | |
| # ============================================================ | |
| # 字幕解析 | |
| # ============================================================ | |
| def parse_srt(srt_text: str): | |
| segments = [] | |
| for block in srt_text.strip().split("\n\n"): | |
| lines = block.strip().split("\n") | |
| if len(lines) >= 3: | |
| times = lines[1].split(" --> ") | |
| segments.append((timestamp_to_seconds(times[0]), timestamp_to_seconds(times[1]), "\n".join(lines[2:]))) | |
| return segments | |
| def timestamp_to_seconds(ts: str): | |
| ts = ts.replace(",", ".") | |
| h, m, s = ts.split(":") | |
| return int(h) * 3600 + int(m) * 60 + float(s) | |
| # ============================================================ | |
| # 字幕绘制(白字黑边,固定样式,无特效参数) | |
| # ============================================================ | |
| def _draw_subtitle_lines(frame, text, font_path, video_w, video_h, font_size=None): | |
| """内部通用:白色文字 + 黑色描边,居中底部显示""" | |
| if not text: | |
| return frame | |
| img_rgba = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).convert("RGBA") | |
| draw = ImageDraw.Draw(img_rgba) | |
| if font_size is None or font_size <= 0: | |
| font_size = max(18, min(video_w // 22, 40)) | |
| try: | |
| font_obj = ImageFont.truetype(font_path, font_size) if font_path else ImageFont.load_default() | |
| except: | |
| font_obj = ImageFont.load_default() | |
| # 自动换行 | |
| max_text_width = int(video_w * 0.85) | |
| lines, current_line = [], "" | |
| for char in text: | |
| test_line = current_line + char | |
| if draw.textbbox((0, 0), test_line, font=font_obj)[2] > max_text_width and current_line: | |
| lines.append(current_line) | |
| current_line = char | |
| else: | |
| current_line = test_line | |
| if current_line: | |
| lines.append(current_line) | |
| line_height = font_size + 10 | |
| bottom_margin = int(video_h * 0.08) | |
| y_start = video_h - bottom_margin - len(lines) * line_height | |
| for i, line in enumerate(lines): | |
| tw = draw.textbbox((0, 0), line, font=font_obj)[2] | |
| tx = (video_w - tw) // 2 | |
| ty = y_start + i * line_height | |
| # 黑色描边 | |
| draw.text((tx, ty), line, fill=(255, 255, 255, 255), font=font_obj, | |
| stroke_width=2, stroke_fill=(0, 0, 0, 220)) | |
| return cv2.cvtColor(np.array(img_rgba.convert("RGB")), cv2.COLOR_RGB2BGR) | |
| def draw_subtitle(frame, text, font_path, video_w, video_h, | |
| font_size=None, **kwargs): | |
| return _draw_subtitle_lines(frame, text, font_path, video_w, video_h, font_size) | |
| def draw_typewriter_subtitle(frame, full_text, visible_chars, font_path, video_w, video_h, | |
| font_size=None, **kwargs): | |
| if not full_text or visible_chars <= 0: | |
| return frame | |
| return _draw_subtitle_lines(frame, full_text[:visible_chars], font_path, video_w, video_h, font_size) | |