26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
131 lines
3.9 KiB
TypeScript
131 lines
3.9 KiB
TypeScript
/**
|
|
* FFmpeg Adapter Factory
|
|
* 自动检测并选择最佳的 FFmpeg 适配器
|
|
*/
|
|
|
|
import { LogWrapper } from '@/snowcat-core/helper/log';
|
|
import { FFmpegAddonAdapter } from './ffmpeg-addon-adapter';
|
|
import { FFmpegExecAdapter } from './ffmpeg-exec-adapter';
|
|
import type { IFFmpegAdapter } from './ffmpeg-adapter-interface';
|
|
|
|
/**
|
|
* FFmpeg 适配器工厂
|
|
*/
|
|
export class FFmpegAdapterFactory {
|
|
private static instance: IFFmpegAdapter | null = null;
|
|
private static initPromise: Promise<IFFmpegAdapter> | null = null;
|
|
|
|
/**
|
|
* 初始化并获取最佳的 FFmpeg 适配器
|
|
* @param logger 日志记录器
|
|
* @param ffmpegPath FFmpeg 可执行文件路径(用于 Exec 适配器)
|
|
* @param ffprobePath FFprobe 可执行文件路径(用于 Exec 适配器)
|
|
* @param binaryPath 二进制文件路径(来自 pathWrapper.binaryPath,用于 Addon 适配器)
|
|
*/
|
|
static async getAdapter (
|
|
logger: LogWrapper,
|
|
ffmpegPath: string = 'ffmpeg',
|
|
ffprobePath: string = 'ffprobe',
|
|
binaryPath?: string
|
|
): Promise<IFFmpegAdapter> {
|
|
// 如果已经初始化,直接返回
|
|
if (this.instance) {
|
|
return this.instance;
|
|
}
|
|
|
|
// 如果正在初始化,等待初始化完成
|
|
if (this.initPromise) {
|
|
return this.initPromise;
|
|
}
|
|
|
|
// 开始初始化
|
|
this.initPromise = this.initialize(logger, ffmpegPath, ffprobePath, binaryPath);
|
|
|
|
try {
|
|
this.instance = await this.initPromise;
|
|
return this.instance;
|
|
} finally {
|
|
this.initPromise = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 初始化适配器
|
|
*/
|
|
private static async initialize (
|
|
logger: LogWrapper,
|
|
ffmpegPath: string,
|
|
ffprobePath: string,
|
|
binaryPath?: string
|
|
): Promise<IFFmpegAdapter> {
|
|
// 1. 优先尝试使用 Native Addon
|
|
if (binaryPath) {
|
|
const addonAdapter = new FFmpegAddonAdapter(binaryPath);
|
|
|
|
logger.log('[FFmpeg] 检查 Native Addon 可用性...');
|
|
if (await addonAdapter.isAvailable()) {
|
|
logger.log('[FFmpeg] ✓ 使用 Native Addon 适配器');
|
|
return addonAdapter;
|
|
}
|
|
|
|
logger.log('[FFmpeg] Native Addon 不可用,尝试使用命令行工具');
|
|
} else {
|
|
logger.log('[FFmpeg] 未提供 binaryPath,跳过 Native Addon 检测');
|
|
}
|
|
|
|
// 2. 降级到 execFile 实现
|
|
const execAdapter = new FFmpegExecAdapter(ffmpegPath, ffprobePath, binaryPath, logger);
|
|
|
|
logger.log(`[FFmpeg] 检查命令行工具可用性: ${ffmpegPath}`);
|
|
if (await execAdapter.isAvailable()) {
|
|
logger.log('[FFmpeg] 使用命令行工具适配器 ✓');
|
|
return execAdapter;
|
|
}
|
|
|
|
// 3. 都不可用,返回 execAdapter 但会在使用时报错
|
|
logger.logError('[FFmpeg] 警告: FFmpeg 不可用,将使用命令行适配器但可能失败');
|
|
return execAdapter;
|
|
}
|
|
|
|
/**
|
|
* 重置适配器(用于测试或重新初始化)
|
|
*/
|
|
static reset (): void {
|
|
this.instance = null;
|
|
this.initPromise = null;
|
|
}
|
|
|
|
/**
|
|
* 更新 FFmpeg 路径并重新初始化
|
|
* @param logger 日志记录器
|
|
* @param ffmpegPath FFmpeg 可执行文件路径
|
|
* @param ffprobePath FFprobe 可执行文件路径
|
|
*/
|
|
static async updateFFmpegPath (
|
|
logger: LogWrapper,
|
|
ffmpegPath: string,
|
|
ffprobePath: string
|
|
): Promise<void> {
|
|
// 如果当前使用的是 Exec 适配器,更新路径
|
|
if (this.instance && this.instance instanceof FFmpegExecAdapter) {
|
|
logger.log(`[FFmpeg] 更新 FFmpeg 路径: ${ffmpegPath}`);
|
|
this.instance.setFFmpegPath(ffmpegPath);
|
|
this.instance.setFFprobePath(ffprobePath);
|
|
|
|
// 验证新路径是否可用
|
|
if (await this.instance.isAvailable()) {
|
|
logger.log('[FFmpeg] 新路径验证成功 ✓');
|
|
} else {
|
|
logger.logError('[FFmpeg] 警告: 新 FFmpeg 路径不可用');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取当前适配器(不初始化)
|
|
*/
|
|
static getCurrentAdapter (): IFFmpegAdapter | null {
|
|
return this.instance;
|
|
}
|
|
}
|