gongxiangp2/lib/audio-processor.ts

99 lines
2.4 KiB
TypeScript

/**
* 音频处理工具
*
* 提供音频文件的解析和处理功能,包括元数据提取和封面图提取
*/
import { parseBuffer } from 'music-metadata'
/**
* 音频元数据
*/
export interface AudioMetadata {
duration: number // 时长(秒)
durationFormatted: string // 格式化时长 (mm:ss)
format: string // 音频格式
bitrate?: number // 比特率
sampleRate?: number // 采样率
channels?: number // 声道数
picture?: {
data: Buffer
format: string
description?: string
} // 封面图数据
}
/**
* 格式化时长为 mm:ss 格式
*/
export function formatDuration(seconds: number): string {
const mins = Math.floor(seconds / 60)
const secs = Math.floor(seconds % 60)
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
}
/**
* 解析音频文件获取元数据(包括封面图)
*
* @param buffer - 音频文件的 Buffer
* @param mimeType - 音频文件的 MIME 类型
* @returns 音频元数据(包含封面图数据)
*/
export async function parseAudioMetadata(
buffer: Buffer,
mimeType?: string
): Promise<AudioMetadata | null> {
try {
const metadata = await parseBuffer(buffer, { mimeType })
const duration = metadata.format.duration || 0
// 提取封面图(如果存在)
let picture = undefined
if (metadata.common.picture && metadata.common.picture.length > 0) {
const pic = metadata.common.picture[0]
picture = {
data: Buffer.from(pic.data), // 将 Uint8Array 转换为 Buffer
format: pic.format || 'image/jpeg',
description: pic.description,
}
}
return {
duration,
durationFormatted: formatDuration(duration),
format: metadata.format.container || 'unknown',
bitrate: metadata.format.bitrate,
sampleRate: metadata.format.sampleRate,
channels: metadata.format.numberOfChannels,
picture,
}
} catch (error) {
console.error('[AudioProcessor] Failed to parse audio metadata:', error)
return null
}
}
/**
* 检查文件是否为音频文件
*/
export function isAudioFile(mimeType: string): boolean {
return mimeType.startsWith('audio/')
}
/**
* 支持的音频格式
*/
export const SUPPORTED_AUDIO_FORMATS = [
'audio/mpeg', // MP3
'audio/wav', // WAV
'audio/x-wav',
'audio/flac', // FLAC
'audio/aac', // AAC
'audio/mp4', // M4A
'audio/x-m4a',
'audio/ogg', // OGG
'audio/webm', // WebM
]