mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-12-23 00:10:19 +08:00
132 lines
4.2 KiB
TypeScript
132 lines
4.2 KiB
TypeScript
/**
|
||
* FFmpeg Adapter Factory
|
||
* 自动检测并选择最佳的 FFmpeg 适配器
|
||
*/
|
||
|
||
import { LogWrapper } from './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;
|
||
}
|
||
}
|