An API service that analyzes MP3 files and returns the number of audio frames. Specifically designed for MPEG Version 1 Audio Layer 3 files.
- Bun v1.0.0 or higher
- Clone the repository:
git clone <repository-url>
cd mp3-parser- Install dependencies:
bun install- (Optional) Configure the port:
export PORT=3000 # Default is 3000# Development with hot reload
bun run dev
# Production
bun run startThe server runs on http://localhost:3000 by default.
Upload an MP3 file to get the frame count.
Request:
- Content-Type: multipart/form-data
- Body: form field named
filecontaining the MP3 file
Response:
{
"frameCount": 1234
}curl -X POST http://localhost:3000/file-upload \
-F "file=@/path/to/your/file.mp3"| Status | Description |
|---|---|
| 400 | Missing file, empty file, or invalid MP3 |
| 415 | Unsupported format (not MPEG1 Layer 3) |
| 500 | Internal server error |
Error format:
{
"error": "Error message here"
}Run the test suite:
bun testTo validate the API is returning accurate frame counts, use the included sample file:
- Start the server:
bun run start- In another terminal, upload the sample file:
curl -X POST http://localhost:3000/file-upload \
-F "file=@sample_file.mp3"Expected output:
{"frameCount":6089}Use mediainfo to verify the frame count matches exactly:
mediainfo --fullscan sample_file.mp3 | grep "Frame count"
# Output: Frame count : 6089The API returns exactly 6089 frames, matching mediainfo 100%.
| Property | Value |
|---|---|
| Format | MPEG Version 1 Layer III |
| Bitrate | VBR (~64 kbps average) |
| Sample Rate | 44.1 kHz |
| Channels | Stereo |
| Duration | ~159 seconds |
| Expected Frames | 6089 |
This project uses Bun's native Bun.serve() API directly rather than a framework like Express or Hono. Here's why:
-
Bun.serve() is production-ready: Bun's HTTP server is built on top of a highly optimized C++ implementation. It handles routing, request/response lifecycle, and even WebSockets out of the box.
-
Minimal overhead: For a focused API with a single endpoint, adding a framework introduces unnecessary abstraction. The native API gives us exactly what we need:
- Route registration with method-specific handlers
- Request/Response objects that follow web standards (Fetch API)
- Built-in support for multipart/form-data parsing
-
Type safety without wrappers: The
RequestandResponsetypes are standard web APIs, well-documented and familiar to anyone who's worked with modern JavaScript. -
Educational clarity: This project demonstrates how HTTP routing works at its core—a Map of paths to handlers with method discrimination. Frameworks often obscure these fundamentals.
The HTTPServer class in this project is a thin wrapper (~100 lines) that provides:
- Chainable route registration (
server.post("/upload", handler)) - Centralized error handling for domain-specific errors
- Clean separation between HTTP infrastructure and business logic
The parser implements frame counting for MPEG Version 1 Layer 3 audio files according to the ISO/IEC 11172-3 MPEG Audio specification. For a practical reference, see the MP3 Frame Header documentation.
An MP3 file is a sequence of frames, where each frame contains:
- A 4-byte header with metadata (bitrate, sample rate, etc.)
- Optional CRC (2 bytes if protection bit is set)
- Side information (17 or 32 bytes depending on stereo/mono)
- The actual audio data
┌─────────────────────────────────────────────────────────────────┐
│ [ID3v2 Tag] │ Frame 1 │ Frame 2 │ Frame 3 │ ... │ EOF │
│ (optional) │ │ │ │ │ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────┐
│ Header (4 bytes) │
│ Side Info (17/32 bytes) │
│ Audio Data │
└───────────────────────────┘
The 4-byte frame header is the key to parsing. Here's its bit layout:
AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
A (11 bits) - Frame sync (all bits set = 0x7FF)
B (2 bits) - MPEG Audio version (11 = MPEG1, 10 = MPEG2, etc.)
C (2 bits) - Layer (01 = Layer III, 10 = Layer II, 11 = Layer I)
D (1 bit) - Protection bit (CRC)
E (4 bits) - Bitrate index (lookup table)
F (2 bits) - Sample rate index (lookup table)
G (1 bit) - Padding bit
H (1 bit) - Private bit
I (2 bits) - Channel mode (00 = Stereo, 11 = Mono, etc.)
J (2 bits) - Mode extension
K (1 bit) - Copyright
L (1 bit) - Original
M (2 bits) - Emphasis
-
Skip ID3v2 tags: If the file starts with "ID3", read the tag size (stored as a syncsafe integer) and skip past it.
-
Find frame sync: Scan for the sync pattern—first 11 bits all set to 1 (
0xFFfollowed by0xE0mask on the second byte). -
Validate header: Extract version, layer, bitrate index, and sample rate index. Reject frames that aren't MPEG1 Layer 3 or have invalid indices.
-
Skip Xing/Info frames: VBR files have a metadata frame at the start containing "Xing" or "Info" identifier. This frame has a valid header but no audio—skip it.
-
Calculate frame size: Use the formula:
frameSize = floor(144 × bitrate / sampleRate) + padding -
Count and advance: Increment the frame count and jump ahead by
frameSizebytes to the next frame.
For MPEG1 Layer 3, these are the valid values:
| Bitrate Index | Bitrate (kbps) |
|---|---|
| 0 | free format |
| 1 | 32 |
| 2 | 40 |
| 3 | 48 |
| 4 | 56 |
| 5 | 64 |
| 6 | 80 |
| 7 | 96 |
| 8 | 112 |
| 9 | 128 |
| 10 | 160 |
| 11 | 192 |
| 12 | 224 |
| 13 | 256 |
| 14 | 320 |
| 15 | reserved |
| Sample Rate Index | Sample Rate (Hz) |
|---|---|
| 0 | 44100 |
| 1 | 48000 |
| 2 | 32000 |
| 3 | reserved |
VBR (Variable Bit Rate) files include a special first frame that contains:
- Total frame count
- Total file size
- TOC (table of contents) for seeking
- Quality indicator
This frame uses a valid MP3 header but contains no audio data—it's purely metadata. The identifier ("Xing" for VBR or "Info" for CBR) appears after the header and side information. The parser detects and skips this frame to return an accurate count of audio frames only, matching tools like mediainfo.
- ISO/IEC 11172-3 - Official MPEG Audio standard
- MP3 Frame Header Documentation - Practical reference for header structure
- ID3v2 Specification - ID3 tag format and syncsafe integers
- Xing VBR Header - VBR metadata frame structure
src/
├── index.ts # Root barrel export
├── http/ # HTTP infrastructure
│ ├── server.ts # HTTPServer class with Bun.serve()
│ ├── routes.ts # Route definitions
│ ├── types.ts # HTTP types
│ └── helpers.ts # Utilities (jsonResponse)
├── parser/ # MP3 parsing domain
│ ├── mp3-parser.ts # MP3Parser class
│ ├── constants.ts # Bitrate/sample rate tables, identifiers
│ ├── types.ts # FrameHeader type
│ └── errors.ts # Custom error classes
└── controllers/ # Request handlers
└── file-upload.controller.ts
See AGENTS.md for detailed architecture documentation.