Skip to main content
Glama
audio-processor.test.ts10.7 kB
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; import AudioProcessor from '../src/services/audio-processor.js'; import { getPreset, presetExists } from '../src/utils/presets.js'; import { validateFilePath, validateAudioOperations } from '../src/schemas/validation.js'; import { AudioOperations, ProcessingInput, ProcessingOutput } from '../src/types/index.js'; // Mock dependencies jest.mock('fluent-ffmpeg'); jest.mock('glob'); jest.mock('fs', () => ({ promises: { stat: jest.fn(), access: jest.fn(), mkdir: jest.fn(), unlink: jest.fn() }, constants: { R_OK: 4, W_OK: 2, F_OK: 0 } })); const mockGlob = jest.mocked(await import('glob')).glob; const mockFs = jest.mocked((await import('fs')).promises); const mockFfmpeg = jest.mocked(await import('fluent-ffmpeg')).default; describe('AudioProcessor', () => { let audioProcessor: AudioProcessor; let mockCommand: any; beforeEach(() => { audioProcessor = new AudioProcessor(); jest.clearAllMocks(); // Setup mock FFmpeg command mockCommand = { audioFilters: jest.fn().mockReturnThis(), audioFrequency: jest.fn().mockReturnThis(), audioChannels: jest.fn().mockReturnThis(), audioCodec: jest.fn().mockReturnThis(), audioBitrate: jest.fn().mockReturnThis(), seekInput: jest.fn().mockReturnThis(), duration: jest.fn().mockReturnThis(), output: jest.fn().mockReturnThis(), on: jest.fn().mockReturnThis(), run: jest.fn() }; (mockFfmpeg as any).mockReturnValue(mockCommand); }); afterEach(() => { jest.clearAllMocks(); }); describe('initialization', () => { it('should create an AudioProcessor instance', () => { expect(audioProcessor).toBeInstanceOf(AudioProcessor); }); it('should have queue management methods', () => { expect(typeof audioProcessor.getQueueStatus).toBe('function'); expect(typeof audioProcessor.pause).toBe('function'); expect(typeof audioProcessor.resume).toBe('function'); expect(typeof audioProcessor.clear).toBe('function'); }); }); describe('queue status', () => { it('should return initial queue status', () => { const status = audioProcessor.getQueueStatus(); expect(status).toHaveProperty('size'); expect(status).toHaveProperty('pending'); expect(status).toHaveProperty('isPaused'); }); }); }); describe('Presets', () => { describe('getPreset', () => { it('should return valid preset for game-audio-mobile', () => { const preset = getPreset('game-audio-mobile'); expect(preset).toHaveProperty('name', 'game-audio-mobile'); expect(preset).toHaveProperty('description'); expect(preset).toHaveProperty('operations'); expect(preset).toHaveProperty('outputFormat'); }); it('should return valid preset for elevenLabs-optimize', () => { const preset = getPreset('elevenLabs-optimize'); expect(preset).toHaveProperty('name', 'elevenLabs-optimize'); expect(preset.operations).toHaveProperty('format'); expect(preset.operations.format).toHaveProperty('channels', 1); }); it('should throw error for invalid preset', () => { expect(() => getPreset('invalid-preset' as any)).toThrow(); }); }); describe('presetExists', () => { it('should return true for valid presets', () => { expect(presetExists('game-audio-mobile')).toBe(true); expect(presetExists('elevenLabs-optimize')).toBe(true); expect(presetExists('voice-processing')).toBe(true); }); it('should return false for invalid presets', () => { expect(presetExists('invalid-preset')).toBe(false); expect(presetExists('')).toBe(false); }); }); }); describe('Validation', () => { describe('validateFilePath', () => { it('should validate supported audio file extensions', () => { expect(validateFilePath('test.mp3')).toBe(true); expect(validateFilePath('test.wav')).toBe(true); expect(validateFilePath('test.ogg')).toBe(true); expect(validateFilePath('test.flac')).toBe(true); expect(validateFilePath('test.m4a')).toBe(true); expect(validateFilePath('test.aac')).toBe(true); }); it('should reject unsupported file extensions', () => { expect(validateFilePath('test.txt')).toBe(false); expect(validateFilePath('test.jpg')).toBe(false); expect(validateFilePath('test')).toBe(false); expect(validateFilePath('')).toBe(false); }); it('should handle case insensitive extensions', () => { expect(validateFilePath('test.MP3')).toBe(true); expect(validateFilePath('test.WAV')).toBe(true); }); }); describe('validateAudioOperations', () => { it('should validate valid operations', () => { const validOperations = { volume: { adjust: -3, normalize: true, targetLUFS: -20 }, format: { sampleRate: 44100, bitrate: 192, channels: 2, codec: 'mp3' } }; expect(validateAudioOperations(validOperations)).toBe(true); }); it('should reject invalid volume values', () => { const invalidOperations = { volume: { adjust: 100 // Too high } }; expect(validateAudioOperations(invalidOperations)).toBe(false); }); it('should reject invalid sample rates', () => { const invalidOperations = { format: { sampleRate: 32000 // Not in enum } }; expect(validateAudioOperations(invalidOperations)).toBe(false); }); }); }); describe('Batch Processing', () => { beforeEach(() => { mockFs.stat.mockResolvedValue({ isFile: () => true } as any); mockFs.access.mockResolvedValue(undefined); mockFs.mkdir.mockResolvedValue(undefined); mockCommand.on.mockImplementation((event: string, callback: Function) => { if (event === 'end') { setTimeout(() => callback(), 0); } return mockCommand; }); }); it('should process multiple files in batch', async () => { const mockFiles = ['/input/file1.mp3', '/input/file2.wav', '/input/file3.ogg']; (mockGlob as jest.Mock).mockResolvedValue(mockFiles); const input: ProcessingInput = { directory: '/input', pattern: '*.{mp3,wav,ogg}' }; const output: ProcessingOutput = { directory: '/output', suffix: '_processed' }; const operations: AudioOperations = { volume: { normalize: true } }; const result = await audioProcessor.batchProcessAudio(input, output, operations, false); expect(result.totalFiles).toBe(3); expect(result.successfulFiles).toBe(3); expect(result.failedFiles).toBe(0); expect(result.results).toHaveLength(3); expect(mockCommand.run).toHaveBeenCalledTimes(3); }); it('should handle no files found', async () => { (mockGlob as jest.Mock).mockResolvedValue([]); const input: ProcessingInput = { directory: '/empty' }; const output: ProcessingOutput = { directory: '/output' }; await expect(audioProcessor.batchProcessAudio(input, output, {}, false)) .rejects.toThrow('No audio files found'); }); }); describe('Real-world Scenarios', () => { beforeEach(() => { mockFs.stat.mockResolvedValue({ isFile: () => true } as any); mockFs.access.mockResolvedValue(undefined); mockFs.mkdir.mockResolvedValue(undefined); mockCommand.on.mockImplementation((event: string, callback: Function) => { if (event === 'end') { setTimeout(() => callback(), 0); } return mockCommand; }); }); it('should handle ElevenLabs voice optimization workflow', async () => { const operations: AudioOperations = { format: { sampleRate: 22050, channels: 1, codec: 'mp3', bitrate: 160 }, volume: { normalize: true, targetLUFS: -20 }, effects: { fadeIn: 0.05, fadeOut: 0.1 } }; const result = await audioProcessor.processAudioFile( '/input/elevenlabs_voice.wav', '/output/game_voice.mp3', operations, true ); expect(result.success).toBe(true); expect(mockCommand.audioFrequency).toHaveBeenCalledWith(22050); expect(mockCommand.audioChannels).toHaveBeenCalledWith(1); expect(mockCommand.audioCodec).toHaveBeenCalledWith('libmp3lame'); expect(mockCommand.audioBitrate).toHaveBeenCalledWith('160k'); expect(mockCommand.audioFilters).toHaveBeenCalledWith('loudnorm=I=-20:LRA=7:tp=-2'); }); it('should handle game audio optimization workflow', async () => { const operations: AudioOperations = { format: { sampleRate: 44100, channels: 2, codec: 'vorbis', bitrate: 192 }, volume: { normalize: true, targetLUFS: -18 } }; const result = await audioProcessor.processAudioFile( '/input/game_music.wav', '/output/game_music.ogg', operations, false ); expect(result.success).toBe(true); expect(mockCommand.audioCodec).toHaveBeenCalledWith('libvorbis'); expect(mockCommand.audioBitrate).toHaveBeenCalledWith('192k'); }); }); describe('Error Handling', () => { it('should handle FFmpeg not found gracefully', async () => { // Mock which to throw error const { default: which } = await import('which'); (which as jest.Mock).mockRejectedValueOnce(new Error('Command not found')); const { checkFFmpegAvailability } = await import('../src/utils/ffmpeg.js'); const available = await checkFFmpegAvailability(); expect(available).toBe(false); }); it('should handle processing failures gracefully', async () => { mockFs.stat.mockResolvedValue({ isFile: () => true } as any); mockFs.access.mockResolvedValue(undefined); mockFs.mkdir.mockResolvedValue(undefined); // Mock FFmpeg failure mockCommand.on.mockImplementation((event: string, callback: Function) => { if (event === 'error') { setTimeout(() => callback(new Error('FFmpeg processing failed')), 0); } return mockCommand; }); const result = await audioProcessor.processAudioFile( '/input/test.mp3', '/output/failed.mp3', { volume: { adjust: -3 } }, false ); expect(result.success).toBe(false); expect(result.error).toContain('FFmpeg processing failed'); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DeveloperZo/mcp-audio-tweaker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server