11
This commit is contained in:
@@ -693,12 +693,13 @@ class AdvancedCoverGenerator:
|
|||||||
print(f"Converted text position from string to dict: {text_position}", file=sys.stderr)
|
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:
|
try:
|
||||||
font = ImageFont.truetype(font_path, font_size)
|
font = load_cjk_font(font_family, font_size, font_weight)
|
||||||
except:
|
except Exception as e:
|
||||||
print(f"Warning: Failed to load font {font_path}, using default", file=sys.stderr)
|
print(f"Warning: Failed to load title font: {e}", file=sys.stderr)
|
||||||
font = ImageFont.load_default()
|
font = load_cjk_font("SimHei", font_size, font_weight)
|
||||||
|
|
||||||
# ✅ 关键修复:支持多行文字,使用anchor='mm'实现中心对齐
|
# ✅ 关键修复:支持多行文字,使用anchor='mm'实现中心对齐
|
||||||
# 计算文字中心点位置(百分比转像素)
|
# 计算文字中心点位置(百分比转像素)
|
||||||
@@ -949,8 +950,8 @@ class AdvancedCoverGenerator:
|
|||||||
font_size = font_size_from_config
|
font_size = font_size_from_config
|
||||||
# 重新加载字体
|
# 重新加载字体
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(font_path, font_size)
|
font = load_cjk_font(font_family, font_size, font_weight)
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 获取排版参数 - 优先使用 titles.main 中的值,否则使用顶级配置,最后使用默认值
|
# 获取排版参数 - 优先使用 titles.main 中的值,否则使用顶级配置,最后使用默认值
|
||||||
@@ -1391,12 +1392,15 @@ class AdvancedCoverGenerator:
|
|||||||
print(f"Converted subtitle position from string to dict: {subtitle_position}", file=sys.stderr)
|
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:
|
try:
|
||||||
subtitle_font = ImageFont.truetype(subtitle_font_path, subtitle_font_size)
|
subtitle_font = load_cjk_font(
|
||||||
except:
|
subtitle_font_family, subtitle_font_size, subtitle_font_weight
|
||||||
print(f"Warning: Failed to load subtitle font {subtitle_font_path}, using default", file=sys.stderr)
|
)
|
||||||
subtitle_font = ImageFont.load_default()
|
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轴需要反转,无旋转时不需要
|
# ✅ 修复旋转坐标问题:有旋转时Y轴需要反转,无旋转时不需要
|
||||||
@@ -1442,8 +1446,14 @@ class AdvancedCoverGenerator:
|
|||||||
subtitle_font_size = subtitle_font_size_from_config
|
subtitle_font_size = subtitle_font_size_from_config
|
||||||
# 重新加载字体
|
# 重新加载字体
|
||||||
try:
|
try:
|
||||||
subtitle_font = ImageFont.truetype(subtitle_font_path, subtitle_font_size)
|
from modules.chinese_font import load_cjk_font
|
||||||
except:
|
|
||||||
|
subtitle_font = load_cjk_font(
|
||||||
|
subtitle_font_family,
|
||||||
|
subtitle_font_size,
|
||||||
|
subtitle_font_weight,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 获取排版参数 - 优先使用 titles.sub 中的值,否则使用顶级配置,最后使用默认值
|
# 获取排版参数 - 优先使用 titles.sub 中的值,否则使用顶级配置,最后使用默认值
|
||||||
@@ -1937,13 +1947,13 @@ class AdvancedCoverGenerator:
|
|||||||
break
|
break
|
||||||
|
|
||||||
# 如果还是找不到,使用默认字体
|
# 如果还是找不到,使用默认字体
|
||||||
if not font_path or not os.path.exists(font_path):
|
try:
|
||||||
print(f"Warning: Font file not found for '{font_family}', using fallback", file=sys.stderr)
|
from modules.chinese_font import resolve_cjk_font_path
|
||||||
if font_path:
|
|
||||||
print(f" Attempted path: {font_path}", file=sys.stderr)
|
|
||||||
return 'C:\\Windows\\Fonts\\msyh.ttc'
|
|
||||||
|
|
||||||
return 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 一致)
|
# ✅ 标准封面尺寸 3:4 比例(与前端 CoverCustomEditor.vue 一致)
|
||||||
COVER_WIDTH = 1080
|
COVER_WIDTH = 1080
|
||||||
|
|||||||
@@ -115,11 +115,16 @@ def add_text_to_image(img, title, config):
|
|||||||
|
|
||||||
style = config.get('style', 'default') # default, outline, blur_bg, gradient, split
|
style = config.get('style', 'default') # default, outline, blur_bg, gradient, split
|
||||||
|
|
||||||
# 加载字体
|
# 加载字体(禁止 load_default,否则中文乱码)
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(font_path, font_size)
|
from modules.chinese_font import load_cjk_font
|
||||||
except:
|
|
||||||
font = ImageFont.load_default()
|
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)
|
draw = ImageDraw.Draw(pil_img)
|
||||||
|
|||||||
5
scripts/cover/fonts/README.md
Normal file
5
scripts/cover/fonts/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 封面字体目录(可选)
|
||||||
|
|
||||||
|
将 `simhei.ttf` 等字体放入此目录可在无系统字体环境下生成中文封面。
|
||||||
|
|
||||||
|
Windows 默认会回退到 `%WINDIR%\Fonts\simhei.ttf` 或 `msyh.ttc`。
|
||||||
161
scripts/cover/modules/chinese_font.py
Normal file
161
scripts/cover/modules/chinese_font.py
Normal 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 优先于 .ttc(PIL 对 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
|
||||||
@@ -37,19 +37,63 @@ function escapeDrawtext(text) {
|
|||||||
.replace(/%/g, "\\%");
|
.replace(/%/g, "\\%");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ffmpeg drawtext 路径转义(Windows 盘符冒号) */
|
||||||
|
function escapeFfmpegPath(filePath) {
|
||||||
|
return String(filePath).replace(/\\/g, "/").replace(/:/g, "\\:");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定位系统中文字体(ffmpeg drawtext 必须指定 fontfile)
|
||||||
|
*/
|
||||||
|
function locateCjkFontFile() {
|
||||||
|
const scriptsDir = process.env.AICLIENT_COVER_SCRIPTS_DIR || "";
|
||||||
|
const windir = process.env.WINDIR || "C:\\Windows";
|
||||||
|
const candidates = [
|
||||||
|
path.join(scriptsDir, "fonts", "simhei.ttf"),
|
||||||
|
path.join(scriptsDir, "fonts", "msyh.ttc"),
|
||||||
|
path.join(windir, "Fonts", "simhei.ttf"),
|
||||||
|
path.join(windir, "Fonts", "msyh.ttc"),
|
||||||
|
path.join(windir, "Fonts", "simsun.ttc"),
|
||||||
|
"C:/Windows/Fonts/simhei.ttf",
|
||||||
|
"C:/Windows/Fonts/msyh.ttc",
|
||||||
|
];
|
||||||
|
for (const p of candidates) {
|
||||||
|
if (p && fs.existsSync(p)) return p;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function buildTextPosition(titlePosition) {
|
function buildTextPosition(titlePosition) {
|
||||||
if (titlePosition === "top") return "y=50";
|
if (titlePosition === "top") return "y=50";
|
||||||
if (titlePosition === "center") return "y=(h-text_h)/2";
|
if (titlePosition === "center") return "y=(h-text_h)/2";
|
||||||
return "y=h-text_h-50";
|
return "y=h-text_h-50";
|
||||||
}
|
}
|
||||||
|
|
||||||
function runFfmpegCover({ sourceVideo, titleText, effectStyle, coverPath, tempFramePath, emit }) {
|
const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".bmp", ".webp"]);
|
||||||
|
|
||||||
|
function isImagePath(filePath) {
|
||||||
|
return IMAGE_EXT.has(path.extname(String(filePath || "")).toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareFrameFromSource(sourcePath, tempFramePath, emit) {
|
||||||
|
if (isImagePath(sourcePath)) {
|
||||||
|
emit?.("正在加载图片素材…");
|
||||||
|
execFfmpeg([
|
||||||
|
"-i",
|
||||||
|
sourcePath,
|
||||||
|
"-vf",
|
||||||
|
"scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black",
|
||||||
|
"-y",
|
||||||
|
tempFramePath,
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
emit?.("正在提取视频帧…");
|
emit?.("正在提取视频帧…");
|
||||||
execFfmpeg([
|
execFfmpeg([
|
||||||
"-ss",
|
"-ss",
|
||||||
"00:00:03",
|
"00:00:03",
|
||||||
"-i",
|
"-i",
|
||||||
sourceVideo,
|
sourcePath,
|
||||||
"-vframes",
|
"-vframes",
|
||||||
"1",
|
"1",
|
||||||
"-pix_fmt",
|
"-pix_fmt",
|
||||||
@@ -63,16 +107,41 @@ function runFfmpegCover({ sourceVideo, titleText, effectStyle, coverPath, tempFr
|
|||||||
"-y",
|
"-y",
|
||||||
tempFramePath,
|
tempFramePath,
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runFfmpegCover({ sourceVideo, titleText, effectStyle, coverPath, tempFramePath, emit }) {
|
||||||
|
prepareFrameFromSource(sourceVideo, tempFramePath, emit);
|
||||||
|
|
||||||
const titleFontSize = effectStyle.titleFontSize ?? 90;
|
const titleFontSize = effectStyle.titleFontSize ?? 90;
|
||||||
const titleFontColor = effectStyle.titleFontColor || "#FFFFFF";
|
const titleFontColor = effectStyle.titleFontColor || "#FFFFFF";
|
||||||
const titlePosition = effectStyle.titlePosition || "bottom";
|
const titlePosition = effectStyle.titlePosition || "bottom";
|
||||||
const textPos = buildTextPosition(titlePosition);
|
const textPos = buildTextPosition(titlePosition);
|
||||||
const escaped = escapeDrawtext(titleText);
|
|
||||||
const vf = `drawtext=text='${escaped}':fontsize=${titleFontSize}:fontcolor=${titleFontColor}:x=(w-text_w)/2:${textPos}:shadowcolor=black:shadowx=2:shadowy=2`;
|
const fontFile = locateCjkFontFile();
|
||||||
|
if (!fontFile) {
|
||||||
|
throw new Error("未找到中文字体文件,无法使用 ffmpeg 绘制标题(请确认 C:\\Windows\\Fonts\\simhei.ttf 存在)");
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleFilePath = path.join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
`cover_title_${Date.now()}.txt`,
|
||||||
|
);
|
||||||
|
fs.writeFileSync(titleFilePath, String(titleText), { encoding: "utf8" });
|
||||||
|
|
||||||
|
const fontOpt = `fontfile='${escapeFfmpegPath(fontFile)}'`;
|
||||||
|
const textOpt = `textfile='${escapeFfmpegPath(titleFilePath)}'`;
|
||||||
|
const vf = `drawtext=${fontOpt}:${textOpt}:fontsize=${titleFontSize}:fontcolor=${titleFontColor}:x=(w-text_w)/2:${textPos}:shadowcolor=black:shadowx=2:shadowy=2`;
|
||||||
|
|
||||||
emit?.("正在叠加标题文字(ffmpeg)…");
|
emit?.("正在叠加标题文字(ffmpeg)…");
|
||||||
|
try {
|
||||||
execFfmpeg(["-i", tempFramePath, "-vf", vf, "-y", coverPath]);
|
execFfmpeg(["-i", tempFramePath, "-vf", vf, "-y", coverPath]);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(titleFilePath);
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldUsePilCover(effectStyle) {
|
function shouldUsePilCover(effectStyle) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function normalizePathForPython(filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildPythonEnv() {
|
function buildPythonEnv() {
|
||||||
const env = { ...process.env, PYTHONIOENCODING: "utf-8" };
|
const env = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" };
|
||||||
const ffmpeg = pipelineNative.locateFfmpeg();
|
const ffmpeg = pipelineNative.locateFfmpeg();
|
||||||
if (ffmpeg && fs.existsSync(ffmpeg)) {
|
if (ffmpeg && fs.existsSync(ffmpeg)) {
|
||||||
const ffmpegDir = path.dirname(ffmpeg);
|
const ffmpegDir = path.dirname(ffmpeg);
|
||||||
@@ -39,6 +39,16 @@ function buildPythonEnv() {
|
|||||||
env.PATH = `${ffmpegDir}${sep}${env.PATH || ""}`;
|
env.PATH = `${ffmpegDir}${sep}${env.PATH || ""}`;
|
||||||
env.AICLIENT_FFMPEG_PATH = ffmpeg;
|
env.AICLIENT_FFMPEG_PATH = ffmpeg;
|
||||||
}
|
}
|
||||||
|
const scriptsDir = locateCoverScriptsDir();
|
||||||
|
console.log("scriptsDir", scriptsDir);
|
||||||
|
if (scriptsDir) {
|
||||||
|
env.AICLIENT_COVER_SCRIPTS_DIR = scriptsDir;
|
||||||
|
env.APP_ROOT = path.dirname(scriptsDir);
|
||||||
|
const fontsDir = path.join(scriptsDir, "fonts");
|
||||||
|
if (fs.existsSync(fontsDir)) {
|
||||||
|
env.AICLIENT_COVER_FONTS_DIR = fontsDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user