This commit is contained in:
949036910@qq.com
2026-06-02 22:48:18 +08:00
parent f091e17ca4
commit f9bccba872
21 changed files with 2386 additions and 68 deletions

View File

@@ -693,12 +693,13 @@ class AdvancedCoverGenerator:
print(f"Converted text position from string to dict: {text_position}", file=sys.stderr)
# 加载字体
font_path = self._get_font_path(font_family)
from modules.chinese_font import load_cjk_font
try:
font = ImageFont.truetype(font_path, font_size)
except:
print(f"Warning: Failed to load font {font_path}, using default", file=sys.stderr)
font = ImageFont.load_default()
font = load_cjk_font(font_family, font_size, font_weight)
except Exception as e:
print(f"Warning: Failed to load title font: {e}", file=sys.stderr)
font = load_cjk_font("SimHei", font_size, font_weight)
# ✅ 关键修复支持多行文字使用anchor='mm'实现中心对齐
# 计算文字中心点位置(百分比转像素)
@@ -949,8 +950,8 @@ class AdvancedCoverGenerator:
font_size = font_size_from_config
# 重新加载字体
try:
font = ImageFont.truetype(font_path, font_size)
except:
font = load_cjk_font(font_family, font_size, font_weight)
except Exception:
pass
# 获取排版参数 - 优先使用 titles.main 中的值,否则使用顶级配置,最后使用默认值
@@ -1391,12 +1392,15 @@ class AdvancedCoverGenerator:
print(f"Converted subtitle position from string to dict: {subtitle_position}", file=sys.stderr)
# 加载副标题字体
subtitle_font_path = self._get_font_path(subtitle_font_family)
from modules.chinese_font import load_cjk_font
try:
subtitle_font = ImageFont.truetype(subtitle_font_path, subtitle_font_size)
except:
print(f"Warning: Failed to load subtitle font {subtitle_font_path}, using default", file=sys.stderr)
subtitle_font = ImageFont.load_default()
subtitle_font = load_cjk_font(
subtitle_font_family, subtitle_font_size, subtitle_font_weight
)
except Exception as e:
print(f"Warning: Failed to load subtitle font: {e}", file=sys.stderr)
subtitle_font = load_cjk_font("SimHei", subtitle_font_size, subtitle_font_weight)
# 计算副标题中心点位置(百分比转像素)
# ✅ 修复旋转坐标问题有旋转时Y轴需要反转无旋转时不需要
@@ -1442,8 +1446,14 @@ class AdvancedCoverGenerator:
subtitle_font_size = subtitle_font_size_from_config
# 重新加载字体
try:
subtitle_font = ImageFont.truetype(subtitle_font_path, subtitle_font_size)
except:
from modules.chinese_font import load_cjk_font
subtitle_font = load_cjk_font(
subtitle_font_family,
subtitle_font_size,
subtitle_font_weight,
)
except Exception:
pass
# 获取排版参数 - 优先使用 titles.sub 中的值,否则使用顶级配置,最后使用默认值
@@ -1937,13 +1947,13 @@ class AdvancedCoverGenerator:
break
# 如果还是找不到,使用默认字体
if not font_path or not os.path.exists(font_path):
print(f"Warning: Font file not found for '{font_family}', using fallback", file=sys.stderr)
if font_path:
print(f" Attempted path: {font_path}", file=sys.stderr)
return 'C:\\Windows\\Fonts\\msyh.ttc'
return font_path
try:
from modules.chinese_font import resolve_cjk_font_path
return resolve_cjk_font_path(font_family, 400)
except Exception as e:
print(f"Warning: _get_font_path fallback: {e}", file=sys.stderr)
return str(Path(os.environ.get("WINDIR", "C:/Windows")) / "Fonts" / "simhei.ttf")
# ✅ 标准封面尺寸 3:4 比例(与前端 CoverCustomEditor.vue 一致)
COVER_WIDTH = 1080

View File

@@ -115,11 +115,16 @@ def add_text_to_image(img, title, config):
style = config.get('style', 'default') # default, outline, blur_bg, gradient, split
# 加载字体
# 加载字体(禁止 load_default否则中文乱码
try:
font = ImageFont.truetype(font_path, font_size)
except:
font = ImageFont.load_default()
from modules.chinese_font import load_cjk_font
font = load_cjk_font(font_family, font_size)
except Exception as e:
print(f"[cover_generator] font load failed: {e}, retry SimHei", file=sys.stderr)
from modules.chinese_font import load_cjk_font
font = load_cjk_font("SimHei", font_size)
# 创建绘图对象
draw = ImageDraw.Draw(pil_img)

View File

