Files
yaoyaoai/scripts/cover/modules/chinese_font.py
949036910@qq.com 7fa83f57b6 11
2026-06-02 23:18:07 +08:00

172 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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)
try:
from modules.font_manager import get_font_manager
ziti_path = get_font_manager().find_font(family, weight)
if ziti_path and os.path.isfile(ziti_path):
return str(Path(ziti_path).resolve())
except Exception as e:
print(f"[chinese_font] font_manager lookup skipped: {e}", file=sys.stderr)
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