An API service that analyzes MP3 files and returns the number of audio frames. Specifically designed for MPEG Version 1 Audio Layer 3 files. Built using Bun's native Bun.serve() API without external HTTP frameworks.
Accepts multipart/form-data with an MP3 file and returns the frame count.
Request:
- Content-Type: multipart/form-data
- Body: form field
filecontaining the MP3 file
Response:
- Success (200):
{ "frameCount": <number> } - Error (400/415/500):
{ "error": "<message>" }
src/
├── index.ts # Root barrel export
├── http/ # HTTP infrastructure layer
│ ├── index.ts # Barrel export
│ ├── server.ts # HTTPServer class with Bun.serve()
│ ├── routes.ts # Route definitions
│ ├── types.ts # HTTP types (RouteHandler, HttpMethod, etc.)
│ └── helpers.ts # HTTP utilities (jsonResponse)
├── parser/ # MP3 parsing domain
│ ├── index.ts # Barrel export
│ ├── mp3-parser.ts # MP3Parser class for frame parsing
│ ├── constants.ts # MPEG1 Layer 3 tables and bit masks
│ ├── types.ts # Parser types (FrameHeader)
│ └── errors.ts # Custom error classes
└── controllers/ # Request handlers
├── index.ts # Barrel export
└── file-upload.controller.ts
tests/
├── mp3-parser.test.ts # Unit tests for MP3Parser
└── server.test.ts # Integration tests for API
- Constructor accepts
Uint8Arrayof file data - Static
fromArrayBuffer(buffer)factory method - Static
fromStream(stream)async factory method (usesfor awaiton ReadableStream) countFrames()returnsnumber(the count of audio frames)
- Constructor accepts optional port (default: 3000)
route(method, path, handler)registers a route (chainable)get(path, handler)shorthand for GET routes (chainable)post(path, handler)shorthand for POST routes (chainable)put(path, handler)shorthand for PUT routes (chainable)patch(path, handler)shorthand for PATCH routes (chainable)delete(path, handler)shorthand for DELETE routes (chainable)start()builds routes and starts the HTTP serverstop()stops the servergetPort()returns current port- Routes use AdonisJS-style registration via chained method calls
Routes are registered using AdonisJS-style chainable methods in src/http/routes.ts:
export function registerRoutes(server: HTTPServer): void {
server.post("/file-upload", handleFileUpload);
}Multiple methods can be chained on the same path without overwriting:
server
.get("/health", handleHealthCheck)
.post("/file-upload", handleFileUpload)
.delete("/file-upload", handleFileDelete);- Skip ID3v2 tags at file start (syncsafe integer size calculation)
- Find frame sync patterns (0xFF followed by 0xE_ where top 3 bits set)
- Validate MPEG Version 1 Layer 3 headers
- Skip Xing/Info VBR metadata frames (contain no audio data)
- Calculate frame size:
floor(144 * bitrate / sampleRate) + padding - Count valid audio frames until end of file
VBR files contain a metadata frame at the start with a "Xing" or "Info" identifier. This frame:
- Has a valid MP3 frame header but contains no audio
- Located at offset = FRAME_HEADER_SIZE (4) + side_info_size (32 for stereo, 17 for mono)
- Must be skipped to get accurate audio frame count
All magic numbers are defined in src/parser/constants.ts:
- Lookup Tables:
MPEG1_LAYER3_BITRATES,MPEG1_SAMPLE_RATES - Identifiers:
ID3V2_IDENTIFIER,XING_IDENTIFIER,INFO_IDENTIFIER - Frame Sync:
FRAME_SYNC_BYTE1(0xFF),FRAME_SYNC_MASK(0xE0) - Version/Layer:
MPEG_VERSION_1(0b11),LAYER_3(0b01) - Bit Masks:
TWO_BIT_MASK,ONE_BIT_MASK,FOUR_BIT_MASK,SYNCSAFE_BYTE_MASK - Sizes:
FRAME_HEADER_SIZE(4),ID3V2_HEADER_SIZE(10),SIDE_INFO_SIZE_STEREO(32),SIDE_INFO_SIZE_MONO(17) - Invalid Indices:
INVALID_BITRATE_INDEX_FREE,INVALID_BITRATE_INDEX_RESERVED,INVALID_SAMPLE_RATE_INDEX - Channel Mode:
CHANNEL_MODE_MONO(3)
- Frame sync: first 11 bits all 1s (0xFF 0xE0 mask)
- MPEG1 version bits: 11 (positions 19-20)
- Layer 3 bits: 01 (positions 17-18)
- Bitrates: 32-320 kbps (indices 1-14)
- Sample rates: 44100, 48000, 32000 Hz
- ISO/IEC 11172-3 - Official MPEG Audio standard
- MP3 Frame Header Documentation - Practical reference
bun run dev # Development with hot reload
bun run start # Production
bun test # Run tests- PORT: Server port (default: 3000)