236 lines
11 KiB
Python
236 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
自定义模板封面修复补丁
|
|
将这段代码复制到 subtitle_cover_generator_simple.py 替换 _generate_custom_template_cover 方法
|
|
"""
|
|
|
|
def _generate_custom_template_cover(self, frame):
|
|
"""
|
|
使用自定义模板生成封面 (修复版)
|
|
|
|
Args:
|
|
frame: 视频帧
|
|
|
|
Returns:
|
|
生成的封面路径
|
|
"""
|
|
try:
|
|
print("Generating custom template cover (fixed version)...", file=sys.stderr)
|
|
template = self.template
|
|
|
|
# 转换为PIL图像
|
|
image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
|
width, height = image.size
|
|
|
|
# 1. 应用背景模糊
|
|
if template.get('background', {}).get('blurEnabled', False):
|
|
blur_radius = template['background'].get('blurRadius', 15)
|
|
print(f"Applying background blur: {blur_radius}", file=sys.stderr)
|
|
image = image.filter(ImageFilter.GaussianBlur(radius=blur_radius))
|
|
|
|
# 2. 处理人像描边(如果启用RMBG)
|
|
if template.get('portrait', {}).get('strokeEnabled', False):
|
|
if RMBG_AVAILABLE:
|
|
try:
|
|
print("Extracting portrait for stroke effect...", file=sys.stderr)
|
|
portrait = rembg_remove(image)
|
|
|
|
# 创建描边效果
|
|
stroke_color = template['portrait'].get('strokeColor', '#FFD700')
|
|
stroke_width = template['portrait'].get('strokeWidth', 3)
|
|
stroke_type = template['portrait'].get('strokeType', 'solid')
|
|
|
|
print(f"Stroke: color={stroke_color}, width={stroke_width}, type={stroke_type}", file=sys.stderr)
|
|
|
|
# 合成带描边的人像
|
|
if portrait.mode == 'RGBA':
|
|
# 获取alpha通道作为mask
|
|
alpha = portrait.split()[3]
|
|
|
|
# 对mask进行边缘检测
|
|
alpha_np = np.array(alpha)
|
|
edges = cv2.Canny(alpha_np, 30, 100)
|
|
|
|
# 转换描边颜色
|
|
stroke_rgb = self._hex_to_rgb(stroke_color)
|
|
outline_layer = Image.new('RGBA', (width, height), (*stroke_rgb, 0))
|
|
|
|
# 创建多层描边
|
|
for i in range(stroke_width, 0, -1):
|
|
# 外层更粗的描边
|
|
kernel_size = i * 3
|
|
kernel = np.ones((kernel_size, kernel_size), np.uint8)
|
|
layer_edges = cv2.dilate(edges, kernel, iterations=1)
|
|
|
|
# 转换为PIL图像
|
|
layer_pil = Image.fromarray(layer_edges)
|
|
layer_colored = Image.new('RGBA', (width, height), (*stroke_rgb, 255))
|
|
layer_colored.putalpha(layer_pil)
|
|
|
|
# 合并到描边层
|
|
outline_layer = Image.alpha_composite(outline_layer, layer_colored)
|
|
|
|
# 创建结果:背景 + 描边 + 人像
|
|
result = Image.new('RGB', (width, height))
|
|
result.paste(image, (0, 0))
|
|
|
|
# 将描边和人像合成到背景上
|
|
result_rgba = result.convert('RGBA')
|
|
result_rgba = Image.alpha_composite(result_rgba, outline_layer)
|
|
result_rgba = Image.alpha_composite(result_rgba, portrait)
|
|
image = result_rgba.convert('RGB')
|
|
|
|
print("Portrait stroke effect applied successfully", file=sys.stderr)
|
|
except Exception as e:
|
|
print(f"Portrait stroke failed: {e}", file=sys.stderr)
|
|
import traceback
|
|
traceback.print_exc(file=sys.stderr)
|
|
else:
|
|
print("RMBG not available, skipping portrait stroke", file=sys.stderr)
|
|
|
|
# 3. 添加蒙版(如果有)
|
|
mask_config = template.get('mask', {})
|
|
mask_path = mask_config.get('imagePath', '')
|
|
if mask_path and os.path.exists(mask_path):
|
|
try:
|
|
print(f"Adding mask from: {mask_path}", file=sys.stderr)
|
|
mask_img = Image.open(mask_path).convert('RGBA')
|
|
|
|
# 获取蒙版位置和大小
|
|
mask_pos = mask_config.get('position', {'x': 10, 'y': 10})
|
|
mask_size = mask_config.get('size', {'width': 30, 'height': 20})
|
|
mask_opacity = mask_config.get('opacity', 0.8)
|
|
|
|
mask_width = int(mask_size['width'] / 100 * width)
|
|
mask_height = int(mask_size['height'] / 100 * height)
|
|
mask_x = int(mask_pos['x'] / 100 * width)
|
|
mask_y = int(mask_pos['y'] / 100 * height)
|
|
|
|
# 调整蒙版大小和透明度
|
|
mask_img = mask_img.resize((mask_width, mask_height), Image.LANCZOS)
|
|
if mask_opacity < 1.0:
|
|
alpha = mask_img.split()[3]
|
|
alpha = alpha.point(lambda p: int(p * mask_opacity))
|
|
mask_img.putalpha(alpha)
|
|
|
|
# 将蒙版粘贴到图像上
|
|
image_rgba = image.convert('RGBA')
|
|
image_rgba.paste(mask_img, (mask_x - mask_width // 2, mask_y - mask_height // 2), mask_img)
|
|
image = image_rgba.convert('RGB')
|
|
|
|
print("Mask applied successfully", file=sys.stderr)
|
|
except Exception as e:
|
|
print(f"Failed to add mask: {e}", file=sys.stderr)
|
|
import traceback
|
|
traceback.print_exc(file=sys.stderr)
|
|
|
|
# 4. 添加标题
|
|
draw = ImageDraw.Draw(image)
|
|
|
|
# 加载字体
|
|
try:
|
|
font_main = ImageFont.truetype("NotoSerifCJK-VF", template['titles']['main'].get('fontSize', 60))
|
|
font_sub = ImageFont.truetype("NotoSerifCJK-VF", template['titles']['sub'].get('fontSize', 40))
|
|
except:
|
|
try:
|
|
font_main = ImageFont.truetype("NotoSerifCJK-VF", template['titles']['main'].get('fontSize', 60))
|
|
font_sub = ImageFont.truetype("NotoSerifCJK-VF", template['titles']['sub'].get('fontSize', 40))
|
|
except:
|
|
font_main = ImageFont.load_default()
|
|
font_sub = ImageFont.load_default()
|
|
print("Warning: Failed to load custom font, using default", file=sys.stderr)
|
|
|
|
# 绘制主标题(支持自动换行)
|
|
main_title = template['titles']['main']
|
|
if main_title.get('text'):
|
|
main_text = main_title['text']
|
|
main_color = self._hex_to_rgb(main_title.get('color', '#FFFFFF'))
|
|
main_pos_x = int(width * main_title['position']['x'] / 100)
|
|
main_pos_y = int(height * main_title['position']['y'] / 100)
|
|
max_chars_per_line = main_title.get('maxLength', 4) # 每行最多字符数
|
|
|
|
# 将文字分行
|
|
lines = []
|
|
for i in range(0, len(main_text), max_chars_per_line):
|
|
lines.append(main_text[i:i + max_chars_per_line])
|
|
|
|
# 计算行高和总高度
|
|
line_height = int(font_main.size * 1.2) # 行高为字体大小的1.2倍
|
|
total_height = len(lines) * line_height
|
|
|
|
# 计算起始Y坐标,使多行文字垂直居中
|
|
start_y = main_pos_y - total_height // 2 + line_height // 2
|
|
|
|
# 逐行绘制
|
|
for i, line in enumerate(lines):
|
|
line_y = start_y + i * line_height
|
|
|
|
# 绘制描边
|
|
if main_title.get('strokeWidth', 0) > 0:
|
|
stroke_color = self._hex_to_rgb(main_title.get('strokeColor', '#000000'))
|
|
for offset_x in range(-main_title['strokeWidth'], main_title['strokeWidth'] + 1):
|
|
for offset_y in range(-main_title['strokeWidth'], main_title['strokeWidth'] + 1):
|
|
if offset_x == 0 and offset_y == 0:
|
|
continue
|
|
draw.text((main_pos_x + offset_x, line_y + offset_y),
|
|
line, font=font_main, fill=stroke_color, anchor='mm')
|
|
|
|
# 绘制主体
|
|
draw.text((main_pos_x, line_y), line, font=font_main, fill=main_color, anchor='mm')
|
|
|
|
print(f"Drew main title: '{main_text}' ({len(lines)} lines) at ({main_pos_x}, {main_pos_y})", file=sys.stderr)
|
|
|
|
# 绘制副标题(支持自动换行)
|
|
sub_title = template['titles']['sub']
|
|
if sub_title.get('text'):
|
|
sub_text = sub_title['text']
|
|
sub_color = self._hex_to_rgb(sub_title.get('color', '#FFFFFF'))
|
|
sub_pos_x = int(width * sub_title['position']['x'] / 100)
|
|
sub_pos_y = int(height * sub_title['position']['y'] / 100)
|
|
max_chars_per_line = sub_title.get('maxLength', 3) # 每行最多字符数
|
|
|
|
# 将文字分行
|
|
lines = []
|
|
for i in range(0, len(sub_text), max_chars_per_line):
|
|
lines.append(sub_text[i:i + max_chars_per_line])
|
|
|
|
# 计算行高和总高度
|
|
line_height = int(font_sub.size * 1.2) # 行高为字体大小的1.2倍
|
|
total_height = len(lines) * line_height
|
|
|
|
# 计算起始Y坐标,使多行文字垂直居中
|
|
start_y = sub_pos_y - total_height // 2 + line_height // 2
|
|
|
|
# 逐行绘制
|
|
for i, line in enumerate(lines):
|
|
line_y = start_y + i * line_height
|
|
|
|
# 绘制描边
|
|
if sub_title.get('strokeWidth', 0) > 0:
|
|
stroke_color = self._hex_to_rgb(sub_title.get('strokeColor', '#000000'))
|
|
for offset_x in range(-sub_title['strokeWidth'], sub_title['strokeWidth'] + 1):
|
|
for offset_y in range(-sub_title['strokeWidth'], sub_title['strokeWidth'] + 1):
|
|
if offset_x == 0 and offset_y == 0:
|
|
continue
|
|
draw.text((sub_pos_x + offset_x, line_y + offset_y),
|
|
line, font=font_sub, fill=stroke_color, anchor='mm')
|
|
|
|
# 绘制主体
|
|
draw.text((sub_pos_x, line_y), line, font=font_sub, fill=sub_color, anchor='mm')
|
|
|
|
print(f"Drew sub title: '{sub_text}' ({len(lines)} lines) at ({sub_pos_x}, {sub_pos_y})", file=sys.stderr)
|
|
|
|
# 保存封面
|
|
cover_path = os.path.join(self.output_dir, 'cover_custom_template.png')
|
|
image.save(cover_path, quality=95)
|
|
|
|
print(f"Custom template cover generated successfully: {cover_path}", file=sys.stderr)
|
|
return cover_path
|
|
|
|
except Exception as e:
|
|
print(f"Error generating custom template cover: {e}", file=sys.stderr)
|
|
import traceback
|
|
traceback.print_exc(file=sys.stderr)
|
|
raise Exception(f"Custom template cover generation failed: {str(e)}")
|