diff --git a/src/mime.js b/src/mime.js index c145648..d820c97 100644 --- a/src/mime.js +++ b/src/mime.js @@ -37,6 +37,12 @@ const OUTPUT_MIME_TYPES = { 'tar.bz2': 'application/x-bzip2', '7z': 'application/x-7z-compressed', 'tar.7z': 'application/x-7z-compressed', + mp3: 'audio/mpeg', + wav: 'audio/wav', + ogg: 'audio/ogg', + flac: 'audio/flac', + aac: 'audio/aac', + m4a: 'audio/x-m4a', }; export async function detectInputMime(filePath) { diff --git a/test/mime.test.js b/test/mime.test.js index 1a2e07c..cb74fba 100644 --- a/test/mime.test.js +++ b/test/mime.test.js @@ -277,3 +277,87 @@ describe('resolveInputFormat — compound archive extensions', () => { await fs.unlink(fixturePath); }); }); + +describe('outputMimeType — audio', () => { + it('returns the correct MIME type for each audio target format', () => { + expect(outputMimeType('mp3')).toBe('audio/mpeg'); + expect(outputMimeType('wav')).toBe('audio/wav'); + expect(outputMimeType('ogg')).toBe('audio/ogg'); + expect(outputMimeType('flac')).toBe('audio/flac'); + expect(outputMimeType('aac')).toBe('audio/aac'); + expect(outputMimeType('m4a')).toBe('audio/x-m4a'); + }); +}); + +describe('resolveInputFormat — audio formats', () => { + it('accepts a WAV file (RIFF....WAVE) declared as wav', async () => { + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-audio.wav'); + await fs.writeFile(fixturePath, Buffer.concat([Buffer.from('RIFF'), Buffer.alloc(4), Buffer.from('WAVE')])); + + const result = await resolveInputFormat(fixturePath, 'wav'); + expect(result).toEqual({ mime: 'audio/wav', valid: true }); + + await fs.unlink(fixturePath); + }); + + it('accepts a FLAC file (fLaC magic) declared as flac', async () => { + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-audio.flac'); + await fs.writeFile(fixturePath, Buffer.from('fLaC')); + + const result = await resolveInputFormat(fixturePath, 'flac'); + expect(result).toEqual({ mime: 'audio/flac', valid: true }); + + await fs.unlink(fixturePath); + }); + + it('accepts an MP3 file (MPEG layer 3 frame sync) declared as mp3', async () => { + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-audio.mp3'); + // Frame sync 0xFF 0xFB: byte1 & 0xE0 == 0xE0 (sync), byte1 & 0x06 == 0x02 (layer 3, not ADTS). + await fs.writeFile(fixturePath, Buffer.from([0xff, 0xfb, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00])); + + const result = await resolveInputFormat(fixturePath, 'mp3'); + expect(result).toEqual({ mime: 'audio/mpeg', valid: true }); + + await fs.unlink(fixturePath); + }); + + it('accepts an AAC/ADTS file declared as aac', async () => { + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-audio.aac'); + // Frame sync 0xFF 0xF1: byte1 & 0xE0 == 0xE0 (sync), byte1 & 0x16 == 0x10 (ADTS, not layer 3). + await fs.writeFile(fixturePath, Buffer.from([0xff, 0xf1, 0x4c, 0x80, 0x01, 0x3f, 0xfc, 0x00])); + + const result = await resolveInputFormat(fixturePath, 'aac'); + expect(result).toEqual({ mime: 'audio/aac', valid: true }); + + await fs.unlink(fixturePath); + }); + + it('accepts an OGG/Vorbis file declared as ogg', async () => { + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-audio.ogg'); + // 'OggS' + 24 ignored bytes (file-type's ignore(28) counts from the 'OggS' magic itself, + // confirmed empirically against the installed file-type package) + packet-type field '\x01vorbis'. + const body = Buffer.concat([ + Buffer.from('OggS'), + Buffer.alloc(24), + Buffer.concat([Buffer.from([0x01]), Buffer.from('vorbis'), Buffer.alloc(1)]), + ]); + await fs.writeFile(fixturePath, body); + + const result = await resolveInputFormat(fixturePath, 'ogg'); + expect(result).toEqual({ mime: 'audio/ogg', valid: true }); + + await fs.unlink(fixturePath); + }); + + it('accepts an M4A file (ISO-BMFF ftyp box, M4A brand) declared as m4a', async () => { + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-audio.m4a'); + // 4-byte box size (unchecked) + 'ftyp' + major brand 'M4A ' (space-padded, 4 bytes). + const body = Buffer.concat([Buffer.alloc(4), Buffer.from('ftyp'), Buffer.from('M4A ')]); + await fs.writeFile(fixturePath, body); + + const result = await resolveInputFormat(fixturePath, 'm4a'); + expect(result).toEqual({ mime: 'audio/x-m4a', valid: true }); + + await fs.unlink(fixturePath); + }); +});