Skip to main content

Convert raw MP3 audio into pretraining-ready WAV/FLAC, with validation and dataset manifest output.

Project description

audio-prep

CI PyPI Docs Python 3.10+ License: MIT Code style: Ruff Type checked: mypy Requires FFmpeg

Convert source audio into pretraining-ready WAV/FLAC, with validation and a dataset manifest as the handoff artifact to the pretraining pipeline. Discovery supports common FFmpeg-readable audio/video containers by default and can scan all files with --extensions all.

Defaults: 16 kHz, mono — the standard input spec for Wav2Vec2 / XLS-R style self-supervised speech pretraining. Override via CLI flags if a different downstream model needs something else.

Repo layout

audio-prep-pipeline/
├── src/audio_prep/
│   ├── config.py        # ConversionConfig: target spec + behavior knobs
│   ├── converter.py      # find_audio_files, convert_file, convert_batch
│   ├── validator.py      # probe_duration, validate_output
│   ├── chunker.py         # ChunkConfig, chunk_file, chunk_batch -- Silero VAD speech chunking
│   ├── manifest.py       # build_manifest, write_manifest (JSONL output)
│   ├── exceptions.py     # ConversionError, ProbeError, ChunkingError
│   └── cli.py             # `audio-prep convert ...` / `audio-prep chunk ...` entry point
├── tests/
│   ├── conftest.py        # synthetic-audio fixtures (no binary files checked in)
│   ├── test_converter.py
│   ├── test_validator.py
│   ├── test_chunker.py    # fake-detector tests, no real VAD model needed
│   ├── test_manifest.py
│   └── test_cli.py
├── .github/workflows/ci.yml   # lint + typecheck + test matrix (3.10-3.12)
├── .pre-commit-config.yaml     # ruff + mypy + basic hygiene hooks, runs on every commit
├── pyproject.toml               # deps, ruff config, mypy config, pytest config
└── Makefile                      # `make check` runs everything CI runs, locally

Commands

audio-prep has two independent subcommands. Neither depends on the other running first -- both scan --input-dir for supported source files directly.

  • audio-prep convert - conversion only: ffmpeg resample/remix/re-encode into the target WAV/FLAC spec, then validation, then an optional manifest. Does not chunk.
  • audio-prep chunk - chunking only: Silero VAD speech chunking straight from source audio, with its own resample/format/manifest options. Does not convert or validate.

convert pipeline stages

  1. discovery (find_audio_files) — recursively find supported source files under an input directory.
  2. conversion (convert_file / convert_batch) — shell out to ffmpeg to resample/remix/re-encode into the target WAV/FLAC spec. Mirrors the input directory's subfolder structure on output. Runs in a process pool since each conversion is an independent subprocess call.
  3. validation (validate_output) — re-opens each converted file with soundfile and checks it actually matches the requested sample rate, channel count, and minimum duration. This catches the case where ffmpeg exits 0 but silently produced something degenerate.
  4. manifest (build_manifest / write_manifest) — JSONL file, one row per source file, recording status (ok / conversion_failed / validation_failed), output path, duration, sample rate, and any error.

Conversion failures don't abort the batch — a bad file in a 50,000-file corpus shows up as one conversion_failed row in the manifest, not a crashed job three hours in.

chunk pipeline stages

  1. discovery (find_audio_files) — recursively find supported source files under an input directory.
  2. chunking (chunk_file / chunk_batch) — runs Silero VAD over each file (decoding/resampling via ffmpeg) and splits it into speech-only chunks bounded by a [min, max] duration window, so silence-heavy source recordings don't waste pretraining compute.
  3. manifest (build_chunk_manifest / write_manifest), optional — JSONL file, one row per source file, recording status (ok / chunking_failed), chunk count, and chunk paths.

Chunking failures (e.g. no speech detected) work the same way: chunk_batch returns a ChunkResult per file instead of raising.

Setup

# ffmpeg is a system dependency, not a pip package
sudo apt-get install ffmpeg   # or: brew install ffmpeg

pip install audio-prep-pipeline

Install directly from GitHub:

pip install "audio-prep-pipeline @ git+https://github.com/nattkorat/audio-prep-pipeline.git"

For local development:

make install   # pip install -e ".[dev]" + pre-commit install

The same install provides both audio-prep convert and audio-prep chunk. FFmpeg/FFprobe are still system dependencies and must be available on PATH. If Silero VAD cannot load in an offline environment, pass --allow-energy-fallback to use a lower-quality offline detector instead.

Usage

audio-prep convert

CLI:

audio-prep convert \
    --input-dir data/raw_mp3 \
    --output-dir data/wav16k \
    --format wav \
    --sample-rate 16000 \
    --workers 8 \
    --manifest data/manifest.jsonl

Python:

from pathlib import Path

from audio_prep import ConversionConfig, build_manifest, convert_batch, validate_output, write_manifest

config = ConversionConfig(
    output_format="wav",
    sample_rate=16_000,
    channels=1,
    num_workers=8,
)

