docs+test: add audio format docs and shared-storage utils tests

- Document all supported audio formats in mukke CLAUDE.md with browser
  compatibility matrix and notes on formats needing transcoding
- Add utils.test.ts for shared-storage: MIME type mappings, audio
  extension validation, and AUDIO_EXTENSIONS completeness checks (19 tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-21 11:12:58 +01:00
parent 7a1ae1284c
commit 99091ecf9e
2 changed files with 95 additions and 0 deletions

View file

@ -120,6 +120,28 @@ pnpm --filter @mukke/landing dev # Landing page
{ id, lyricsId, lineNumber, text, startTime, endTime }
```
## Supported Audio Formats
Playback uses HTML5 Audio (browser-native codec support). Upload accepts any `audio/*` MIME type.
| Format | Extensions | Browser Playback | Notes |
|--------|-----------|-----------------|-------|
| MP3 | `.mp3` | All browsers | ID3 tag read/write supported |
| WAV | `.wav` | All browsers | Uncompressed PCM |
| OGG Vorbis | `.ogg` | Chrome, Firefox, Edge | No Safari support |
| FLAC | `.flac` | All modern browsers | Lossless |
| AAC/M4A | `.aac`, `.m4a` | All browsers | Common iOS format |
| OPUS | `.opus` | Chrome, Firefox, Edge | Best quality/size ratio |
| WebM | `.webm` | Chrome, Firefox, Edge | Container format |
| AIFF | `.aiff`, `.aif` | Safari, Chrome | Common macOS format |
| WMA | `.wma` | Edge only | Legacy Windows format |
| ALAC | `.alac` | Safari | Apple Lossless |
| APE | `.ape` | None natively | Monkey's Audio (upload/metadata only) |
| WavPack | `.wv` | None natively | Hybrid lossless (upload/metadata only) |
| DSF/DFF | `.dsf`, `.dff` | None natively | DSD audio (upload/metadata only) |
**Note:** Formats without native browser playback can be uploaded and have metadata extracted (via `music-metadata`), but require server-side transcoding for playback (not yet implemented).
## Key Technologies
| Component | Technology |

View file

@ -0,0 +1,73 @@
import { describe, it, expect } from 'vitest';
import { getContentType, validateFileExtension, AUDIO_EXTENSIONS } from './utils';
describe('getContentType', () => {
describe('audio formats', () => {
const audioMappings: [string, string][] = [
['.mp3', 'audio/mpeg'],
['.wav', 'audio/wav'],
['.ogg', 'audio/ogg'],
['.m4a', 'audio/mp4'],
['.aac', 'audio/aac'],
['.flac', 'audio/flac'],
['.aiff', 'audio/aiff'],
['.aif', 'audio/aiff'],
['.opus', 'audio/opus'],
['.wma', 'audio/x-ms-wma'],
['.alac', 'audio/mp4'],
['.ape', 'audio/x-ape'],
['.wv', 'audio/x-wavpack'],
['.dsf', 'audio/dsf'],
['.dff', 'audio/dff'],
];
it.each(audioMappings)('%s → %s', (ext, expected) => {
expect(getContentType(`song${ext}`)).toBe(expected);
});
it('handles uppercase extensions', () => {
expect(getContentType('song.FLAC')).toBe('audio/flac');
expect(getContentType('song.M4A')).toBe('audio/mp4');
});
it('returns application/octet-stream for unknown extensions', () => {
expect(getContentType('song.xyz')).toBe('application/octet-stream');
});
});
});
describe('AUDIO_EXTENSIONS', () => {
it('contains all common audio formats', () => {
const expected = [
'.mp3',
'.wav',
'.ogg',
'.m4a',
'.aac',
'.flac',
'.aiff',
'.aif',
'.opus',
'.wma',
'.webm',
'.alac',
'.ape',
'.wv',
'.dsf',
'.dff',
];
for (const ext of expected) {
expect(AUDIO_EXTENSIONS).toContain(ext);
}
});
});
describe('validateFileExtension', () => {
it('validates audio files against AUDIO_EXTENSIONS', () => {
expect(validateFileExtension('song.flac', AUDIO_EXTENSIONS)).toBe(true);
expect(validateFileExtension('song.mp3', AUDIO_EXTENSIONS)).toBe(true);
expect(validateFileExtension('song.opus', AUDIO_EXTENSIONS)).toBe(true);
expect(validateFileExtension('song.exe', AUDIO_EXTENSIONS)).toBe(false);
expect(validateFileExtension('song.pdf', AUDIO_EXTENSIONS)).toBe(false);
});
});