Metadata-Version: 2.4
Name: audiolabtools
Version: 0.1.0
Summary: Measure audio from Python: loudness, true peak, voice quality and signal checks, each number with its status and margin. A small client for the AudioLab API.
Author: Nathan Renting
License: MIT
Project-URL: Homepage, https://audiolab.tools/api
Project-URL: Documentation, https://audiolab.tools/docs/start/compare-voice-agent-vendors
Project-URL: Changelog, https://audiolab.tools/changelog
Project-URL: Error codes, https://audiolab.tools/docs/errors
Project-URL: OpenAPI spec, https://audiolab.tools/openapi.yaml
Keywords: audio,loudness,lufs,ebu-r128,true-peak,voice-quality,audio-analysis,tts,qa,audiolab
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# audiolabtools

A small, typed Python client for the [AudioLab](https://audiolab.tools/api) API: loudness, true peak, voice quality and signal checks, with the status and the margin of every number. No dependencies beyond the standard library, Python 3.9 or newer.

## Install

```bash
pip install audiolabtools
```

## First call

```python
from audiolabtools import AudioLab

print(AudioLab().loudness("https://audiolab.tools/reference/is-original.mp3")["integratedLufs"])
```

That works without a key: the keyless trial allows 20 calls a month and 3 a minute, on the analysis routes. A free key (1,000 calls and 2 GB a month, no card) is on [/account](https://audiolab.tools/account); pass it as `AudioLab(api_key="al_live_...")` or set `AUDIOLAB_KEY`.

## The ten numbers

The guide [Compare voice-agent and TTS vendors](https://audiolab.tools/docs/start/compare-voice-agent-vendors) names ten numbers that are worth reading on conversational audio. One call gives you all ten, each with its status and its margin:

```python
numbers = AudioLab().ten_numbers("https://audiolab.tools/samples/voice/speech-male-plain.mp3")

for field, m in numbers.items():
    print(f"{field:20} {m.value!s:>8} {m.unit:9} {m.status:10} {m.margin or ''}")
```

```
integratedLufs          -31.5 LUFS      validated  ±0.1 LU
truePeakDb              -14.8 dBTP      validated  +0.2 / -0.4 dB
peakDb                 -14.82 dBFS      validated
averageSpeechRmsDb     -30.75 dBFS      validated
noiseFloorDb           -76.33 dBFS      validated
signalToNoiseDb         45.58 dB        validated  ±6 dB
clipping                 None severity  validated
speechRatio             0.644 share     proxy
longSilences                2 count     proxy
bandwidthHz            9991.4 Hz        validated
```

A **status** (`validated`, `proxy`, `beta`, `withdrawn`, `planned`) says how far to trust a number, and a **margin** how far it can be off. Read both before you rank vendors on a number; the guide says what each one means and what it cannot tell you. Under the hood this is three calls (loudness, voice, signal) with `round` and `measure_status` on. Every call counts against your limits, so `only=["integratedLufs", "truePeakDb"]` reads just those and makes only the calls that carry them: the loudness numbers are one call, the voice numbers a second, the bandwidth a third.

## Your own files

Every analysis method takes a public URL or the path to a local file:

```python
client = AudioLab()
client.voice("take-07.wav")                       # read here, sent to the API
client.check_target("master.wav", "spotify")      # pass or fail against a delivery target
client.batch(["a.wav", "https://example.com/b.mp3"])   # up to 20 sources, needs a key
```

A local file is read on your machine and **uploaded to the API**: nothing is measured locally. A file up to 4 MB goes in the request body and is discarded inside the request; a larger one (up to 150 MB, and `ten_numbers` uploads it once) goes through a signed upload into EU storage, and a sweep removes it within the hour. Only numbers come back. The details are on [/privacy](https://audiolab.tools/privacy). There is no offline mode, and the engine is not published as a package.

The methods: `loudness`, `voice`, `signal`, `check_target`, `timeseries`, `spectrum`, `segments`, `profile`, `batch`, `ten_numbers` and `usage`. Each returns the answer of its route as a plain `dict`, keyed as in the [OpenAPI spec](https://audiolab.tools/openapi.yaml). The common options are `round=True` (fewer decimals, same keys), `measure_status=True` (adds the status and margin of every measure) and `name="..."` (a label we keep next to the numbers).

## Examples

Two scripts that use the package are shown in full in their guides, with what each does and what it cannot do: [a quality gate for CI](https://audiolab.tools/docs/start/audio-quality-gate-in-ci) (`audio_gate.py`, exit code 1 when a file breaks your limits) and [an audit of a dataset of conversations](https://audiolab.tools/docs/start/audit-a-conversation-dataset) (`audit_dataset.py`, one CSV row per file). Both upload the files to the API: see the guides.

## Where you stand

```python
usage = AudioLab().usage()          # needs a key; free to call, and it does not count
print(usage["plan"], usage["calls"]["used"], "of", usage["calls"]["limit"])
```

The same numbers as [/account](https://audiolab.tools/account): the plan, the calls and data used this month against the limits the API enforces, and the requests per minute.

## Errors

Every error has a stable `code` and a section on [/docs/errors](https://audiolab.tools/docs/errors) with the cause, the fix and an example. Each code is its own exception class, under a base class per HTTP status:

```python
from audiolabtools import AudioLab, DataQuotaExceeded, PlanLimitError, RateLimitError

try:
    client.loudness("master.wav")
except DataQuotaExceeded as e:        # one code
    print(e.usage)                    # {"window": "month", "limit": ..., "used": ...}
except RateLimitError as e:           # any 429
    time.sleep(e.retry_after or 60)
except PlanLimitError as e:           # any 402
    print(e.docs_url)
```

Every exception carries `status`, `code`, `message`, `docs_url`, `usage` (on a limit) and `retry_after` (on a 429). Code against `code`, not against the wording of `message`. A code this version does not know yet arrives as the base class of its status with `code` set, so an upgrade of the API does not break a `try`. The package does not retry: you decide.

<!-- generated:errors begin -->
| Exception | Status | Code | When |
|---|---|---|---|
| `InvalidApiKey` | 401 | `invalid_api_key` | Invalid or revoked API key |
| `RateLimited` | 429 | `rate_limited` | Too many requests this minute |
| `QuotaExceeded` | 402 | `quota_exceeded` | Monthly call limit reached |
| `DataQuotaExceeded` | 402 | `data_quota_exceeded` | Monthly data limit reached |
| `ProfileRequiresPaidPlan` | 402 | `profile_requires_paid_plan` | This preset needs a paid plan |
| `FileTooLarge` | 413 | `file_too_large` | File too large |
| `TrackTooLong` | 413 | `track_too_long` | Recording too long |
| `UnsupportedFormat` | 422 | `unsupported_format` | This is not audio we can read |
| `UrlNotAllowed` | 400 | `url_not_allowed` | URL not allowed |
| `UrlFetchFailed` | 400 | `url_fetch_failed` | Could not fetch the audio |
| `InvalidJson` | 400 | `invalid_json` | Request body is not valid JSON |
| `MissingInput` | 400 | `missing_input` | No audio in the request |
| `InvalidParameter` | 400 | `invalid_parameter` | A parameter is not valid |
| `BatchTooLarge` | 400 | `batch_too_large` | Too many files in one batch |
| `UploadNotFound` | 400 | `upload_not_found` | Uploaded file not found |
| `UnknownEndpoint` | 404 | `unknown_endpoint` | Unknown endpoint |
| `MethodNotAllowed` | 405 | `method_not_allowed` | Use POST (or GET on /v1/usage) |
| `StorageUnavailable` | 500 | `storage_unavailable` | Storage not available |
| `AnalysisError` | 500 | `analysis_error` | Something went wrong on our side |
| `MissingApiKey` | none | `missing_api_key` | No API key configured (MCP server or Python client) |
| `ApiTimeout` | none | `api_timeout` | The API took too long (MCP server or Python client) |
| `ApiUnreachable` | none | `api_unreachable` | The API could not be reached (MCP server or Python client) |
| `InvalidInput` | none | `invalid_input` | The call is missing something (MCP server or Python client) |
<!-- generated:errors end -->

## Config

```python
client = AudioLab(
    api_key="al_live_...",                  # default: the AUDIOLAB_KEY environment variable
    base_url="https://audiolab.tools/v1",   # default; must be https (or this machine)
    timeout=330,                            # seconds; just above the API's own ceiling, because a long file streams for a while
)

with AudioLab() as client:                  # optional: no connection is held open
    ...
```

## What it does not do

It measures; it does not normalise or write audio, and it does not read the words. A number is only as good as its status: see [what AudioLab does not do](https://audiolab.tools/docs/start/what-audiolab-does-not-do) and [how to read a response](https://audiolab.tools/docs/start/read-a-response). Changes to the API are announced on the [changelog](https://audiolab.tools/changelog); new fields arrive behind an opt-in.

MIT licensed. The analysis behind the API is a paid product with a free tier; this client is free to copy and fork.