results = convert_batch(Path("data/raw_mp3"), Path("data/wav16k"), config)
validations = {
    result.output: validate_output(result.output, config)
    for result in results
    if result.success and result.output is not None
}
records = build_manifest(results, validations)
write_manifest(records, Path("data/manifest.jsonl"))
Flag Default Meaning
--input-dir (required) directory of source audio files
--output-dir (required) where converted output is written
--extensions common FFmpeg audio/video extensions comma-separated source extensions, or all to pass every regular file to FFmpeg
--format wav output format (wav or flac)
--sample-rate 16000 target sample rate
--channels 1 target channel count
--workers 4 parallel conversion workers
--min-duration-sec 0.5 validation fails files shorter than this
--overwrite off re-convert even if output already exists and passes validation
--normalize-loudness off apply EBU R128 loudness normalization (-23 LUFS)
--manifest none path to write a JSONL manifest

audio-prep chunk

Independent of convert -- scans --input-dir for supported source files and runs VAD chunking directly against them, decoding (and resampling, if --sample-rate doesn't match the source) via ffmpeg:

CLI:

audio-prep chunk \
    --input-dir data/raw_mp3 \
    --output-dir data/chunks \
    --sample-rate 16000 \
    --format flac \
    --min-duration-sec 5 \
    --max-duration-sec 20 \
    --workers 4 \
    --manifest data/chunk_manifest.jsonl

Python:

from pathlib import Path

from audio_prep import ChunkConfig, build_chunk_manifest, chunk_batch, write_manifest

config = ChunkConfig(
    min_duration_sec=5,
    max_duration_sec=20,
    output_format="flac",
    sample_rate=16_000,
    num_workers=4,
)

results = chunk_batch(Path("data/raw_mp3"), Path("data/chunks"), config)
records = build_chunk_manifest(results)
write_manifest(records, Path("data/chunk_manifest.jsonl"))
Flag Default Meaning
--input-dir (required) directory of source audio files to scan and chunk
--output-dir <input-dir>/chunks where chunks are written
--extensions common FFmpeg audio/video extensions comma-separated source extensions, or all to pass every regular file to FFmpeg
--format wav output chunk format (wav or flac)
--sample-rate 16000 resample (via ffmpeg) to this rate before chunking if the source doesn't already match it
--min-duration-sec 5.0 drop chunks shorter than this
--max-duration-sec 20.0 split longer speech into windows this size
--workers 4 parallel chunking workers
--overwrite off re-chunk even if valid output already exists
--allow-energy-fallback off fall back to a low-quality energy detector if Silero can't load, instead of raising
--manifest none path to write a JSONL chunk manifest (source file, status, chunk count/paths)

chunk_file itself doesn't care about source extension -- it decodes whatever path it's given -- so the Python API can also chunk an existing WAV/FLAC corpus (e.g. convert_batch output) by passing source_files explicitly instead of relying on chunk_batch discovery. This is an advanced Python API case:

from pathlib import Path

from audio_prep import ChunkConfig, ConversionConfig, build_manifest, chunk_batch
from audio_prep import convert_batch, validate_output

config = ConversionConfig(output_format="wav", sample_rate=16_000, channels=1, num_workers=8)
results = convert_batch(Path("data/raw_mp3"), Path("data/wav16k"), config)
validations = {
    result.output: validate_output(result.output, config)
    for result in results
    if result.success and result.output is not None
}
records = build_manifest(results, validations)

# source_files bypasses chunk_batch discovery, so this works
# directly against the already-converted WAV output above.
valid_outputs = [path for path, v in validations.items() if v.valid]
chunk_config = ChunkConfig(min_duration_sec=5, max_duration_sec=20, num_workers=4)
chunk_results = chunk_batch(
    Path("data/wav16k"),
    Path("data/wav16k/chunks"),
    chunk_config,
    source_files=valid_outputs,
)

Development workflow

make format     # ruff format + autofix
make lint       # ruff check
make typecheck  # mypy --strict
make test       # pytest with coverage
make check      # all of the above -- run this before opening a PR

pre-commit (installed via make install) runs ruff + mypy + basic hygiene checks automatically on every commit. CI (.github/workflows/ci.yml) re-runs the same checks plus the full test matrix across Python 3.10–3.12 on every push and PR.

Extending this

Natural next additions, in roughly the order they'd come up:

  • Streaming manifest writes for very large corpora, instead of holding all ConversionResults in memory before writing.

Note: This is the template, that you have to extend from. Main branch is protected so you have to create another branch to work on.

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

audio_prep_pipeline-0.1.2.tar.gz (37.2 kB view details)

Uploaded Source

Built Distribution

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

audio_prep_pipeline-0.1.2-py3-none-any.whl (22.5 kB view details)

Uploaded Python 3

File details

Details for the file audio_prep_pipeline-0.1.2.tar.gz.

File metadata

  • Download URL: audio_prep_pipeline-0.1.2.tar.gz
  • Upload date:
  • Size: 37.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.4

File hashes

Hashes for audio_prep_pipeline-0.1.2.tar.gz
Algorithm Hash digest
SHA256 249d83903b7b9225b01fcd626c43a8e6c6f9b45c557c7114586db77dee2c592c
MD5 df8bf791de2a5762071bb521b5e8d0f7
BLAKE2b-256 f52af8acc6f886dfe3743de6a43438ae70c767e9ece4a9c5d74bf261b64da8c7

See more details on using hashes here.

File details

Details for the file audio_prep_pipeline-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for audio_prep_pipeline-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 3c8d98e5cf2fd5eaca5e0b78e1993f286b5e957115fcf5cb6ddff0df059beaa4
MD5 165412909fcb9995bf9037d084f81288
BLAKE2b-256 11d53e3f71cede55b309cef95a37ac5ce66cf1d18a63add73448cbb606a671d6

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