Refactor FFmpeg file conversion logic and API

Unified file conversion in FFmpegAddonAdapter to use decodeAudioToFmt for all formats, updated FFmpeg interface and service to support new conversion method, and added adapter name checks in GetRecord and DownloadFileRecordStream for optimized conversion flow. Updated native addon binaries to support these changes.
This commit is contained in:
手瓜一十雪 2025-11-08 12:22:51 +08:00
parent 8de49a3109
commit ec2af3120c
12 changed files with 33 additions and 18 deletions

View File

@ -68,11 +68,13 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
const addon = this.ensureAddon();
const info = await addon.getVideoInfo(videoPath, 'bmp24');
let format = info.format.includes(',') ? info.format.split(',')[0] ?? info.format : info.format;
console.log('[FFmpegAddonAdapter] Detected format:', format);
return {
width: info.width,
height: info.height,
duration: info.duration,
format: info.format,
format: format,
thumbnail: info.image,
};
}
@ -88,7 +90,7 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
/**
* PCM
*/
async convertToPCM (filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }> {
async convertToPCM (filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number; }> {
const addon = this.ensureAddon();
const result = await addon.decodeAudioToPCM(filePath, pcmPath, 24000);
@ -100,13 +102,8 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
*/
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`);
}
console.log('[FFmpegAddonAdapter] Converting file:', inputFile, 'to', outputFile, 'as', format);
await addon.decodeAudioToFmt(inputFile, outputFile, format);
}
/**

View File

@ -49,23 +49,25 @@ export interface AudioPCMResult {
* FFmpeg interface providing all audio/video processing methods
*/
export interface FFmpeg {
convertFile (inputFile: string, outputFile: string, format: string): Promise<{ success: boolean; }>;
/**
* Get video information including resolution, duration, format, codec and first frame thumbnail
*/
getVideoInfo(filePath: string, format?: 'bmp' | 'bmp24'): Promise<VideoInfo>;
getVideoInfo (filePath: string, format?: 'bmp' | 'bmp24'): Promise<VideoInfo>;
/**
* Get duration of audio or video file in seconds
*/
getDuration(filePath: string): Promise<number>;
getDuration (filePath: string): Promise<number>;
/**
* Convert audio file to NTSILK format (WeChat voice message format)
*/
convertToNTSilkTct(inputPath: string, outputPath: string): Promise<void>;
convertToNTSilkTct (inputPath: string, outputPath: string): Promise<void>;
/**
* Decode audio file to raw PCM data
*/
decodeAudioToPCM(filePath: string, pcmPath: string, sampleRate?: number): Promise<{ result: boolean, sampleRate: number }>;
decodeAudioToPCM (filePath: string, pcmPath: string, sampleRate?: number): Promise<{ result: boolean, sampleRate: number; }>;
decodeAudioToFmt (filePath: string, pcmPath: string, format: string): Promise<{ channels: number; sampleRate: number; format: string; }>;
}

View File

@ -48,6 +48,14 @@ export class FFmpegService {
this.initialized = true;
}
public static getAdapterName (): string {
if (!this.adapter) {
throw new Error('FFmpeg service not initialized. Please call FFmpegService.init() first.');
}
return this.adapter.name;
}
/**
* FFmpeg
*/

View File

@ -7,7 +7,7 @@ import { FFmpegService } from '@/common/ffmpeg';
const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac'];
type Payload = {
out_format: string
out_format: string;
} & GetFilePayload;
export default class GetRecord extends GetFileBase {
@ -28,8 +28,12 @@ export default class GetRecord extends GetFileBase {
try {
await fs.access(outputFile);
} catch {
await this.decodeFile(inputFile, pcmFile);
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
if (FFmpegService.getAdapterName() === 'FFmpegAddon') {
await FFmpegService.convertFile(inputFile, outputFile, payload.out_format);
} else {
await this.decodeFile(inputFile, pcmFile);
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
}
}
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
res.file = outputFile;

View File

@ -47,8 +47,12 @@ export class DownloadFileRecordStream extends BaseDownloadStream<Payload, Downlo
streamPath = outputFile;
} catch {
// 尝试解码 silk 到 pcm 再用 ffmpeg 转换
await this.decodeFile(downloadPath, pcmFile);
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
if (FFmpegService.getAdapterName() === 'FFmpegAddon') {
await FFmpegService.convertFile(downloadPath, outputFile, payload.out_format);
} else {
await this.decodeFile(downloadPath, pcmFile);
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
}
streamPath = outputFile;
}
}