@audd/sdk 1.4.7
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.
- package/LICENSE +21 -0
- package/README.md +335 -0
- package/dist/http-y2YwjfAl.d.cts +37 -0
- package/dist/http-y2YwjfAl.d.ts +37 -0
- package/dist/index.cjs +1442 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +533 -0
- package/dist/index.d.ts +533 -0
- package/dist/index.js +1398 -0
- package/dist/index.js.map +1 -0
- package/dist/longpoll.cjs +246 -0
- package/dist/longpoll.cjs.map +1 -0
- package/dist/longpoll.d.cts +58 -0
- package/dist/longpoll.d.ts +58 -0
- package/dist/longpoll.js +244 -0
- package/dist/longpoll.js.map +1 -0
- package/package.json +65 -0
package/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.
|
package/README.md
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# audd
|
|
2
|
+
|
|
3
|
+
[](https://github.com/AudDMusic/audd-node/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/AudDMusic/audd-node/actions/workflows/contract.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@audd/sdk)
|
|
6
|
+
|
|
7
|
+
Official TypeScript / Node.js SDK for the [AudD](https://audd.io) music
|
|
8
|
+
recognition API.
|
|
9
|
+
|
|
10
|
+
AudD identifies music from a short audio clip, a URL, a long file, or a
|
|
11
|
+
live stream. The HTTPS API is a plain form-POST — every endpoint is
|
|
12
|
+
documented at **[docs.audd.io](https://docs.audd.io)** and you can call
|
|
13
|
+
it from anywhere `fetch` works. This package adds typed result models
|
|
14
|
+
with helpers for cover art, streaming-provider URLs, and previews;
|
|
15
|
+
`AbortSignal`-aware async; ESM and CJS dual-build; and a separate
|
|
16
|
+
browser-safe entry point for tokenless longpoll widgets.
|
|
17
|
+
|
|
18
|
+
## Quickstart
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @audd/sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Recognize from a URL:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { AudD } from "@audd/sdk";
|
|
28
|
+
|
|
29
|
+
const audd = new AudD("test"); // grab a real token at https://dashboard.audd.io
|
|
30
|
+
const song = await audd.recognize("https://audd.tech/example.mp3");
|
|
31
|
+
if (song) {
|
|
32
|
+
console.log(`${song.artist} — ${song.title}`);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Recognize a local file (Node):
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { AudD } from "@audd/sdk";
|
|
40
|
+
import { readFile } from "node:fs/promises";
|
|
41
|
+
|
|
42
|
+
const audd = new AudD("test");
|
|
43
|
+
|
|
44
|
+
// Pass a path…
|
|
45
|
+
const song = await audd.recognize("./clip.mp3");
|
|
46
|
+
|
|
47
|
+
// …or pass bytes directly.
|
|
48
|
+
const bytes = await readFile("./clip.mp3");
|
|
49
|
+
const song2 = await audd.recognize(bytes);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
A `null` return means the server completed the request successfully but
|
|
53
|
+
found no match — distinct from an error, which throws.
|
|
54
|
+
|
|
55
|
+
## Authentication
|
|
56
|
+
|
|
57
|
+
Pass the token literally:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const audd = new AudD("d29ebb...");
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or set `AUDD_API_TOKEN` in the environment and construct without
|
|
64
|
+
arguments:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const audd = new AudD();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For long-running services that rotate credentials, swap the token at
|
|
71
|
+
runtime without aborting in-flight requests:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
audd.setApiToken(nextToken);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## What you get back
|
|
78
|
+
|
|
79
|
+
By default, `recognize` resolves to a typed `RecognitionResult` with core
|
|
80
|
+
tags plus AudD's universal song link — no metadata-block opt-in needed:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const song = await audd.recognize("https://audd.tech/example.mp3");
|
|
84
|
+
if (!song) return;
|
|
85
|
+
|
|
86
|
+
// Core fields
|
|
87
|
+
console.log(song.artist, song.title, song.album);
|
|
88
|
+
console.log(song.releaseDate, song.label, song.timecode);
|
|
89
|
+
|
|
90
|
+
// AudD's universal song page — links into every provider
|
|
91
|
+
console.log(song.songLink);
|
|
92
|
+
|
|
93
|
+
// Helpers — driven off songLink, work without any `return` opt-in
|
|
94
|
+
console.log(song.thumbnailUrl); // cover-art image, or null
|
|
95
|
+
console.log(song.streamingUrl("spotify")); // direct or lis.tn redirect
|
|
96
|
+
console.log(song.streamingUrls()); // map of provider -> URL
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If you need provider-specific metadata blocks, opt in per call. Request
|
|
100
|
+
only what you need — each provider you ask for adds latency:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const song = await audd.recognize("https://audd.tech/example.mp3", {
|
|
104
|
+
return: ["apple_music", "spotify"],
|
|
105
|
+
});
|
|
106
|
+
console.log(song?.appleMusic?.url); // direct Apple Music link
|
|
107
|
+
console.log(song?.spotify?.uri); // spotify:track:...
|
|
108
|
+
console.log(song?.previewUrl()); // first preview across requested providers, or null
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Valid `return` values: `apple_music`, `spotify`, `deezer`, `napster`,
|
|
112
|
+
`musicbrainz`. Blocks are `undefined` when not requested.
|
|
113
|
+
|
|
114
|
+
`streamingUrl(provider)` prefers the direct provider URL when you
|
|
115
|
+
requested that block via `return`, then falls back to the lis.tn redirect
|
|
116
|
+
when `songLink` is on `lis.tn`. YouTube has only the redirect path.
|
|
117
|
+
|
|
118
|
+
## Reading additional metadata
|
|
119
|
+
|
|
120
|
+
Every model carries an `extras` map with any server-side fields outside
|
|
121
|
+
the typed surface, plus a `rawResponse` of the full unparsed JSON. Use
|
|
122
|
+
`extras` to read undocumented or beta fields:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
console.log(song.extras); // any non-typed top-level fields
|
|
126
|
+
console.log(song.rawResponse); // the whole result object as the server returned it
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Long files (enterprise)
|
|
130
|
+
|
|
131
|
+
`recognizeEnterprise` accepts files up to several hours and returns a
|
|
132
|
+
flat array of matches:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const matches = await audd.recognizeEnterprise("./show.mp3", {
|
|
136
|
+
return: ["apple_music", "musicbrainz"],
|
|
137
|
+
limit: 20,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
for (const m of matches) {
|
|
141
|
+
console.log(m.timecode, m.score, m.artist, m.title, m.isrc);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The default per-call timeout is **1 hour** for this endpoint (60s for
|
|
146
|
+
standard recognition); override with `timeoutMs`.
|
|
147
|
+
|
|
148
|
+
## Errors
|
|
149
|
+
|
|
150
|
+
Every server error is a typed exception. Use `instanceof` to branch:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import {
|
|
154
|
+
AudD,
|
|
155
|
+
AudDAPIError,
|
|
156
|
+
AudDAuthenticationError,
|
|
157
|
+
AudDQuotaError,
|
|
158
|
+
AudDSubscriptionError,
|
|
159
|
+
AudDInvalidAudioError,
|
|
160
|
+
AudDRateLimitError,
|
|
161
|
+
AudDConnectionError,
|
|
162
|
+
} from "@audd/sdk";
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
await audd.recognize("./clip.mp3");
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (err instanceof AudDAuthenticationError) {
|
|
168
|
+
// 900 / 901 / 903 — token rejected
|
|
169
|
+
} else if (err instanceof AudDQuotaError) {
|
|
170
|
+
// 902 — out of credits
|
|
171
|
+
} else if (err instanceof AudDSubscriptionError) {
|
|
172
|
+
// 904 / 905 — endpoint not enabled on this token
|
|
173
|
+
} else if (err instanceof AudDInvalidAudioError) {
|
|
174
|
+
// 300 / 400 / 500 — file unreadable / too short / unsupported
|
|
175
|
+
} else if (err instanceof AudDRateLimitError) {
|
|
176
|
+
// 611 — too many requests, slow down
|
|
177
|
+
} else if (err instanceof AudDConnectionError) {
|
|
178
|
+
// network failure or aborted request
|
|
179
|
+
} else if (err instanceof AudDAPIError) {
|
|
180
|
+
console.error(err.errorCode, err.serverMessage, err.requestId);
|
|
181
|
+
} else {
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Every `AudDAPIError` exposes `errorCode`, `serverMessage`, `httpStatus`,
|
|
188
|
+
`requestId`, `requestedParams`, `requestMethod`, `brandedMessage`, and
|
|
189
|
+
`rawResponse`. The full hierarchy lives in
|
|
190
|
+
[`src/errors.ts`](src/errors.ts).
|
|
191
|
+
|
|
192
|
+
## Configuration
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { AudD } from "@audd/sdk";
|
|
196
|
+
|
|
197
|
+
const audd = new AudD("...token...", {
|
|
198
|
+
maxRetries: 3, // retry budget per call
|
|
199
|
+
backoffFactorMs: 500, // initial backoff (ms), jittered, exponential
|
|
200
|
+
fetch: customFetch, // bring your own fetch (proxy, mTLS, observability)
|
|
201
|
+
onEvent: (e) => { // request/response/exception inspection hook
|
|
202
|
+
console.log(e.method, e.httpStatus, e.elapsedMs, e.requestId);
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Per-call cancellation via `AbortSignal`, including for multi-hour
|
|
208
|
+
enterprise calls:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const controller = new AbortController();
|
|
212
|
+
setTimeout(() => controller.abort(), 30_000);
|
|
213
|
+
|
|
214
|
+
const matches = await audd.recognizeEnterprise("./show.mp3", {
|
|
215
|
+
signal: controller.signal,
|
|
216
|
+
limit: 50,
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The constructor also accepts an options-only form
|
|
221
|
+
(`new AudD({ apiToken, ... })`) if you'd rather pass everything as one
|
|
222
|
+
object — equivalent to the two-argument form above.
|
|
223
|
+
|
|
224
|
+
A single client instance handles concurrent requests fine; spin up one
|
|
225
|
+
per process, not one per call.
|
|
226
|
+
|
|
227
|
+
## Streams
|
|
228
|
+
|
|
229
|
+
Real-time recognition over a live audio stream. Once a stream is
|
|
230
|
+
registered, AudD POSTs each match to your callback URL — or if you
|
|
231
|
+
can't host one, drains events to a longpoll endpoint instead.
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
await audd.streams.setCallbackUrl("https://your.app/audd-callback", {
|
|
235
|
+
returnMetadata: ["apple_music", "musicbrainz"],
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await audd.streams.add({
|
|
239
|
+
url: "https://stream.example/live.m3u8",
|
|
240
|
+
radioId: 12345,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const streams = await audd.streams.list();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Parse incoming callback POSTs into a typed payload:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const payload = audd.streams.parseCallback(reqBodyJson);
|
|
250
|
+
if (payload.isResult) {
|
|
251
|
+
for (const r of payload.result!.results) {
|
|
252
|
+
console.log(r.artist, r.title, r.score);
|
|
253
|
+
}
|
|
254
|
+
} else if (payload.isNotification) {
|
|
255
|
+
console.log(payload.notification!.notificationCode,
|
|
256
|
+
payload.notification!.notificationMessage);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Receiving events without a callback URL (longpoll)
|
|
261
|
+
|
|
262
|
+
Useful when you can't expose a public HTTPS receiver. Before the first
|
|
263
|
+
event, the SDK runs a one-time `getCallbackUrl` preflight — AudD
|
|
264
|
+
silently discards events for accounts without any callback URL set, so
|
|
265
|
+
this catches the trap early. Pass `skipCallbackCheck: true` to opt out.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
for await (const event of audd.streams.longpoll(category, { timeout: 30 })) {
|
|
269
|
+
console.log(event);
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`category` is a 9-character string derived locally from your token and
|
|
274
|
+
`radioId`:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
const category = audd.streams.deriveLongpollCategory(12345);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Browser / widget consumers
|
|
281
|
+
|
|
282
|
+
The `audd/longpoll` sub-entry exports a tokenless `LongpollConsumer` for
|
|
283
|
+
front-end use. It carries no api_token — your server derives the
|
|
284
|
+
category and ships it to the browser. Bundlers tree-shake the auth
|
|
285
|
+
client out of the resulting bundle.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { LongpollConsumer } from "@audd/sdk/longpoll";
|
|
289
|
+
|
|
290
|
+
const consumer = new LongpollConsumer("abc123def");
|
|
291
|
+
for await (const event of consumer.iterate({ timeout: 30 })) {
|
|
292
|
+
console.log(event);
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Custom catalog (advanced — not for music recognition)
|
|
297
|
+
|
|
298
|
+
> The custom-catalog endpoint is **not** how you submit audio for
|
|
299
|
+
> recognition. For recognition, use `recognize()` or
|
|
300
|
+
> `recognizeEnterprise()`. This endpoint adds songs to your private
|
|
301
|
+
> fingerprint database. Requires special access — contact api@audd.io.
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
await audd.customCatalog.add({
|
|
305
|
+
audioId: 42,
|
|
306
|
+
source: "https://example.com/my-track.mp3",
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
A raw-request escape hatch is available under `audd.advanced.rawRequest`
|
|
311
|
+
for endpoints not yet wrapped on this SDK.
|
|
312
|
+
|
|
313
|
+
## Resource cleanup
|
|
314
|
+
|
|
315
|
+
Both `AudD` and `LongpollConsumer` implement `Symbol.asyncDispose` for
|
|
316
|
+
[explicit resource management](https://github.com/tc39/proposal-explicit-resource-management):
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
{
|
|
320
|
+
await using audd = new AudD("...");
|
|
321
|
+
await audd.recognize("...");
|
|
322
|
+
} // close() called automatically here
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Older runtimes can call `close()` manually.
|
|
326
|
+
|
|
327
|
+
## License
|
|
328
|
+
|
|
329
|
+
MIT — see [LICENSE](./LICENSE).
|
|
330
|
+
|
|
331
|
+
## Support
|
|
332
|
+
|
|
333
|
+
- Documentation: https://docs.audd.io
|
|
334
|
+
- Tokens: https://dashboard.audd.io
|
|
335
|
+
- Email: api@audd.io
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface HttpResponse {
|
|
2
|
+
jsonBody: unknown;
|
|
3
|
+
httpStatus: number;
|
|
4
|
+
requestId: string | null;
|
|
5
|
+
rawText: string;
|
|
6
|
+
}
|
|
7
|
+
type FetchLike = typeof globalThis.fetch;
|
|
8
|
+
interface HttpClientOptions {
|
|
9
|
+
apiToken: string;
|
|
10
|
+
fetch?: FetchLike;
|
|
11
|
+
/** Per-call timeout in ms. Defaults: 60_000 standard, 3_600_000 enterprise. */
|
|
12
|
+
defaultTimeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Form-data field value. Strings go through directly; Blob carries binary
|
|
16
|
+
* payloads with a filename when constructed via `new File()`.
|
|
17
|
+
*/
|
|
18
|
+
type FormFieldValue = string | Blob | undefined;
|
|
19
|
+
declare class HttpClient {
|
|
20
|
+
private apiToken;
|
|
21
|
+
private readonly fetchImpl;
|
|
22
|
+
private readonly defaultTimeoutMs;
|
|
23
|
+
constructor(opts: HttpClientOptions);
|
|
24
|
+
/** Atomically swap the token used for subsequent requests. */
|
|
25
|
+
setApiToken(newToken: string): void;
|
|
26
|
+
postForm(url: string, fields: Record<string, FormFieldValue>, opts?: {
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}): Promise<HttpResponse>;
|
|
30
|
+
get(url: string, params: Record<string, string | undefined>, opts?: {
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}): Promise<HttpResponse>;
|
|
34
|
+
private send;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { type FetchLike as F, HttpClient as H };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface HttpResponse {
|
|
2
|
+
jsonBody: unknown;
|
|
3
|
+
httpStatus: number;
|
|
4
|
+
requestId: string | null;
|
|
5
|
+
rawText: string;
|
|
6
|
+
}
|
|
7
|
+
type FetchLike = typeof globalThis.fetch;
|
|
8
|
+
interface HttpClientOptions {
|
|
9
|
+
apiToken: string;
|
|
10
|
+
fetch?: FetchLike;
|
|
11
|
+
/** Per-call timeout in ms. Defaults: 60_000 standard, 3_600_000 enterprise. */
|
|
12
|
+
defaultTimeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Form-data field value. Strings go through directly; Blob carries binary
|
|
16
|
+
* payloads with a filename when constructed via `new File()`.
|
|
17
|
+
*/
|
|
18
|
+
type FormFieldValue = string | Blob | undefined;
|
|
19
|
+
declare class HttpClient {
|
|
20
|
+
private apiToken;
|
|
21
|
+
private readonly fetchImpl;
|
|
22
|
+
private readonly defaultTimeoutMs;
|
|
23
|
+
constructor(opts: HttpClientOptions);
|
|
24
|
+
/** Atomically swap the token used for subsequent requests. */
|
|
25
|
+
setApiToken(newToken: string): void;
|
|
26
|
+
postForm(url: string, fields: Record<string, FormFieldValue>, opts?: {
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}): Promise<HttpResponse>;
|
|
30
|
+
get(url: string, params: Record<string, string | undefined>, opts?: {
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}): Promise<HttpResponse>;
|
|
34
|
+
private send;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { type FetchLike as F, HttpClient as H };
|