/** * 音频处理工具 * * 提供音频文件的解析和处理功能,包括元数据提取和封面图提取 */ 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 { 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 ]