NapCatQQ/src/common/ffmpeg-addon-adapter.ts
手瓜一十雪 54ac072bfb Replace console.error with console.log in error handler
Changed error logging in FFmpegAddonAdapter from console.error to console.log when addon loading fails.
2025-10-30 21:39:17 +08:00

130 lines
3.6 KiB
TypeScript

/**
* FFmpeg Native Addon Adapter
* 使用原生 Node.js Addon 实现的 FFmpeg 适配器
*/
import { platform, arch } from 'node:os';
import path from 'node:path';
import { existsSync } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import type { FFmpeg } from './ffmpeg-addon';
import type { IFFmpegAdapter, VideoInfoResult } from './ffmpeg-adapter-interface';
import { dlopen } from 'node:process';
/**
* 获取 Native Addon 路径
* @param binaryPath 二进制文件路径(来自 pathWrapper.binaryPath)
*/
function getAddonPath(binaryPath: string): string {
const platformName = platform();
const archName = arch();
let addonFileName: string = process.platform + '.' + process.arch;
let addonPath = path.join(binaryPath, "./native/ffmpeg/", `${addonFileName}.node`);
if (existsSync(addonPath)) {
throw new Error(`Unsupported platform: ${platformName} ${archName}`);
}
return addonPath;
}
/**
* FFmpeg Native Addon 适配器实现
*/
export class FFmpegAddonAdapter implements IFFmpegAdapter {
public readonly name = 'FFmpegAddon';
private addon: FFmpeg | null = null;
private binaryPath: string;
constructor(binaryPath: string) {
this.binaryPath = binaryPath;
}
/**
* 检查 Addon 是否可用
*/
async isAvailable(): Promise<boolean> {
try {
const addonPath = getAddonPath(this.binaryPath);
if (!existsSync(addonPath)) {
return false;
}
let temp_addon = { exports: {} };
dlopen(temp_addon, addonPath);
this.addon = temp_addon.exports as FFmpeg;
return this.addon !== null;
} catch (error) {
console.log('[FFmpegAddonAdapter] Failed to load addon:', error);
return false;
}
}
private ensureAddon(): FFmpeg {
if (!this.addon) {
throw new Error('FFmpeg Addon is not available');
}
return this.addon;
}
/**
* 获取视频信息
*/
async getVideoInfo(videoPath: string): Promise<VideoInfoResult> {
const addon = this.ensureAddon();
const info = await addon.getVideoInfo(videoPath, 'bmp24');
return {
width: info.width,
height: info.height,
duration: info.duration,
format: info.format,
thumbnail: info.image,
};
}
/**
* 获取时长
*/
async getDuration(filePath: string): Promise<number> {
const addon = this.ensureAddon();
return addon.getDuration(filePath);
}
/**
* 转换为 PCM
*/
async convertToPCM(filePath: string, pcmPath: string): Promise<Buffer> {
const addon = this.ensureAddon();
const result = await addon.decodeAudioToPCM(filePath);
// 写入文件
await writeFile(pcmPath, result.pcm);
return result.pcm;
}
/**
* 转换文件
*/
async convertFile(inputFile: string, outputFile: string, format: string): Promise<void> {
const addon = this.ensureAddon();
if (format === 'silk' || format === 'ntsilk') {
// 使用 Addon 的 NTSILK 转换
await addon.convertToNTSilkTct(inputFile, outputFile);
} else {
throw new Error(`Format '${format}' is not supported by FFmpeg Addon`);
}
}
/**
* 提取缩略图
*/
async extractThumbnail(videoPath: string, thumbnailPath: string): Promise<void> {
const addon = this.ensureAddon();
const info = await addon.getVideoInfo(videoPath);
// 将缩略图写入文件
await writeFile(thumbnailPath, info.image);
}
}