Skip to main content

Frame-accurate, lossless MP3 splitting and duration parsing in pure Python — no ffmpeg, no subprocess, no decode step.

Project description

waxcut

CI Workflow Security and Linting OpenSSF Scorecard License Python

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 ID3v2 tags when scanning for the first frame.
  • Recognizes and excludes the Xing/Info/VBRI VBR 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

waxcut-0.1.0.tar.gz (10.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

waxcut-0.1.0-py3-none-any.whl (11.6 kB view details)

Uploaded Python 3

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

Hashes for waxcut-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f8fae3390224babd5febec5d5dc5add24003f5d62484ad00467e75f9d938c24d
MD5 6fc16a02b14442f515d016565c55fdae
BLAKE2b-256 a54ebec5d1600d7a4172c2dde5423a2cde9b8f264454b30b6ce6e626a79c0266

See more details on using hashes here.

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

Hashes for waxcut-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f6a6ebad608a1c06230fb9bcf7590e517e508f736db64634929313f481371dd6
MD5 169dbf952d828420d4a43221ff2d298d
BLAKE2b-256 4e29d8d5d8de30358672f5849cf5a1894e8dde4715e720014abc713ccfd6add7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page