audd 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- audd-1.4.0/.gitignore +14 -0
- audd-1.4.0/CHANGELOG.md +95 -0
- audd-1.4.0/LICENSE +21 -0
- audd-1.4.0/PKG-INFO +193 -0
- audd-1.4.0/README.md +155 -0
- audd-1.4.0/pyproject.toml +91 -0
- audd-1.4.0/src/audd/__init__.py +46 -0
- audd-1.4.0/src/audd/_callbacks.py +69 -0
- audd-1.4.0/src/audd/_http.py +182 -0
- audd-1.4.0/src/audd/_retry.py +150 -0
- audd-1.4.0/src/audd/_source.py +99 -0
- audd-1.4.0/src/audd/_user_agent.py +13 -0
- audd-1.4.0/src/audd/_version.py +3 -0
- audd-1.4.0/src/audd/advanced.py +86 -0
- audd-1.4.0/src/audd/client.py +619 -0
- audd-1.4.0/src/audd/custom_catalog.py +91 -0
- audd-1.4.0/src/audd/errors.py +198 -0
- audd-1.4.0/src/audd/longpoll.py +184 -0
- audd-1.4.0/src/audd/models.py +408 -0
- audd-1.4.0/src/audd/streams.py +284 -0
audd-1.4.0/.gitignore
ADDED
audd-1.4.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
## [1.4.0] - 2026-05-05
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Brand casing fix.** Class names now match the brand `AudD` (capital D) instead of the previous `Au-d-d` PascalCase: `AudD`, `AsyncAudD`, `AudDError`, `AudDAPIError`, `AudDAuthenticationError`, `AudDQuotaError`, `AudDSubscriptionError`, `AudDCustomCatalogAccessError`, `AudDInvalidRequestError`, `AudDInvalidAudioError`, `AudDRateLimitError`, `AudDStreamLimitError`, `AudDNotReleasedError`, `AudDBlockedError`, `AudDNeedsUpdateError`, `AudDServerError`, `AudDConnectionError`, `AudDSerializationError`, `AudDEvent`. The package import path stays `audd` (lowercase), and the env var stays `AUDD_API_TOKEN`.
|
|
10
|
+
- **README polish.** Streams gets a top-level section that includes tokenless longpoll as a subsection ("Receiving events without exposing your token"); the capability table groups longpoll with streams. Concurrency note moved into Configuration as a brief one-liner. Removed internal "design spec" references from public docstrings.
|
|
11
|
+
|
|
12
|
+
## [1.3.0] - 2026-05-05
|
|
13
|
+
|
|
14
|
+
Coordinated v1.3.0 stable release across the audd-sdks family. No breaking
|
|
15
|
+
changes; the version bump signals API stability across all nine SDKs.
|
|
16
|
+
|
|
17
|
+
The full v0.3.0 polish — env-var auto-pickup, streaming/preview helpers
|
|
18
|
+
with metadata fallback, `onEvent` inspection hook, thread-safe token
|
|
19
|
+
rotation, plus per-language work (JPMS module-info, Kotlin `Flow<LongpollEvent>`,
|
|
20
|
+
Swift `Sendable` + DocC, .NET AOT/source-gen + `IServiceCollection`, Rust
|
|
21
|
+
TLS feature flags + `Serialize`, PHP PSR-3 logger, Python `__repr__` +
|
|
22
|
+
`pretty_print()`, Go `slog` example) is now the v1.3.0 baseline.
|
|
23
|
+
|
|
24
|
+
## [0.3.0] - 2026-05-05
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **`RecognitionResult.__repr__` and `EnterpriseMatch.__repr__`** — useful one-line representations for logs / REPL: `<RecognitionResult artist='X' title='Y' timecode='00:56' song_link='https://lis.tn/abc'>`. Empty fields are omitted; `timecode` is always present (it's required server-side).
|
|
29
|
+
- **`RecognitionResult.pretty_print()` / `EnterpriseMatch.pretty_print()`** — debug helper that writes the full model state (typed fields plus any forward-compat extras) as indented JSON to stdout (or a caller-supplied stream). Useful when you want to see exactly what came back over the wire.
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
|
|
33
|
+
- README opens with an explicit "thread-safe / safe for concurrent use" statement.
|
|
34
|
+
|
|
35
|
+
## [0.2.1] - 2026-05-05
|
|
36
|
+
|
|
37
|
+
### Improved
|
|
38
|
+
|
|
39
|
+
- **`streaming_url(provider)` now falls back to the metadata block when `song_link` is non-lis.tn.** Previously returned `None` for YouTube `song_link` values; now picks the direct URL from `apple_music.url` / `spotify.external_urls.spotify` / `deezer.link` / `napster.href` when the corresponding metadata was requested via `return=`. Direct URLs are also now *preferred* over the lis.tn redirect when both paths resolve (no redirect = faster client-side load). YouTube as a provider has no metadata-block fallback (only the lis.tn redirect path).
|
|
40
|
+
- `streaming_urls()` correspondingly returns more entries — every provider with either a direct URL or a lis.tn redirect available.
|
|
41
|
+
|
|
42
|
+
## [0.2.0] - 2026-05-05
|
|
43
|
+
|
|
44
|
+
DX polish — env-var auto-pickup, streaming/preview helpers, on_event inspection hook, thread-safe token rotation.
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- **`AUDD_API_TOKEN` env-var fallback** (§7.11): `AudD()` / `AsyncAudD()` constructed without an explicit `api_token` now reads it from `os.environ.get("AUDD_API_TOKEN")`. Raises `ValueError` with a dashboard-link hint if both are missing.
|
|
49
|
+
- **`RecognitionResult.streaming_url(provider)`** (§4.3): returns the `lis.tn` redirect URL for `spotify` / `apple_music` / `deezer` / `napster` / `youtube` (e.g. `https://lis.tn/abc?spotify` 302-redirects to the song's Spotify page). Returns `None` for non-lis.tn `song_link` values (e.g., YouTube ones).
|
|
50
|
+
- **`RecognitionResult.streaming_urls()`** (§4.3): convenience dict of all five providers' redirect URLs.
|
|
51
|
+
- **`RecognitionResult.preview_url()`** (§4.3): first available 30-second preview URL from `apple_music.previews[0].url` → `spotify.preview_url` → `deezer.preview`. Documented caveat about provider TOS.
|
|
52
|
+
- Same `streaming_url` / `streaming_urls` / `thumbnail_url` helpers on `EnterpriseMatch`.
|
|
53
|
+
- **`set_api_token(new_token)`** on `AudD` / `AsyncAudD` (§7.10): thread-safe token rotation via a `threading.Lock`. In-flight requests continue with the old token; subsequent requests use the new one. Validates non-empty.
|
|
54
|
+
- **`on_event` inspection hook** on `AudD` / `AsyncAudD` (§7.7a): a callable receiving `AudDEvent` lifecycle events (kind=`request`/`response`/`exception`, method, url, request_id, http_status, elapsed_ms, error_code, extras). Off by default. Never logs the api_token or request body. Hook exceptions are swallowed at debug level so observability never breaks the request path.
|
|
55
|
+
|
|
56
|
+
### Internal
|
|
57
|
+
|
|
58
|
+
- 26 new regression tests (`tests/unit/test_v0_2_polish.py`) covering all four feature areas.
|
|
59
|
+
|
|
60
|
+
## [0.1.1] - 2026-05-04
|
|
61
|
+
|
|
62
|
+
Independent code review caught several issues. v0.1.1 fixes them before they propagate to the other 8 language SDKs being modeled on this implementation.
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- **C1 — `prepare_source` now returns a per-attempt re-opener.** *Note: the reviewer flagged this as a "silent zero-byte upload on retry" bug, but verification (running the regression test against v0.1.0 source) showed that httpx auto-seeks file handles between `post()` calls, so audd-python v0.1.0 was actually correct.* The re-opener pattern is kept as **defense in depth**: it doesn't depend on the HTTP library's seeking behavior, raises cleanly on unseekable streams (rather than silently sending an empty body), and matches the mandatory pattern other SDKs need (their HTTP libraries may not auto-seek).
|
|
67
|
+
- **C2 — `advanced.find_lyrics` was using READ retry policy.** Fixed to use RECOGNITION (the lyrics endpoint is metered; the spec §7.1 cost-aware retry policy explicitly groups it with `recognize`).
|
|
68
|
+
- **C3 — Code 51 (deprecation warning) was raising unconditionally.** Per spec §6.5, when the server returns code 51 with a usable `result`, the SDK now emits a `DeprecationWarning` and returns the result. Only raises if `result` is absent.
|
|
69
|
+
- **S2 — Non-JSON HTTP errors mapped to `AudDSerializationError`.** A 502 with an HTML body now correctly raises `AudDServerError` preserving the HTTP status; `AudDSerializationError` is reserved for 2xx-with-bad-JSON.
|
|
70
|
+
- **S5 — `LongpollConsumer` silently swallowed HTTP errors.** A 401/403/500 returned `{}` and the consumer looped forever, especially painful in browsers. Now non-2xx raises `AudDServerError`; JSON decode failures raise `AudDSerializationError`.
|
|
71
|
+
- **S6 — `LongpollConsumer` had no retry/timeout knobs.** Per spec §4.1 ("same retry, timeout, transport configurability as the authenticated client"), now accepts `max_retries` and `backoff_factor` and applies READ-class retries on connection failures + 5xx.
|
|
72
|
+
- **S9 — Confusing `FileNotFoundError` for typo'd URLs.** A string source that's neither HTTP(S) nor an existing path now raises `TypeError` with a hint about the URL prefix.
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **Context-manager protocol** on `AudD`, `AsyncAudD`, `LongpollConsumer`, `AsyncLongpollConsumer` — `with AudD(...) as audd:` / `async with AsyncAudD(...) as audd:` now work.
|
|
77
|
+
|
|
78
|
+
### Tests
|
|
79
|
+
|
|
80
|
+
- Added 16 regression tests covering each finding (`tests/unit/test_review_fixes.py`, `tests/unit/test_review_fixes_longpoll.py`). Total: 108 unit + contract tests passing.
|
|
81
|
+
|
|
82
|
+
## [0.1.0] - 2026-05-04
|
|
83
|
+
|
|
84
|
+
### Added
|
|
85
|
+
- Sync (`AudD`) and async (`AsyncAudD`) clients with parity for every capability.
|
|
86
|
+
- `recognize(source, *, return_, market, timeout)` — auto-detects URL / path / file-like / bytes sources.
|
|
87
|
+
- `recognize_enterprise(source, ...)` with 1-hour read timeout default.
|
|
88
|
+
- `streams.*` namespace: `set_callback_url(url, return_metadata=...)`, `get_callback_url`, `add`, `set_url`, `delete`, `list`, `longpoll(category, *, skip_callback_check=False)` with default-on preflight, plus pure helpers `derive_longpoll_category` and `parse_callback`.
|
|
89
|
+
- `custom_catalog.add(audio_id, source)` — namespaced with NOT-for-recognition warning docstring; 904 errors raise `AudDCustomCatalogAccessError`.
|
|
90
|
+
- `advanced.*` namespace: `find_lyrics(query)`, `raw_request(method, params)` escape hatch.
|
|
91
|
+
- Tokenless `LongpollConsumer` / `AsyncLongpollConsumer` for browser/widget contexts.
|
|
92
|
+
- Forward-compatible Pydantic v2 models with `extra="allow"` on every type.
|
|
93
|
+
- Full error hierarchy mapped to the 25+ AudD error codes.
|
|
94
|
+
- Cost-aware retry policy: read endpoints retry 408/429/5xx; recognition endpoints don't retry post-upload read timeouts (cost protection); mutating endpoints retry only pre-upload connection failures.
|
|
95
|
+
- CI: ruff + mypy strict + pytest matrix on Py 3.9–3.13 (`ci.yml`); contract tests on push/PR + daily cron + `openapi-updated` repository_dispatch (`contract.yml`); tag-triggered PyPI publishing with Sigstore attestation (`release.yml`).
|
audd-1.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AudD (https://audd.io)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
audd-1.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: audd
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: Official Python SDK for the AudD music recognition API.
|
|
5
|
+
Project-URL: Homepage, https://audd.io
|
|
6
|
+
Project-URL: Documentation, https://docs.audd.io
|
|
7
|
+
Project-URL: Source, https://github.com/AudDMusic/audd-python
|
|
8
|
+
Project-URL: Issues, https://github.com/AudDMusic/audd-python/issues
|
|
9
|
+
Author-email: AudD <api@audd.io>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: audd,audio,fingerprinting,music-recognition
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: anyio>=4
|
|
26
|
+
Requires-Dist: httpx<1,>=0.27
|
|
27
|
+
Requires-Dist: pydantic<3,>=2.5
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: jsonschema>=4.21; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
33
|
+
Requires-Dist: pyyaml>=6; extra == 'dev'
|
|
34
|
+
Requires-Dist: referencing>=0.35; extra == 'dev'
|
|
35
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# audd
|
|
40
|
+
|
|
41
|
+
[](https://github.com/AudDMusic/audd-python/actions/workflows/ci.yml)
|
|
42
|
+
[](https://github.com/AudDMusic/audd-python/actions/workflows/contract.yml)
|
|
43
|
+
[](https://pypi.org/project/audd/)
|
|
44
|
+
[](https://pypi.org/project/audd/)
|
|
45
|
+
|
|
46
|
+
Official Python SDK for the [AudD](https://audd.io) music recognition API.
|
|
47
|
+
|
|
48
|
+
## Quickstart
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install audd
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from audd import AudD
|
|
56
|
+
|
|
57
|
+
audd = AudD(api_token="test") # use your token from https://dashboard.audd.io
|
|
58
|
+
result = audd.recognize("https://audd.tech/example.mp3")
|
|
59
|
+
if result:
|
|
60
|
+
print(f"{result.artist} — {result.title}")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Capabilities
|
|
64
|
+
|
|
65
|
+
| What | How |
|
|
66
|
+
|---|---|
|
|
67
|
+
| Recognize a short clip (≤25s) | `audd.recognize(source)` |
|
|
68
|
+
| Recognize a long file (hours, days) | `audd.recognize_enterprise(source, limit=...)` |
|
|
69
|
+
| Manage stream recognition (set callback, longpoll for events) | `audd.streams.*` |
|
|
70
|
+
|
|
71
|
+
`source` accepts a URL, a file path, a file-like object, or raw bytes — auto-detected.
|
|
72
|
+
|
|
73
|
+
## Async
|
|
74
|
+
|
|
75
|
+
Use `AsyncAudD` instead — same surface:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from audd import AsyncAudD
|
|
79
|
+
|
|
80
|
+
async def main():
|
|
81
|
+
audd = AsyncAudD(api_token="test")
|
|
82
|
+
try:
|
|
83
|
+
result = await audd.recognize("https://audd.tech/example.mp3")
|
|
84
|
+
print(result)
|
|
85
|
+
finally:
|
|
86
|
+
await audd.aclose()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Errors
|
|
90
|
+
|
|
91
|
+
Every server error becomes a typed exception:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from audd import AudD, AudDAuthenticationError, AudDSubscriptionError
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
AudD(api_token="bad").recognize("https://x.mp3")
|
|
98
|
+
except AudDAuthenticationError as e:
|
|
99
|
+
print(f"check your token: {e.error_code} {e.message}")
|
|
100
|
+
except AudDSubscriptionError:
|
|
101
|
+
print("this endpoint isn't enabled on your token")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The full hierarchy is documented in [`src/audd/errors.py`](src/audd/errors.py). Every `AudDAPIError` carries `error_code`, `message`, `http_status`, `request_id`, `requested_params`, `request_method`, `branded_message`, and `raw_response`.
|
|
105
|
+
|
|
106
|
+
## Forward compatibility
|
|
107
|
+
|
|
108
|
+
Models accept and round-trip unknown server fields via `model_extra`:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
result = audd.recognize("https://example.mp3", return_=["apple_music"])
|
|
112
|
+
print(result.apple_music.url) # typed
|
|
113
|
+
print(result.model_extra) # any other unknown fields
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
If AudD adds a new metadata block tomorrow (e.g., `tidal`), you can read it as `result.model_extra["tidal"]` *today* — no SDK release needed. The next SDK release adds the typed `tidal` field, and both paths keep working.
|
|
117
|
+
|
|
118
|
+
## Streams
|
|
119
|
+
|
|
120
|
+
Manage real-time stream recognition (set callback, longpoll for events):
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
audd.streams.set_callback_url("https://your.server/cb")
|
|
124
|
+
audd.streams.add("https://your.stream.url", radio_id=42)
|
|
125
|
+
for event in audd.streams.list():
|
|
126
|
+
print(event)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Receiving events without exposing your token
|
|
130
|
+
|
|
131
|
+
For browser widgets and other contexts where shipping the api_token would leak it,
|
|
132
|
+
derive a `category` server-side and share that with the consumer:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from audd import LongpollConsumer
|
|
136
|
+
|
|
137
|
+
# `category` is derived server-side via AudD(...).streams.derive_longpoll_category(radio_id),
|
|
138
|
+
# then shared with the browser/widget. The consumer carries no api_token.
|
|
139
|
+
consumer = LongpollConsumer(category="abc123def")
|
|
140
|
+
for event in consumer.iterate(timeout=30):
|
|
141
|
+
print(event)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Configuration
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import httpx
|
|
148
|
+
from audd import AudD
|
|
149
|
+
|
|
150
|
+
audd = AudD(
|
|
151
|
+
api_token="...",
|
|
152
|
+
max_retries=3, # retry budget per call
|
|
153
|
+
backoff_factor=0.5, # initial backoff seconds (jittered)
|
|
154
|
+
httpx_client=httpx.Client(proxies="http://corp-proxy:8080"),
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Default timeouts: 30s connect / 60s read for standard endpoints, 30s connect / **1 hour** read for the enterprise endpoint. Pass `timeout=` per call to override.
|
|
159
|
+
|
|
160
|
+
**Concurrency:** `AudD` and `AsyncAudD` are safe for concurrent use — share one instance across threads or asyncio tasks. `set_api_token(...)` rotates the token safely; in-flight requests continue with the old token, subsequent requests use the new one.
|
|
161
|
+
|
|
162
|
+
## Custom catalog (advanced — not for music recognition)
|
|
163
|
+
|
|
164
|
+
> ⚠ **The custom-catalog endpoint is NOT how you submit audio for music recognition.**
|
|
165
|
+
> For recognition, use `recognize()` or `recognize_enterprise()`. The custom-catalog
|
|
166
|
+
> endpoint adds songs to your private fingerprint database for *your* account.
|
|
167
|
+
> Requires special access — contact api@audd.io if you need it.
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
audd.custom_catalog.add(audio_id=42, source="https://my.song.mp3")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Advanced
|
|
174
|
+
|
|
175
|
+
For endpoints not yet wrapped by typed methods on this SDK, use the raw-request escape hatch:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
raw = audd.advanced.raw_request("someNewMethod", {"q": "x"})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Spec contract
|
|
182
|
+
|
|
183
|
+
This SDK builds against the [`audd-openapi`](https://github.com/AudDMusic/audd-openapi) spec. The contract tests in `tests/contract/` validate the parser against the canonical fixture set on every push, on a daily cron, and on every spec update.
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT — see [LICENSE](./LICENSE).
|
|
188
|
+
|
|
189
|
+
## Support
|
|
190
|
+
|
|
191
|
+
- Documentation: https://docs.audd.io
|
|
192
|
+
- Tokens: https://dashboard.audd.io
|
|
193
|
+
- Email: api@audd.io
|
audd-1.4.0/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# audd
|
|
2
|
+
|
|
3
|
+
[](https://github.com/AudDMusic/audd-python/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/AudDMusic/audd-python/actions/workflows/contract.yml)
|
|
5
|
+
[](https://pypi.org/project/audd/)
|
|
6
|
+
[](https://pypi.org/project/audd/)
|
|
7
|
+
|
|
8
|
+
Official Python SDK for the [AudD](https://audd.io) music recognition API.
|
|
9
|
+
|
|
10
|
+
## Quickstart
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install audd
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from audd import AudD
|
|
18
|
+
|
|
19
|
+
audd = AudD(api_token="test") # use your token from https://dashboard.audd.io
|
|
20
|
+
result = audd.recognize("https://audd.tech/example.mp3")
|
|
21
|
+
if result:
|
|
22
|
+
print(f"{result.artist} — {result.title}")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Capabilities
|
|
26
|
+
|
|
27
|
+
| What | How |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Recognize a short clip (≤25s) | `audd.recognize(source)` |
|
|
30
|
+
| Recognize a long file (hours, days) | `audd.recognize_enterprise(source, limit=...)` |
|
|
31
|
+
| Manage stream recognition (set callback, longpoll for events) | `audd.streams.*` |
|
|
32
|
+
|
|
33
|
+
`source` accepts a URL, a file path, a file-like object, or raw bytes — auto-detected.
|
|
34
|
+
|
|
35
|
+
## Async
|
|
36
|
+
|
|
37
|
+
Use `AsyncAudD` instead — same surface:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from audd import AsyncAudD
|
|
41
|
+
|
|
42
|
+
async def main():
|
|
43
|
+
audd = AsyncAudD(api_token="test")
|
|
44
|
+
try:
|
|
45
|
+
result = await audd.recognize("https://audd.tech/example.mp3")
|
|
46
|
+
print(result)
|
|
47
|
+
finally:
|
|
48
|
+
await audd.aclose()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Errors
|
|
52
|
+
|
|
53
|
+
Every server error becomes a typed exception:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from audd import AudD, AudDAuthenticationError, AudDSubscriptionError
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
AudD(api_token="bad").recognize("https://x.mp3")
|
|
60
|
+
except AudDAuthenticationError as e:
|
|
61
|
+
print(f"check your token: {e.error_code} {e.message}")
|
|
62
|
+
except AudDSubscriptionError:
|
|
63
|
+
print("this endpoint isn't enabled on your token")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The full hierarchy is documented in [`src/audd/errors.py`](src/audd/errors.py). Every `AudDAPIError` carries `error_code`, `message`, `http_status`, `request_id`, `requested_params`, `request_method`, `branded_message`, and `raw_response`.
|
|
67
|
+
|
|
68
|
+
## Forward compatibility
|
|
69
|
+
|
|
70
|
+
Models accept and round-trip unknown server fields via `model_extra`:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
result = audd.recognize("https://example.mp3", return_=["apple_music"])
|
|
74
|
+
print(result.apple_music.url) # typed
|
|
75
|
+
print(result.model_extra) # any other unknown fields
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If AudD adds a new metadata block tomorrow (e.g., `tidal`), you can read it as `result.model_extra["tidal"]` *today* — no SDK release needed. The next SDK release adds the typed `tidal` field, and both paths keep working.
|
|
79
|
+
|
|
80
|
+
## Streams
|
|
81
|
+
|
|
82
|
+
Manage real-time stream recognition (set callback, longpoll for events):
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
audd.streams.set_callback_url("https://your.server/cb")
|
|
86
|
+
audd.streams.add("https://your.stream.url", radio_id=42)
|
|
87
|
+
for event in audd.streams.list():
|
|
88
|
+
print(event)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Receiving events without exposing your token
|
|
92
|
+
|
|
93
|
+
For browser widgets and other contexts where shipping the api_token would leak it,
|
|
94
|
+
derive a `category` server-side and share that with the consumer:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from audd import LongpollConsumer
|
|
98
|
+
|
|
99
|
+
# `category` is derived server-side via AudD(...).streams.derive_longpoll_category(radio_id),
|
|
100
|
+
# then shared with the browser/widget. The consumer carries no api_token.
|
|
101
|
+
consumer = LongpollConsumer(category="abc123def")
|
|
102
|
+
for event in consumer.iterate(timeout=30):
|
|
103
|
+
print(event)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import httpx
|
|
110
|
+
from audd import AudD
|
|
111
|
+
|
|
112
|
+
audd = AudD(
|
|
113
|
+
api_token="...",
|
|
114
|
+
max_retries=3, # retry budget per call
|
|
115
|
+
backoff_factor=0.5, # initial backoff seconds (jittered)
|
|
116
|
+
httpx_client=httpx.Client(proxies="http://corp-proxy:8080"),
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Default timeouts: 30s connect / 60s read for standard endpoints, 30s connect / **1 hour** read for the enterprise endpoint. Pass `timeout=` per call to override.
|
|
121
|
+
|
|
122
|
+
**Concurrency:** `AudD` and `AsyncAudD` are safe for concurrent use — share one instance across threads or asyncio tasks. `set_api_token(...)` rotates the token safely; in-flight requests continue with the old token, subsequent requests use the new one.
|
|
123
|
+
|
|
124
|
+
## Custom catalog (advanced — not for music recognition)
|
|
125
|
+
|
|
126
|
+
> ⚠ **The custom-catalog endpoint is NOT how you submit audio for music recognition.**
|
|
127
|
+
> For recognition, use `recognize()` or `recognize_enterprise()`. The custom-catalog
|
|
128
|
+
> endpoint adds songs to your private fingerprint database for *your* account.
|
|
129
|
+
> Requires special access — contact api@audd.io if you need it.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
audd.custom_catalog.add(audio_id=42, source="https://my.song.mp3")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Advanced
|
|
136
|
+
|
|
137
|
+
For endpoints not yet wrapped by typed methods on this SDK, use the raw-request escape hatch:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
raw = audd.advanced.raw_request("someNewMethod", {"q": "x"})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Spec contract
|
|
144
|
+
|
|
145
|
+
This SDK builds against the [`audd-openapi`](https://github.com/AudDMusic/audd-openapi) spec. The contract tests in `tests/contract/` validate the parser against the canonical fixture set on every push, on a daily cron, and on every spec update.
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT — see [LICENSE](./LICENSE).
|
|
150
|
+
|
|
151
|
+
## Support
|
|
152
|
+
|
|
153
|
+
- Documentation: https://docs.audd.io
|
|
154
|
+
- Tokens: https://dashboard.audd.io
|
|
155
|
+
- Email: api@audd.io
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "audd"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Official Python SDK for the AudD music recognition API."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "AudD", email = "api@audd.io" }]
|
|
13
|
+
keywords = ["audd", "music-recognition", "audio", "fingerprinting"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Multimedia :: Sound/Audio :: Analysis",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"httpx>=0.27,<1",
|
|
29
|
+
"pydantic>=2.5,<3",
|
|
30
|
+
"anyio>=4",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://audd.io"
|
|
35
|
+
Documentation = "https://docs.audd.io"
|
|
36
|
+
Source = "https://github.com/AudDMusic/audd-python"
|
|
37
|
+
Issues = "https://github.com/AudDMusic/audd-python/issues"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=8",
|
|
42
|
+
"pytest-asyncio>=0.23",
|
|
43
|
+
"respx>=0.21",
|
|
44
|
+
"mypy>=1.10",
|
|
45
|
+
"ruff>=0.5",
|
|
46
|
+
"pyyaml>=6",
|
|
47
|
+
"jsonschema>=4.21",
|
|
48
|
+
"referencing>=0.35",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.hatch.version]
|
|
52
|
+
path = "src/audd/_version.py"
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.sdist]
|
|
55
|
+
include = ["src/audd", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.wheel]
|
|
58
|
+
packages = ["src/audd"]
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
asyncio_mode = "auto"
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
addopts = "-ra --strict-markers"
|
|
64
|
+
markers = [
|
|
65
|
+
"integration: opt-in tests that hit the live AudD API (requires AUDD_API_TOKEN env var)",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[tool.mypy]
|
|
69
|
+
strict = true
|
|
70
|
+
python_version = "3.9"
|
|
71
|
+
files = ["src/audd"]
|
|
72
|
+
|
|
73
|
+
[[tool.mypy.overrides]]
|
|
74
|
+
module = "tests.*"
|
|
75
|
+
disallow_untyped_defs = false
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
line-length = 100
|
|
79
|
+
target-version = "py39"
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint]
|
|
82
|
+
select = ["E", "F", "W", "I", "B", "UP", "N", "PL", "RUF"]
|
|
83
|
+
ignore = [
|
|
84
|
+
"PLR0913", # too many args — recognize_enterprise legitimately has many kwargs
|
|
85
|
+
"PLC0415", # lazy imports inside namespace getters break circular imports
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint.per-file-ignores]
|
|
89
|
+
"tests/**/*.py" = ["PLR2004", "PLR0915", "S101", "E501"] # magic values, long tests, asserts, line length
|
|
90
|
+
"examples/**/*.py" = ["PLR2004"] # argv-count comparisons in example scripts
|
|
91
|
+
"src/audd/models.py" = ["N815"] # Apple Music/Napster ship camelCase JSON keys; field names must match
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Official Python SDK for the AudD music recognition API."""
|
|
2
|
+
from audd._version import __version__
|
|
3
|
+
from audd.client import AsyncAudD, AudD
|
|
4
|
+
from audd.errors import (
|
|
5
|
+
AudDAPIError,
|
|
6
|
+
AudDAuthenticationError,
|
|
7
|
+
AudDBlockedError,
|
|
8
|
+
AudDConnectionError,
|
|
9
|
+
AudDCustomCatalogAccessError,
|
|
10
|
+
AudDError,
|
|
11
|
+
AudDInvalidAudioError,
|
|
12
|
+
AudDInvalidRequestError,
|
|
13
|
+
AudDNeedsUpdateError,
|
|
14
|
+
AudDNotReleasedError,
|
|
15
|
+
AudDQuotaError,
|
|
16
|
+
AudDRateLimitError,
|
|
17
|
+
AudDSerializationError,
|
|
18
|
+
AudDServerError,
|
|
19
|
+
AudDStreamLimitError,
|
|
20
|
+
AudDSubscriptionError,
|
|
21
|
+
)
|
|
22
|
+
from audd.longpoll import AsyncLongpollConsumer, LongpollConsumer
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"AsyncAudD",
|
|
26
|
+
"AsyncLongpollConsumer",
|
|
27
|
+
"AudD",
|
|
28
|
+
"AudDAPIError",
|
|
29
|
+
"AudDAuthenticationError",
|
|
30
|
+
"AudDBlockedError",
|
|
31
|
+
"AudDConnectionError",
|
|
32
|
+
"AudDCustomCatalogAccessError",
|
|
33
|
+
"AudDError",
|
|
34
|
+
"AudDInvalidAudioError",
|
|
35
|
+
"AudDInvalidRequestError",
|
|
36
|
+
"AudDNeedsUpdateError",
|
|
37
|
+
"AudDNotReleasedError",
|
|
38
|
+
"AudDQuotaError",
|
|
39
|
+
"AudDRateLimitError",
|
|
40
|
+
"AudDSerializationError",
|
|
41
|
+
"AudDServerError",
|
|
42
|
+
"AudDStreamLimitError",
|
|
43
|
+
"AudDSubscriptionError",
|
|
44
|
+
"LongpollConsumer",
|
|
45
|
+
"__version__",
|
|
46
|
+
]
|