Frame-accurate, lossless MP3 splitting and duration parsing in pure Python — no ffmpeg, no subprocess, no decode step.
Project description
waxcut
Frame-accurate, lossless MP3 splitting and duration parsing in pure Python — no ffmpeg, no subprocess, no decode step.
Cuts are made by parsing the file's own MPEG frame headers and byte-copying whole frames, so output is bit-identical to the source, just shorter. No audio decoding happens at all: not on the way in, not on the way out.
Why
Most MP3-splitting tools shell out to ffmpeg or fully decode the file into
PCM before re-encoding. Both work, but both are heavier than the actual
problem requires: MP3 frames are self-describing, so their boundaries can be
located directly from the byte stream and cut without touching the encoded
audio at all. waxcut does that — a self-contained frame parser with no
runtime dependencies and no external binary.
It also handles the parts that make naive frame-splitting subtly wrong:
- Skips leading
ID3v2tags when scanning for the first frame. - Recognizes and excludes the
Xing/Info/VBRIVBR header frame, which is encoder metadata, not audio — including it in output or duration calculations corrupts both. - Parses LAME's gapless-playback delay/padding extension, so the reported duration matches what a real player shows, not just the raw frame count.
Duration parsing is cross-validated against mutagen's independent implementation to the millisecond across a range of real-world encoded files (see Testing).
Install
Not yet published to PyPI. For now, install directly from GitHub:
uv add git+https://github.com/jkeychan/waxcut
# or
pip install git+https://github.com/jkeychan/waxcut
Usage
from pathlib import Path
from waxcut import load_audio_stream, frame_index_at, slice_bytes
stream = load_audio_stream(Path("song.mp3"))
print(f"{stream.playable_duration_ms / 1000:.1f}s")
# Split at the 90-second mark
cut_at = frame_index_at(stream.frames, target_ms=90_000)
first_half = slice_bytes(stream.data, stream.frames, 0, cut_at)
second_half = slice_bytes(stream.data, stream.frames, cut_at, len(stream.frames))
Path("part1.mp3").write_bytes(first_half)
Path("part2.mp3").write_bytes(second_half)
Scope
waxcut parses MPEG-1/2/2.5 Audio Layer III — what "MP3" actually means.
Layer I/II frames are explicitly rejected (UnsupportedMp3Error) rather than
silently mishandled, since virtually no real-world "MP3" file uses them.
Testing
uv sync
uv run pytest tests/ -v
The suite validates frame parsing against mutagen's independent MP3 parser
(duration must match exactly, including LAME gapless delay/padding) across
CBR/VBR, mono/stereo, and multiple encoder tags — including a regression
test for misreading a non-LAME encoder's metadata as if it were LAME's
gapless fields. Where ffmpeg/ffprobe are available, every split output
is independently decoded to confirm it's a valid, playable file.
Contributing
Bug reports and pull requests are welcome — see CONTRIBUTING.md for the dev setup and PR process. Report security vulnerabilities per SECURITY.md rather than as public issues.
License
Apache-2.0 — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file waxcut-0.1.0.tar.gz.
File metadata
- Download URL: waxcut-0.1.0.tar.gz
- Upload date:
- Size: 10.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f8fae3390224babd5febec5d5dc5add24003f5d62484ad00467e75f9d938c24d
|
|
| MD5 |
6fc16a02b14442f515d016565c55fdae
|
|
| BLAKE2b-256 |
a54ebec5d1600d7a4172c2dde5423a2cde9b8f264454b30b6ce6e626a79c0266
|
File details
Details for the file waxcut-0.1.0-py3-none-any.whl.
File metadata
- Download URL: waxcut-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6a6ebad608a1c06230fb9bcf7590e517e508f736db64634929313f481371dd6
|
|
| MD5 |
169dbf952d828420d4a43221ff2d298d
|
|
| BLAKE2b-256 |
4e29d8d5d8de30358672f5849cf5a1894e8dde4715e720014abc713ccfd6add7
|