@@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
"""封面中文 PIL / ffmpeg 字体解析(避免 load_default 导致乱码)"""
from __future__ import annotations
import os
import platform
import sys
from pathlib import Path
from typing import List, Optional
try:
from PIL import ImageFont
except ImportError:
ImageFont = None # type: ignore
_FAMILY_ALIASES = {
"microsoft yahei": "Microsoft YaHei",
"微软雅黑": "Microsoft YaHei",
"simhei": "SimHei",
"黑体": "SimHei",
"simsun": "SimSun",
"宋体": "SimSun",
"arial": "Arial",
"times new roman": "Times New Roman",
}
_WIN_FAMILY_FILES = {
"SimHei": ["simhei.ttf"],
"Microsoft YaHei": ["msyh.ttc", "msyh.ttf", "msyhbd.ttc"],
"SimSun": ["simsun.ttc", "simsunb.ttf"],
"Arial": ["arial.ttf", "arialbd.ttf"],
"Times New Roman": ["times.ttf", "timesbd.ttf"],
}
def _scripts_dir() -> Optional[Path]:
env = os.environ.get("AICLIENT_COVER_SCRIPTS_DIR", "").strip()
if env:
p = Path(env)
if p.is_dir():
return p
# advanced_cover_generator.py 位于 scripts/cover/
here = Path(__file__).resolve().parent.parent
if (here / "advanced_cover_generator.py").is_file():
return here
return None
def _windows_fonts_dir() -> Path:
windir = os.environ.get("WINDIR", "C:\\Windows")
return Path(windir) / "Fonts"
def _normalize_family(font_family: Optional[str]) -> str:
raw = (font_family or "Microsoft YaHei").strip()
key = raw.lower()
return _FAMILY_ALIASES.get(key, raw)
def _candidate_filenames(family: str, weight: int = 400) -> List[str]:
names = list(_WIN_FAMILY_FILES.get(family, []))
if weight >= 700 and family == "Microsoft YaHei":
names = ["msyhbd.ttc", "msyhl.ttc"] + names
# 通用回退:.ttf 优先于 .ttcPIL 对 ttf 更稳)
fallback = ["simhei.ttf", "msyh.ttc", "simsun.ttc", "msyh.ttf"]
out: List[str] = []
for n in names + fallback:
if n not in out:
out.append(n)
return out
def resolve_cjk_font_path(
font_family: Optional[str] = None,
weight: int = 400,
) -> str:
"""
解析可用于 PIL / ffmpeg 的中文字体文件路径。
"""
family = _normalize_family(font_family)
candidates = _candidate_filenames(family, weight)
scripts = _scripts_dir()
if scripts:
fonts_dir = scripts / "fonts"
if fonts_dir.is_dir():
for name in candidates:
p = fonts_dir / name
if p.is_file():
return str(p.resolve())
if platform.system() == "Windows":
win_fonts = _windows_fonts_dir()
for name in candidates:
p = win_fonts / name
if p.is_file():
return str(p.resolve())
if platform.system() == "Darwin":
mac_candidates = [
"/System/Library/Fonts/PingFang.ttc",
"/System/Library/Fonts/STHeiti Light.ttc",
"/Library/Fonts/Arial Unicode.ttf",
]
for p in mac_candidates:
if os.path.isfile(p):
return p
# Linux
linux_candidates = [
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf",
]
for p in linux_candidates:
if os.path.isfile(p):
return p
# 最后尝试Windows 黑体
if platform.system() == "Windows":
simhei = _windows_fonts_dir() / "simhei.ttf"
if simhei.is_file():
return str(simhei.resolve())
raise FileNotFoundError(
f"未找到中文字体family={family}),请在 cover/fonts 放置 simhei.ttf 或安装系统字体"
)
def load_cjk_font(
font_family: Optional[str],
size: int,
weight: int = 400,
) -> "ImageFont.FreeTypeFont":
"""加载支持中文的 PIL 字体,禁止回退到 load_default。"""
if ImageFont is None:
raise RuntimeError("PIL ImageFont 不可用")
path = resolve_cjk_font_path(font_family, weight)
ext = os.path.splitext(path)[1].lower()
if ext == ".ttc":
last_err: Optional[Exception] = None
for index in range(8):
try:
font = ImageFont.truetype(path, size, index=index)
print(
f"[chinese_font] Loaded TTC index={index}: {path} size={size}",
file=sys.stderr,
)
return font
except OSError as e:
last_err = e
continue
raise OSError(f"无法加载字体集合 {path}: {last_err}")
font = ImageFont.truetype(path, size)
print(f"[chinese_font] Loaded: {path} size={size}", file=sys.stderr)
return font