Initial clean project import
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
#!/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)}")
|
||||
Reference in New Issue
Block a user