@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/dist/index.cjs
ADDED
|
@@ -0,0 +1,1442 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var crypto = require('crypto');
|
|
6
|
+
|
|
7
|
+
function _interopNamespace(e) {
|
|
8
|
+
if (e && e.__esModule) return e;
|
|
9
|
+
var n = Object.create(null);
|
|
10
|
+
if (e) {
|
|
11
|
+
Object.keys(e).forEach(function (k) {
|
|
12
|
+
if (k !== 'default') {
|
|
13
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return e[k]; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
26
|
+
|
|
27
|
+
// src/version.ts
|
|
28
|
+
var VERSION = "1.4.6";
|
|
29
|
+
|
|
30
|
+
// src/errors.ts
|
|
31
|
+
var AudDError = class extends Error {
|
|
32
|
+
name = "AudDError";
|
|
33
|
+
};
|
|
34
|
+
var AudDAPIError = class extends AudDError {
|
|
35
|
+
name = "AudDAPIError";
|
|
36
|
+
errorCode;
|
|
37
|
+
httpStatus;
|
|
38
|
+
requestId;
|
|
39
|
+
requestedParams;
|
|
40
|
+
requestMethod;
|
|
41
|
+
brandedMessage;
|
|
42
|
+
rawResponse;
|
|
43
|
+
/** Original `error_message` from the server. (`Error.message` may be overridden by subclasses.) */
|
|
44
|
+
serverMessage;
|
|
45
|
+
constructor(init) {
|
|
46
|
+
super(`[#${init.errorCode}] ${init.message}`);
|
|
47
|
+
this.errorCode = init.errorCode;
|
|
48
|
+
this.serverMessage = init.message;
|
|
49
|
+
this.httpStatus = init.httpStatus;
|
|
50
|
+
this.requestId = init.requestId ?? null;
|
|
51
|
+
this.requestedParams = init.requestedParams ?? {};
|
|
52
|
+
this.requestMethod = init.requestMethod ?? null;
|
|
53
|
+
this.brandedMessage = init.brandedMessage ?? null;
|
|
54
|
+
this.rawResponse = init.rawResponse ?? null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var AudDAuthenticationError = class extends AudDAPIError {
|
|
58
|
+
name = "AudDAuthenticationError";
|
|
59
|
+
};
|
|
60
|
+
var AudDQuotaError = class extends AudDAPIError {
|
|
61
|
+
name = "AudDQuotaError";
|
|
62
|
+
};
|
|
63
|
+
var AudDSubscriptionError = class extends AudDAPIError {
|
|
64
|
+
name = "AudDSubscriptionError";
|
|
65
|
+
};
|
|
66
|
+
var AudDCustomCatalogAccessError = class extends AudDSubscriptionError {
|
|
67
|
+
name = "AudDCustomCatalogAccessError";
|
|
68
|
+
};
|
|
69
|
+
var AudDInvalidRequestError = class extends AudDAPIError {
|
|
70
|
+
name = "AudDInvalidRequestError";
|
|
71
|
+
};
|
|
72
|
+
var AudDInvalidAudioError = class extends AudDAPIError {
|
|
73
|
+
name = "AudDInvalidAudioError";
|
|
74
|
+
};
|
|
75
|
+
var AudDRateLimitError = class extends AudDAPIError {
|
|
76
|
+
name = "AudDRateLimitError";
|
|
77
|
+
};
|
|
78
|
+
var AudDStreamLimitError = class extends AudDAPIError {
|
|
79
|
+
name = "AudDStreamLimitError";
|
|
80
|
+
};
|
|
81
|
+
var AudDNotReleasedError = class extends AudDAPIError {
|
|
82
|
+
name = "AudDNotReleasedError";
|
|
83
|
+
};
|
|
84
|
+
var AudDBlockedError = class extends AudDAPIError {
|
|
85
|
+
name = "AudDBlockedError";
|
|
86
|
+
};
|
|
87
|
+
var AudDNeedsUpdateError = class extends AudDAPIError {
|
|
88
|
+
name = "AudDNeedsUpdateError";
|
|
89
|
+
};
|
|
90
|
+
var AudDServerError = class extends AudDAPIError {
|
|
91
|
+
name = "AudDServerError";
|
|
92
|
+
};
|
|
93
|
+
var AudDConnectionError = class extends AudDError {
|
|
94
|
+
name = "AudDConnectionError";
|
|
95
|
+
cause;
|
|
96
|
+
constructor(message, cause) {
|
|
97
|
+
super(message);
|
|
98
|
+
this.cause = cause;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var AudDSerializationError = class extends AudDError {
|
|
102
|
+
name = "AudDSerializationError";
|
|
103
|
+
rawText;
|
|
104
|
+
constructor(message, rawText = "") {
|
|
105
|
+
super(message);
|
|
106
|
+
this.rawText = rawText;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var CODE_MAP = {
|
|
110
|
+
900: AudDAuthenticationError,
|
|
111
|
+
901: AudDAuthenticationError,
|
|
112
|
+
903: AudDAuthenticationError,
|
|
113
|
+
902: AudDQuotaError,
|
|
114
|
+
904: AudDSubscriptionError,
|
|
115
|
+
905: AudDSubscriptionError,
|
|
116
|
+
50: AudDInvalidRequestError,
|
|
117
|
+
51: AudDInvalidRequestError,
|
|
118
|
+
600: AudDInvalidRequestError,
|
|
119
|
+
601: AudDInvalidRequestError,
|
|
120
|
+
602: AudDInvalidRequestError,
|
|
121
|
+
700: AudDInvalidRequestError,
|
|
122
|
+
701: AudDInvalidRequestError,
|
|
123
|
+
702: AudDInvalidRequestError,
|
|
124
|
+
906: AudDInvalidRequestError,
|
|
125
|
+
300: AudDInvalidAudioError,
|
|
126
|
+
400: AudDInvalidAudioError,
|
|
127
|
+
500: AudDInvalidAudioError,
|
|
128
|
+
610: AudDStreamLimitError,
|
|
129
|
+
611: AudDRateLimitError,
|
|
130
|
+
907: AudDNotReleasedError,
|
|
131
|
+
19: AudDBlockedError,
|
|
132
|
+
31337: AudDBlockedError,
|
|
133
|
+
20: AudDNeedsUpdateError,
|
|
134
|
+
100: AudDServerError,
|
|
135
|
+
1e3: AudDServerError
|
|
136
|
+
};
|
|
137
|
+
function errorClassForCode(code) {
|
|
138
|
+
return CODE_MAP[code] ?? AudDServerError;
|
|
139
|
+
}
|
|
140
|
+
function brandedMessageOf(result) {
|
|
141
|
+
if (typeof result !== "object" || result === null) return null;
|
|
142
|
+
const r = result;
|
|
143
|
+
const parts = [];
|
|
144
|
+
if (typeof r.artist === "string" && r.artist.length > 0) parts.push(r.artist);
|
|
145
|
+
if (typeof r.title === "string" && r.title.length > 0) parts.push(r.title);
|
|
146
|
+
return parts.length === 0 ? null : parts.join(" \u2014 ");
|
|
147
|
+
}
|
|
148
|
+
var CUSTOM_CATALOG_PREFIX = "Adding songs to your custom catalog requires enterprise access that isn't enabled on your account.\n\nNote: the custom-catalog endpoint is for adding songs to your private fingerprint database, not for music recognition. If you intended to identify music, use recognize(...) (or recognizeEnterprise(...) for files longer than 25 seconds) instead.\n\nTo request custom-catalog access, contact api@audd.io.\n\n";
|
|
149
|
+
function raiseFromErrorResponse(body, opts) {
|
|
150
|
+
const code = body.error?.error_code ?? 0;
|
|
151
|
+
const message = body.error?.error_message ?? "";
|
|
152
|
+
const requestedParams = body.request_params ?? body.requested_params ?? {};
|
|
153
|
+
const requestMethod = body.request_api_method ?? null;
|
|
154
|
+
const branded = brandedMessageOf(body.result);
|
|
155
|
+
const fields = {
|
|
156
|
+
errorCode: code,
|
|
157
|
+
message,
|
|
158
|
+
httpStatus: opts.httpStatus,
|
|
159
|
+
requestId: opts.requestId,
|
|
160
|
+
requestedParams,
|
|
161
|
+
requestMethod,
|
|
162
|
+
brandedMessage: branded,
|
|
163
|
+
rawResponse: body
|
|
164
|
+
};
|
|
165
|
+
const Cls = errorClassForCode(code);
|
|
166
|
+
if (opts.customCatalogContext === true && Cls === AudDSubscriptionError) {
|
|
167
|
+
const overridden = `${CUSTOM_CATALOG_PREFIX}[Server message: ${message}]`;
|
|
168
|
+
throw new AudDCustomCatalogAccessError({ ...fields, message: overridden });
|
|
169
|
+
}
|
|
170
|
+
throw new Cls(fields);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/userAgent.ts
|
|
174
|
+
function userAgent() {
|
|
175
|
+
const node = typeof process !== "undefined" && process.version ? process.version.replace(/^v/, "") : "unknown";
|
|
176
|
+
const platform = typeof process !== "undefined" && process.platform ? process.platform : "browser";
|
|
177
|
+
return `audd-node/${VERSION} node/${node} (${platform})`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/http.ts
|
|
181
|
+
var STANDARD_TIMEOUT_MS = 6e4;
|
|
182
|
+
var ENTERPRISE_TIMEOUT_MS = 36e5;
|
|
183
|
+
var HttpClient = class {
|
|
184
|
+
apiToken;
|
|
185
|
+
fetchImpl;
|
|
186
|
+
defaultTimeoutMs;
|
|
187
|
+
constructor(opts) {
|
|
188
|
+
this.apiToken = opts.apiToken;
|
|
189
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
190
|
+
this.defaultTimeoutMs = opts.defaultTimeoutMs ?? STANDARD_TIMEOUT_MS;
|
|
191
|
+
}
|
|
192
|
+
/** Atomically swap the token used for subsequent requests. */
|
|
193
|
+
setApiToken(newToken) {
|
|
194
|
+
this.apiToken = newToken;
|
|
195
|
+
}
|
|
196
|
+
async postForm(url, fields, opts = {}) {
|
|
197
|
+
const form = new FormData();
|
|
198
|
+
form.set("api_token", this.apiToken);
|
|
199
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
200
|
+
if (v === void 0) continue;
|
|
201
|
+
form.set(k, v);
|
|
202
|
+
}
|
|
203
|
+
return this.send(url, { method: "POST", body: form }, opts);
|
|
204
|
+
}
|
|
205
|
+
async get(url, params, opts = {}) {
|
|
206
|
+
const u = new URL(url);
|
|
207
|
+
if (!u.searchParams.has("api_token")) u.searchParams.set("api_token", this.apiToken);
|
|
208
|
+
for (const [k, v] of Object.entries(params)) {
|
|
209
|
+
if (v === void 0) continue;
|
|
210
|
+
u.searchParams.set(k, v);
|
|
211
|
+
}
|
|
212
|
+
return this.send(u.toString(), { method: "GET" }, opts);
|
|
213
|
+
}
|
|
214
|
+
async send(url, init, { timeoutMs, signal: userSignal }) {
|
|
215
|
+
const controller = new AbortController();
|
|
216
|
+
const timer = setTimeout(() => {
|
|
217
|
+
controller.abort();
|
|
218
|
+
}, timeoutMs ?? this.defaultTimeoutMs);
|
|
219
|
+
const userAbortHandler = () => controller.abort();
|
|
220
|
+
if (userSignal !== void 0) {
|
|
221
|
+
if (userSignal.aborted) {
|
|
222
|
+
controller.abort();
|
|
223
|
+
} else {
|
|
224
|
+
userSignal.addEventListener("abort", userAbortHandler, { once: true });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const headers = new Headers(init.headers);
|
|
229
|
+
headers.set("User-Agent", userAgent());
|
|
230
|
+
const response = await this.fetchImpl(url, {
|
|
231
|
+
...init,
|
|
232
|
+
headers,
|
|
233
|
+
signal: controller.signal
|
|
234
|
+
});
|
|
235
|
+
const text = await response.text();
|
|
236
|
+
let json = null;
|
|
237
|
+
if (text !== "") {
|
|
238
|
+
try {
|
|
239
|
+
json = JSON.parse(text);
|
|
240
|
+
} catch {
|
|
241
|
+
json = null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
jsonBody: json,
|
|
246
|
+
httpStatus: response.status,
|
|
247
|
+
requestId: response.headers.get("x-request-id"),
|
|
248
|
+
rawText: text
|
|
249
|
+
};
|
|
250
|
+
} finally {
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
if (userSignal !== void 0) {
|
|
253
|
+
userSignal.removeEventListener("abort", userAbortHandler);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// src/models.ts
|
|
260
|
+
function pickExtras(raw, knownKeys) {
|
|
261
|
+
const known = new Set(knownKeys);
|
|
262
|
+
const extras = {};
|
|
263
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
264
|
+
if (!known.has(k)) extras[k] = v;
|
|
265
|
+
}
|
|
266
|
+
return extras;
|
|
267
|
+
}
|
|
268
|
+
function asObject(raw) {
|
|
269
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
270
|
+
return raw;
|
|
271
|
+
}
|
|
272
|
+
return void 0;
|
|
273
|
+
}
|
|
274
|
+
function requireObject(raw, what) {
|
|
275
|
+
const o = asObject(raw);
|
|
276
|
+
if (o === void 0) throw new TypeError(`${what}: expected object`);
|
|
277
|
+
return o;
|
|
278
|
+
}
|
|
279
|
+
function asString(v) {
|
|
280
|
+
return typeof v === "string" ? v : void 0;
|
|
281
|
+
}
|
|
282
|
+
function asNumber(v) {
|
|
283
|
+
return typeof v === "number" ? v : void 0;
|
|
284
|
+
}
|
|
285
|
+
function asBoolean(v) {
|
|
286
|
+
return typeof v === "boolean" ? v : void 0;
|
|
287
|
+
}
|
|
288
|
+
var STREAMING_PROVIDERS = [
|
|
289
|
+
"spotify",
|
|
290
|
+
"apple_music",
|
|
291
|
+
"deezer",
|
|
292
|
+
"napster",
|
|
293
|
+
"youtube"
|
|
294
|
+
];
|
|
295
|
+
function lisTnStreamingUrl(songLink, provider) {
|
|
296
|
+
if (songLink === void 0 || songLink === "") return null;
|
|
297
|
+
let parsed;
|
|
298
|
+
try {
|
|
299
|
+
parsed = new URL(songLink);
|
|
300
|
+
} catch {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
if (parsed.hostname !== "lis.tn") return null;
|
|
304
|
+
const sep = parsed.search ? "&" : "?";
|
|
305
|
+
return `${songLink}${sep}${provider}`;
|
|
306
|
+
}
|
|
307
|
+
function directStreamingUrl(result, provider) {
|
|
308
|
+
if (provider === "apple_music" && result.appleMusic !== void 0) {
|
|
309
|
+
const url = result.appleMusic["url"];
|
|
310
|
+
if (typeof url === "string" && url.length > 0) return url;
|
|
311
|
+
} else if (provider === "spotify" && result.spotify !== void 0) {
|
|
312
|
+
const ext = result.spotify["external_urls"];
|
|
313
|
+
if (typeof ext === "object" && ext !== null) {
|
|
314
|
+
const sp = ext["spotify"];
|
|
315
|
+
if (typeof sp === "string" && sp.length > 0) return sp;
|
|
316
|
+
}
|
|
317
|
+
const uri = result.spotify["uri"];
|
|
318
|
+
if (typeof uri === "string" && uri.length > 0) return uri;
|
|
319
|
+
} else if (provider === "deezer" && result.deezer !== void 0) {
|
|
320
|
+
const link = result.deezer["link"];
|
|
321
|
+
if (typeof link === "string" && link.length > 0) return link;
|
|
322
|
+
} else if (provider === "napster" && result.napster !== void 0) {
|
|
323
|
+
const href = result.napster["href"];
|
|
324
|
+
if (typeof href === "string" && href.length > 0) return href;
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
function previewFrom(result) {
|
|
329
|
+
if (result.appleMusic !== void 0) {
|
|
330
|
+
const previews = result.appleMusic["previews"];
|
|
331
|
+
if (Array.isArray(previews) && previews.length > 0) {
|
|
332
|
+
const first = previews[0];
|
|
333
|
+
if (typeof first === "object" && first !== null) {
|
|
334
|
+
const url = first["url"];
|
|
335
|
+
if (typeof url === "string" && url.length > 0) return url;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (result.spotify !== void 0) {
|
|
340
|
+
const url = result.spotify["preview_url"];
|
|
341
|
+
if (typeof url === "string" && url.length > 0) return url;
|
|
342
|
+
}
|
|
343
|
+
if (result.deezer !== void 0) {
|
|
344
|
+
const url = result.deezer["preview"];
|
|
345
|
+
if (typeof url === "string" && url.length > 0) return url;
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
var RECOGNITION_KEYS = [
|
|
350
|
+
"timecode",
|
|
351
|
+
"audio_id",
|
|
352
|
+
"artist",
|
|
353
|
+
"title",
|
|
354
|
+
"album",
|
|
355
|
+
"release_date",
|
|
356
|
+
"label",
|
|
357
|
+
"song_link",
|
|
358
|
+
"apple_music",
|
|
359
|
+
"spotify",
|
|
360
|
+
"deezer",
|
|
361
|
+
"napster",
|
|
362
|
+
"musicbrainz"
|
|
363
|
+
];
|
|
364
|
+
function parseRecognitionResult(raw) {
|
|
365
|
+
const r = requireObject(raw, "RecognitionResult");
|
|
366
|
+
const timecode = asString(r.timecode);
|
|
367
|
+
if (timecode === void 0) {
|
|
368
|
+
throw new TypeError("RecognitionResult: timecode missing or not string");
|
|
369
|
+
}
|
|
370
|
+
const audioId = asNumber(r.audio_id);
|
|
371
|
+
const artist = asString(r.artist);
|
|
372
|
+
const title = asString(r.title);
|
|
373
|
+
const songLink = asString(r.song_link);
|
|
374
|
+
const musicbrainz = Array.isArray(r.musicbrainz) ? r.musicbrainz.filter((x) => asObject(x) !== void 0) : void 0;
|
|
375
|
+
const result = {
|
|
376
|
+
timecode,
|
|
377
|
+
audioId,
|
|
378
|
+
artist,
|
|
379
|
+
title,
|
|
380
|
+
album: asString(r.album),
|
|
381
|
+
releaseDate: asString(r.release_date),
|
|
382
|
+
label: asString(r.label),
|
|
383
|
+
songLink,
|
|
384
|
+
appleMusic: asObject(r.apple_music),
|
|
385
|
+
spotify: asObject(r.spotify),
|
|
386
|
+
deezer: asObject(r.deezer),
|
|
387
|
+
napster: asObject(r.napster),
|
|
388
|
+
musicbrainz,
|
|
389
|
+
extras: pickExtras(r, RECOGNITION_KEYS),
|
|
390
|
+
rawResponse: r,
|
|
391
|
+
get isCustomMatch() {
|
|
392
|
+
return audioId !== void 0;
|
|
393
|
+
},
|
|
394
|
+
get isPublicMatch() {
|
|
395
|
+
return audioId === void 0 && (artist !== void 0 || title !== void 0);
|
|
396
|
+
},
|
|
397
|
+
get thumbnailUrl() {
|
|
398
|
+
return lisTnStreamingUrl(songLink, "thumb");
|
|
399
|
+
},
|
|
400
|
+
streamingUrl(provider) {
|
|
401
|
+
if (!STREAMING_PROVIDERS.includes(provider)) {
|
|
402
|
+
throw new TypeError(
|
|
403
|
+
`Unknown streaming provider: ${String(provider)}. Valid: ${STREAMING_PROVIDERS.join(", ")}`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
const direct = directStreamingUrl(result, provider);
|
|
407
|
+
if (direct !== null) return direct;
|
|
408
|
+
return lisTnStreamingUrl(songLink, provider);
|
|
409
|
+
},
|
|
410
|
+
streamingUrls() {
|
|
411
|
+
const out = {};
|
|
412
|
+
for (const p of STREAMING_PROVIDERS) {
|
|
413
|
+
const url = result.streamingUrl(p);
|
|
414
|
+
if (url !== null) out[p] = url;
|
|
415
|
+
}
|
|
416
|
+
return out;
|
|
417
|
+
},
|
|
418
|
+
previewUrl() {
|
|
419
|
+
return previewFrom(result);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
var ENTERPRISE_MATCH_KEYS = [
|
|
425
|
+
"score",
|
|
426
|
+
"timecode",
|
|
427
|
+
"artist",
|
|
428
|
+
"title",
|
|
429
|
+
"album",
|
|
430
|
+
"release_date",
|
|
431
|
+
"label",
|
|
432
|
+
"isrc",
|
|
433
|
+
"upc",
|
|
434
|
+
"song_link",
|
|
435
|
+
"start_offset",
|
|
436
|
+
"end_offset"
|
|
437
|
+
];
|
|
438
|
+
function parseEnterpriseMatch(raw) {
|
|
439
|
+
const r = requireObject(raw, "EnterpriseMatch");
|
|
440
|
+
const score = asNumber(r.score);
|
|
441
|
+
const timecode = asString(r.timecode);
|
|
442
|
+
if (score === void 0) throw new TypeError("EnterpriseMatch: score not number");
|
|
443
|
+
if (timecode === void 0) throw new TypeError("EnterpriseMatch: timecode not string");
|
|
444
|
+
const songLink = asString(r.song_link);
|
|
445
|
+
return {
|
|
446
|
+
score,
|
|
447
|
+
timecode,
|
|
448
|
+
artist: asString(r.artist),
|
|
449
|
+
title: asString(r.title),
|
|
450
|
+
album: asString(r.album),
|
|
451
|
+
releaseDate: asString(r.release_date),
|
|
452
|
+
label: asString(r.label),
|
|
453
|
+
isrc: asString(r.isrc),
|
|
454
|
+
upc: asString(r.upc),
|
|
455
|
+
songLink,
|
|
456
|
+
startOffset: asNumber(r.start_offset),
|
|
457
|
+
endOffset: asNumber(r.end_offset),
|
|
458
|
+
extras: pickExtras(r, ENTERPRISE_MATCH_KEYS),
|
|
459
|
+
rawResponse: r,
|
|
460
|
+
streamingUrl(provider) {
|
|
461
|
+
if (!STREAMING_PROVIDERS.includes(provider)) {
|
|
462
|
+
throw new TypeError(`Unknown streaming provider: ${String(provider)}`);
|
|
463
|
+
}
|
|
464
|
+
return lisTnStreamingUrl(songLink, provider);
|
|
465
|
+
},
|
|
466
|
+
streamingUrls() {
|
|
467
|
+
const out = {};
|
|
468
|
+
for (const p of STREAMING_PROVIDERS) {
|
|
469
|
+
const url = lisTnStreamingUrl(songLink, p);
|
|
470
|
+
if (url !== null) out[p] = url;
|
|
471
|
+
}
|
|
472
|
+
return out;
|
|
473
|
+
},
|
|
474
|
+
get thumbnailUrl() {
|
|
475
|
+
if (songLink === void 0 || songLink === "") return null;
|
|
476
|
+
try {
|
|
477
|
+
const parsed = new URL(songLink);
|
|
478
|
+
if (parsed.hostname !== "lis.tn") return null;
|
|
479
|
+
const sep = parsed.search ? "&" : "?";
|
|
480
|
+
return `${songLink}${sep}thumb`;
|
|
481
|
+
} catch {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
var ENTERPRISE_CHUNK_KEYS = ["songs", "offset"];
|
|
488
|
+
function parseEnterpriseChunkResult(raw) {
|
|
489
|
+
const r = requireObject(raw, "EnterpriseChunkResult");
|
|
490
|
+
const songsRaw = r.songs;
|
|
491
|
+
if (!Array.isArray(songsRaw)) {
|
|
492
|
+
throw new TypeError("EnterpriseChunkResult: songs not array");
|
|
493
|
+
}
|
|
494
|
+
const offset = asString(r.offset);
|
|
495
|
+
if (offset === void 0) throw new TypeError("EnterpriseChunkResult: offset not string");
|
|
496
|
+
return {
|
|
497
|
+
songs: songsRaw.map(parseEnterpriseMatch),
|
|
498
|
+
offset,
|
|
499
|
+
extras: pickExtras(r, ENTERPRISE_CHUNK_KEYS),
|
|
500
|
+
rawResponse: r
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
var STREAM_KEYS = ["radio_id", "url", "stream_running", "longpoll_category"];
|
|
504
|
+
function parseStream(raw) {
|
|
505
|
+
const r = requireObject(raw, "Stream");
|
|
506
|
+
const radioId = asNumber(r.radio_id);
|
|
507
|
+
const url = asString(r.url);
|
|
508
|
+
const streamRunning = asBoolean(r.stream_running);
|
|
509
|
+
if (radioId === void 0) throw new TypeError("Stream: radio_id not number");
|
|
510
|
+
if (url === void 0) throw new TypeError("Stream: url not string");
|
|
511
|
+
if (streamRunning === void 0) throw new TypeError("Stream: stream_running not boolean");
|
|
512
|
+
return {
|
|
513
|
+
radioId,
|
|
514
|
+
url,
|
|
515
|
+
streamRunning,
|
|
516
|
+
longpollCategory: asString(r.longpoll_category),
|
|
517
|
+
extras: pickExtras(r, STREAM_KEYS),
|
|
518
|
+
rawResponse: r
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
var STREAM_CALLBACK_RESULT_ENTRY_KEYS = [
|
|
522
|
+
"artist",
|
|
523
|
+
"title",
|
|
524
|
+
"score",
|
|
525
|
+
"album",
|
|
526
|
+
"release_date",
|
|
527
|
+
"label",
|
|
528
|
+
"song_link",
|
|
529
|
+
"apple_music",
|
|
530
|
+
"spotify",
|
|
531
|
+
"deezer",
|
|
532
|
+
"napster",
|
|
533
|
+
"musicbrainz"
|
|
534
|
+
];
|
|
535
|
+
function parseStreamCallbackResultEntry(raw) {
|
|
536
|
+
const r = requireObject(raw, "StreamCallbackResultEntry");
|
|
537
|
+
const artist = asString(r.artist);
|
|
538
|
+
const title = asString(r.title);
|
|
539
|
+
const score = asNumber(r.score);
|
|
540
|
+
if (artist === void 0) throw new TypeError("StreamCallbackResultEntry: artist not string");
|
|
541
|
+
if (title === void 0) throw new TypeError("StreamCallbackResultEntry: title not string");
|
|
542
|
+
if (score === void 0) throw new TypeError("StreamCallbackResultEntry: score not number");
|
|
543
|
+
const musicbrainz = Array.isArray(r.musicbrainz) ? r.musicbrainz.filter((x) => asObject(x) !== void 0) : void 0;
|
|
544
|
+
return {
|
|
545
|
+
artist,
|
|
546
|
+
title,
|
|
547
|
+
score,
|
|
548
|
+
album: asString(r.album),
|
|
549
|
+
releaseDate: asString(r.release_date),
|
|
550
|
+
label: asString(r.label),
|
|
551
|
+
songLink: asString(r.song_link),
|
|
552
|
+
appleMusic: asObject(r.apple_music),
|
|
553
|
+
spotify: asObject(r.spotify),
|
|
554
|
+
deezer: asObject(r.deezer),
|
|
555
|
+
napster: asObject(r.napster),
|
|
556
|
+
musicbrainz,
|
|
557
|
+
extras: pickExtras(r, STREAM_CALLBACK_RESULT_ENTRY_KEYS),
|
|
558
|
+
rawResponse: r
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
var STREAM_CALLBACK_RESULT_KEYS = [
|
|
562
|
+
"radio_id",
|
|
563
|
+
"timestamp",
|
|
564
|
+
"play_length",
|
|
565
|
+
"results"
|
|
566
|
+
];
|
|
567
|
+
function parseStreamCallbackResult(raw) {
|
|
568
|
+
const r = requireObject(raw, "StreamCallbackResult");
|
|
569
|
+
const radioId = asNumber(r.radio_id);
|
|
570
|
+
if (radioId === void 0) throw new TypeError("StreamCallbackResult: radio_id not number");
|
|
571
|
+
const resultsRaw = r.results;
|
|
572
|
+
if (!Array.isArray(resultsRaw)) throw new TypeError("StreamCallbackResult: results not array");
|
|
573
|
+
return {
|
|
574
|
+
radioId,
|
|
575
|
+
timestamp: asString(r.timestamp),
|
|
576
|
+
playLength: asNumber(r.play_length),
|
|
577
|
+
results: resultsRaw.map(parseStreamCallbackResultEntry),
|
|
578
|
+
extras: pickExtras(r, STREAM_CALLBACK_RESULT_KEYS),
|
|
579
|
+
rawResponse: r
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
var STREAM_CALLBACK_NOTIFICATION_KEYS = [
|
|
583
|
+
"radio_id",
|
|
584
|
+
"stream_running",
|
|
585
|
+
"notification_code",
|
|
586
|
+
"notification_message"
|
|
587
|
+
];
|
|
588
|
+
function parseStreamCallbackNotification(raw) {
|
|
589
|
+
const r = requireObject(raw, "StreamCallbackNotification");
|
|
590
|
+
const radioId = asNumber(r.radio_id);
|
|
591
|
+
if (radioId === void 0)
|
|
592
|
+
throw new TypeError("StreamCallbackNotification: radio_id not number");
|
|
593
|
+
const code = asNumber(r.notification_code);
|
|
594
|
+
if (code === void 0)
|
|
595
|
+
throw new TypeError("StreamCallbackNotification: notification_code not number");
|
|
596
|
+
const message = asString(r.notification_message);
|
|
597
|
+
if (message === void 0)
|
|
598
|
+
throw new TypeError("StreamCallbackNotification: notification_message not string");
|
|
599
|
+
return {
|
|
600
|
+
radioId,
|
|
601
|
+
streamRunning: asBoolean(r.stream_running),
|
|
602
|
+
notificationCode: code,
|
|
603
|
+
notificationMessage: message,
|
|
604
|
+
extras: pickExtras(r, STREAM_CALLBACK_NOTIFICATION_KEYS),
|
|
605
|
+
rawResponse: r
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function parseStreamCallback(raw) {
|
|
609
|
+
const r = requireObject(raw, "StreamCallbackPayload");
|
|
610
|
+
if ("notification" in r) {
|
|
611
|
+
const notification = parseStreamCallbackNotification(r.notification);
|
|
612
|
+
const time = asNumber(r.time) ?? null;
|
|
613
|
+
return {
|
|
614
|
+
result: null,
|
|
615
|
+
notification,
|
|
616
|
+
time,
|
|
617
|
+
rawPayload: r,
|
|
618
|
+
get isResult() {
|
|
619
|
+
return false;
|
|
620
|
+
},
|
|
621
|
+
get isNotification() {
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
const inner = requireObject(r.result, "StreamCallbackPayload.result");
|
|
627
|
+
const result = parseStreamCallbackResult(inner);
|
|
628
|
+
return {
|
|
629
|
+
result,
|
|
630
|
+
notification: null,
|
|
631
|
+
time: null,
|
|
632
|
+
rawPayload: r,
|
|
633
|
+
get isResult() {
|
|
634
|
+
return true;
|
|
635
|
+
},
|
|
636
|
+
get isNotification() {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
var LYRICS_KEYS = [
|
|
642
|
+
"artist",
|
|
643
|
+
"title",
|
|
644
|
+
"lyrics",
|
|
645
|
+
"song_id",
|
|
646
|
+
"full_title",
|
|
647
|
+
"artist_id",
|
|
648
|
+
"song_link",
|
|
649
|
+
"media"
|
|
650
|
+
];
|
|
651
|
+
function parseLyricsResult(raw) {
|
|
652
|
+
const r = requireObject(raw, "LyricsResult");
|
|
653
|
+
const artist = asString(r.artist);
|
|
654
|
+
const title = asString(r.title);
|
|
655
|
+
if (artist === void 0) throw new TypeError("LyricsResult: artist not string");
|
|
656
|
+
if (title === void 0) throw new TypeError("LyricsResult: title not string");
|
|
657
|
+
return {
|
|
658
|
+
artist,
|
|
659
|
+
title,
|
|
660
|
+
lyrics: asString(r.lyrics),
|
|
661
|
+
songId: asNumber(r.song_id),
|
|
662
|
+
fullTitle: asString(r.full_title),
|
|
663
|
+
artistId: asNumber(r.artist_id),
|
|
664
|
+
songLink: asString(r.song_link),
|
|
665
|
+
media: asString(r.media),
|
|
666
|
+
extras: pickExtras(r, LYRICS_KEYS),
|
|
667
|
+
rawResponse: r
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/retry.ts
|
|
672
|
+
var DEFAULT_BACKOFF_FACTOR_MS = 500;
|
|
673
|
+
var DEFAULT_BACKOFF_MAX_MS = 3e4;
|
|
674
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
675
|
+
var HTTP_REQUEST_TIMEOUT = 408;
|
|
676
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
677
|
+
var HTTP_SERVER_ERROR_FLOOR = 500;
|
|
678
|
+
function defaultPolicy(retryClass) {
|
|
679
|
+
return {
|
|
680
|
+
retryClass,
|
|
681
|
+
maxAttempts: DEFAULT_MAX_ATTEMPTS,
|
|
682
|
+
backoffFactorMs: DEFAULT_BACKOFF_FACTOR_MS,
|
|
683
|
+
backoffMaxMs: DEFAULT_BACKOFF_MAX_MS
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function backoffDelayMs(attempt, policy) {
|
|
687
|
+
const base = Math.min(policy.backoffFactorMs * 2 ** attempt, policy.backoffMaxMs);
|
|
688
|
+
return base * (0.5 + Math.random());
|
|
689
|
+
}
|
|
690
|
+
function isAbortError(err) {
|
|
691
|
+
if (err === null || typeof err !== "object") return false;
|
|
692
|
+
const e = err;
|
|
693
|
+
return e.name === "AbortError";
|
|
694
|
+
}
|
|
695
|
+
function isPreUploadConnectionError(err) {
|
|
696
|
+
if (isAbortError(err)) return false;
|
|
697
|
+
return err instanceof TypeError;
|
|
698
|
+
}
|
|
699
|
+
function shouldRetryResponse(resp, retryClass) {
|
|
700
|
+
const s = resp.httpStatus;
|
|
701
|
+
switch (retryClass) {
|
|
702
|
+
case "read":
|
|
703
|
+
return s === HTTP_REQUEST_TIMEOUT || s === HTTP_TOO_MANY_REQUESTS || s >= HTTP_SERVER_ERROR_FLOOR;
|
|
704
|
+
case "recognition":
|
|
705
|
+
return s >= HTTP_SERVER_ERROR_FLOOR;
|
|
706
|
+
case "mutating":
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function shouldRetryError(err, retryClass) {
|
|
711
|
+
switch (retryClass) {
|
|
712
|
+
case "read":
|
|
713
|
+
return isPreUploadConnectionError(err) || isAbortError(err);
|
|
714
|
+
case "recognition":
|
|
715
|
+
return isPreUploadConnectionError(err);
|
|
716
|
+
case "mutating":
|
|
717
|
+
return isPreUploadConnectionError(err);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function sleep(ms) {
|
|
721
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
722
|
+
}
|
|
723
|
+
async function retry(fn, policy) {
|
|
724
|
+
let lastError;
|
|
725
|
+
let lastResp;
|
|
726
|
+
let haveResp = false;
|
|
727
|
+
for (let attempt = 0; attempt < policy.maxAttempts; attempt++) {
|
|
728
|
+
try {
|
|
729
|
+
const resp = await fn();
|
|
730
|
+
if (!shouldRetryResponse(resp, policy.retryClass)) return resp;
|
|
731
|
+
lastResp = resp;
|
|
732
|
+
haveResp = true;
|
|
733
|
+
lastError = void 0;
|
|
734
|
+
} catch (err) {
|
|
735
|
+
if (!shouldRetryError(err, policy.retryClass)) throw err;
|
|
736
|
+
lastError = err;
|
|
737
|
+
haveResp = false;
|
|
738
|
+
if (attempt + 1 >= policy.maxAttempts) throw err;
|
|
739
|
+
}
|
|
740
|
+
if (attempt + 1 >= policy.maxAttempts) break;
|
|
741
|
+
await sleep(backoffDelayMs(attempt, policy));
|
|
742
|
+
}
|
|
743
|
+
if (haveResp && lastResp !== void 0) return lastResp;
|
|
744
|
+
throw lastError;
|
|
745
|
+
}
|
|
746
|
+
function looksLikeUrl(s) {
|
|
747
|
+
return s.startsWith("http://") || s.startsWith("https://");
|
|
748
|
+
}
|
|
749
|
+
function isNodeRuntime() {
|
|
750
|
+
return typeof process !== "undefined" && process.versions !== void 0 && typeof process.versions.node === "string";
|
|
751
|
+
}
|
|
752
|
+
function prepareSource(source) {
|
|
753
|
+
if (typeof source === "string") {
|
|
754
|
+
if (looksLikeUrl(source)) {
|
|
755
|
+
const url = source;
|
|
756
|
+
return () => Promise.resolve({ fields: { url }, isMultipart: false });
|
|
757
|
+
}
|
|
758
|
+
if (!isNodeRuntime()) {
|
|
759
|
+
throw new TypeError(
|
|
760
|
+
`Source string ${JSON.stringify(source)} is not an HTTP URL (must start with http:// or https://). Filesystem paths are only supported on Node. In a browser, pass a Blob/File or Uint8Array instead.`
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
let exists = false;
|
|
764
|
+
try {
|
|
765
|
+
exists = fs.statSync(source).isFile();
|
|
766
|
+
} catch {
|
|
767
|
+
exists = false;
|
|
768
|
+
}
|
|
769
|
+
if (!exists) {
|
|
770
|
+
throw new TypeError(
|
|
771
|
+
`${JSON.stringify(source)} is not an HTTP URL (must start with http:// or https://) and is not an existing file path. Pass a URL, a path, a Blob, or bytes.`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
const filePath = source;
|
|
775
|
+
const filename = path__namespace.basename(filePath);
|
|
776
|
+
return async () => {
|
|
777
|
+
const buf = await fs.promises.readFile(filePath);
|
|
778
|
+
const blob = new Blob([new Uint8Array(buf)], {
|
|
779
|
+
type: "application/octet-stream"
|
|
780
|
+
});
|
|
781
|
+
const file = new File([blob], filename, { type: "application/octet-stream" });
|
|
782
|
+
return { fields: { file }, isMultipart: true };
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
if (source instanceof URL) {
|
|
786
|
+
const url = source.toString();
|
|
787
|
+
return () => Promise.resolve({ fields: { url }, isMultipart: false });
|
|
788
|
+
}
|
|
789
|
+
if (typeof Blob !== "undefined" && source instanceof Blob) {
|
|
790
|
+
const blob = source;
|
|
791
|
+
const filename = "name" in blob && typeof blob.name === "string" ? blob.name : "upload.bin";
|
|
792
|
+
return () => {
|
|
793
|
+
const file = new File([blob], filename, { type: blob.type || "application/octet-stream" });
|
|
794
|
+
return Promise.resolve({ fields: { file }, isMultipart: true });
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
if (source instanceof Uint8Array) {
|
|
798
|
+
const buf = new Uint8Array(source);
|
|
799
|
+
return () => {
|
|
800
|
+
const blob = new Blob([buf], { type: "application/octet-stream" });
|
|
801
|
+
const file = new File([blob], "upload.bin", { type: "application/octet-stream" });
|
|
802
|
+
return Promise.resolve({ fields: { file }, isMultipart: true });
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
throw new TypeError(
|
|
806
|
+
`Unsupported source type ${typeof source}; pass a URL string, URL, Blob/File, Uint8Array, or Buffer.`
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
var DuplicateReturnParameterError = class extends AudDInvalidRequestError {
|
|
810
|
+
name = "DuplicateReturnParameterError";
|
|
811
|
+
constructor() {
|
|
812
|
+
super({
|
|
813
|
+
errorCode: 0,
|
|
814
|
+
message: "URL already contains a `return` query parameter; pass returnMetadata: undefined or remove the parameter from the URL \u2014 refusing to silently overwrite.",
|
|
815
|
+
httpStatus: 0,
|
|
816
|
+
requestId: null,
|
|
817
|
+
requestedParams: {},
|
|
818
|
+
requestMethod: null,
|
|
819
|
+
brandedMessage: null,
|
|
820
|
+
rawResponse: null
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
function deriveLongpollCategory(apiToken, radioId) {
|
|
825
|
+
const inner = crypto.createHash("md5").update(apiToken, "utf8").digest("hex");
|
|
826
|
+
const full = crypto.createHash("md5").update(inner + String(radioId), "utf8").digest("hex");
|
|
827
|
+
return full.slice(0, 9);
|
|
828
|
+
}
|
|
829
|
+
function addReturnToUrl(url, returnMetadata) {
|
|
830
|
+
if (returnMetadata === void 0) return url;
|
|
831
|
+
const value = Array.isArray(returnMetadata) ? returnMetadata.join(",") : returnMetadata;
|
|
832
|
+
const u = new URL(url);
|
|
833
|
+
if (u.searchParams.has("return")) {
|
|
834
|
+
throw new DuplicateReturnParameterError();
|
|
835
|
+
}
|
|
836
|
+
u.searchParams.set("return", value);
|
|
837
|
+
return u.toString();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/streams.ts
|
|
841
|
+
var API_BASE = "https://api.audd.io";
|
|
842
|
+
var NO_CALLBACK_ERROR_CODE = 19;
|
|
843
|
+
var PREFLIGHT_NO_CALLBACK_HINT = "Longpoll won't deliver events because no callback URL is configured for this account. Set one first via streams.setCallbackUrl(...) \u2014 `https://audd.tech/empty/` is fine if you only want longpolling and don't need a real receiver. To skip this check, pass skipCallbackCheck: true.";
|
|
844
|
+
function decodeSuccess(body, httpStatus, requestId) {
|
|
845
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
846
|
+
throw new AudDSerializationError("Unparseable response");
|
|
847
|
+
}
|
|
848
|
+
const obj = body;
|
|
849
|
+
if (obj.status === "error") {
|
|
850
|
+
raiseFromErrorResponse(obj, {
|
|
851
|
+
httpStatus,
|
|
852
|
+
requestId
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
if (obj.status === "success") {
|
|
856
|
+
return obj.result;
|
|
857
|
+
}
|
|
858
|
+
throw new AudDServerError({
|
|
859
|
+
errorCode: 0,
|
|
860
|
+
message: `Unexpected response status: ${JSON.stringify(obj.status)}`,
|
|
861
|
+
httpStatus,
|
|
862
|
+
requestId,
|
|
863
|
+
rawResponse: obj
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
async function runRetried(fn, policy) {
|
|
867
|
+
try {
|
|
868
|
+
return await retry(fn, policy);
|
|
869
|
+
} catch (err) {
|
|
870
|
+
if (err instanceof TypeError) {
|
|
871
|
+
throw new AudDConnectionError(`Network error: ${err.message}`, err);
|
|
872
|
+
}
|
|
873
|
+
if (err !== null && typeof err === "object" && err.name === "AbortError") {
|
|
874
|
+
throw new AudDConnectionError("Request was aborted (timeout)", err);
|
|
875
|
+
}
|
|
876
|
+
throw err;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
var Streams = class {
|
|
880
|
+
constructor(http, readPolicy, mutatingPolicy, apiToken) {
|
|
881
|
+
this.http = http;
|
|
882
|
+
this.readPolicy = readPolicy;
|
|
883
|
+
this.mutatingPolicy = mutatingPolicy;
|
|
884
|
+
this.apiToken = apiToken;
|
|
885
|
+
}
|
|
886
|
+
http;
|
|
887
|
+
readPolicy;
|
|
888
|
+
mutatingPolicy;
|
|
889
|
+
apiToken;
|
|
890
|
+
async post(path2, fields, policy) {
|
|
891
|
+
const resp = await runRetried(
|
|
892
|
+
() => this.http.postForm(`${API_BASE}/${path2}/`, fields),
|
|
893
|
+
policy
|
|
894
|
+
);
|
|
895
|
+
return decodeSuccess(resp.jsonBody, resp.httpStatus, resp.requestId);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Set the callback URL where AudD will POST recognition events.
|
|
899
|
+
*
|
|
900
|
+
* @param url The URL to register.
|
|
901
|
+
* @param opts.returnMetadata Optional metadata block list — appended as
|
|
902
|
+
* `?return=...` to the URL. Raises `DuplicateReturnParameterError` if the
|
|
903
|
+
* URL already contains a `?return=` parameter.
|
|
904
|
+
*/
|
|
905
|
+
async setCallbackUrl(url, opts = {}) {
|
|
906
|
+
const finalUrl = addReturnToUrl(url, opts.returnMetadata);
|
|
907
|
+
await this.post("setCallbackUrl", { url: finalUrl }, this.mutatingPolicy);
|
|
908
|
+
}
|
|
909
|
+
/** Get the currently registered callback URL. */
|
|
910
|
+
async getCallbackUrl() {
|
|
911
|
+
const result = await this.post("getCallbackUrl", {}, this.readPolicy);
|
|
912
|
+
return String(result);
|
|
913
|
+
}
|
|
914
|
+
/** Register a new stream for real-time recognition. */
|
|
915
|
+
async add(opts) {
|
|
916
|
+
const fields = {
|
|
917
|
+
url: opts.url,
|
|
918
|
+
radio_id: String(opts.radioId)
|
|
919
|
+
};
|
|
920
|
+
if (opts.callbacks !== void 0) fields["callbacks"] = opts.callbacks;
|
|
921
|
+
await this.post("addStream", fields, this.mutatingPolicy);
|
|
922
|
+
}
|
|
923
|
+
/** Update the upstream URL of an existing stream. */
|
|
924
|
+
async setUrl(radioId, url) {
|
|
925
|
+
await this.post(
|
|
926
|
+
"setStreamUrl",
|
|
927
|
+
{ radio_id: String(radioId), url },
|
|
928
|
+
this.mutatingPolicy
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
/** Delete an existing stream. */
|
|
932
|
+
async delete(radioId) {
|
|
933
|
+
await this.post("deleteStream", { radio_id: String(radioId) }, this.mutatingPolicy);
|
|
934
|
+
}
|
|
935
|
+
/** List all streams on this account. */
|
|
936
|
+
async list() {
|
|
937
|
+
const result = await this.post("getStreams", {}, this.readPolicy);
|
|
938
|
+
if (!Array.isArray(result)) return [];
|
|
939
|
+
return result.map(parseStream);
|
|
940
|
+
}
|
|
941
|
+
/** Compute the 9-char longpoll category locally — pure, no network. */
|
|
942
|
+
deriveLongpollCategory(radioId) {
|
|
943
|
+
return deriveLongpollCategory(this.apiToken, radioId);
|
|
944
|
+
}
|
|
945
|
+
/** Parse a callback POST body into a typed `StreamCallbackPayload`. */
|
|
946
|
+
parseCallback(body) {
|
|
947
|
+
return parseStreamCallback(body);
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Longpoll the AudD subscription endpoint, yielding parsed JSON dicts
|
|
951
|
+
* (recognition events, notifications, or `{ timeout: ... }` markers).
|
|
952
|
+
*
|
|
953
|
+
* By default, performs a one-time `getCallbackUrl` preflight. If the
|
|
954
|
+
* server returns code 19 (no callback URL configured), throws
|
|
955
|
+
* `AudDInvalidRequestError` with an actionable hint. Pass
|
|
956
|
+
* `skipCallbackCheck: true` to bypass.
|
|
957
|
+
*/
|
|
958
|
+
longpoll(category, opts = {}) {
|
|
959
|
+
const skipCheck = opts.skipCallbackCheck ?? false;
|
|
960
|
+
const timeoutSec = opts.timeout ?? 50;
|
|
961
|
+
const initialSince = opts.sinceTime;
|
|
962
|
+
const getCallbackUrl = () => this.getCallbackUrl();
|
|
963
|
+
const httpGet = (params) => this.http.get(`${API_BASE}/longpoll/`, params);
|
|
964
|
+
const readPolicy = this.readPolicy;
|
|
965
|
+
return {
|
|
966
|
+
[Symbol.asyncIterator]() {
|
|
967
|
+
let preflighted = skipCheck;
|
|
968
|
+
let curSince = initialSince;
|
|
969
|
+
return {
|
|
970
|
+
async next() {
|
|
971
|
+
if (!preflighted) {
|
|
972
|
+
try {
|
|
973
|
+
await getCallbackUrl();
|
|
974
|
+
} catch (err) {
|
|
975
|
+
if (err instanceof AudDAPIError && err.errorCode === NO_CALLBACK_ERROR_CODE) {
|
|
976
|
+
throw new AudDInvalidRequestError({
|
|
977
|
+
errorCode: 0,
|
|
978
|
+
message: PREFLIGHT_NO_CALLBACK_HINT,
|
|
979
|
+
httpStatus: err.httpStatus,
|
|
980
|
+
requestId: err.requestId
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
throw err;
|
|
984
|
+
}
|
|
985
|
+
preflighted = true;
|
|
986
|
+
}
|
|
987
|
+
const params = {
|
|
988
|
+
category,
|
|
989
|
+
timeout: String(timeoutSec)
|
|
990
|
+
};
|
|
991
|
+
if (curSince !== void 0) params["since_time"] = String(curSince);
|
|
992
|
+
const resp = await runRetried(() => httpGet(params), readPolicy);
|
|
993
|
+
const body = resp.jsonBody;
|
|
994
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
995
|
+
throw new AudDSerializationError(
|
|
996
|
+
"Unparseable longpoll response",
|
|
997
|
+
resp.rawText
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
const dict = body;
|
|
1001
|
+
const ts = dict["timestamp"];
|
|
1002
|
+
if (typeof ts === "number") curSince = ts;
|
|
1003
|
+
return { value: dict, done: false };
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
// src/customCatalog.ts
|
|
1012
|
+
var UPLOAD_URL = "https://api.audd.io/upload/";
|
|
1013
|
+
function decodeSuccess2(body, httpStatus, requestId) {
|
|
1014
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
1015
|
+
throw new AudDSerializationError("Unparseable response");
|
|
1016
|
+
}
|
|
1017
|
+
const obj = body;
|
|
1018
|
+
if (obj.status === "error") {
|
|
1019
|
+
raiseFromErrorResponse(obj, {
|
|
1020
|
+
httpStatus,
|
|
1021
|
+
requestId,
|
|
1022
|
+
customCatalogContext: true
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
if (obj.status !== "success") {
|
|
1026
|
+
throw new AudDServerError({
|
|
1027
|
+
errorCode: 0,
|
|
1028
|
+
message: `Unexpected status: ${JSON.stringify(obj.status)}`,
|
|
1029
|
+
httpStatus,
|
|
1030
|
+
requestId,
|
|
1031
|
+
rawResponse: obj
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
async function runRetried2(fn, policy) {
|
|
1036
|
+
try {
|
|
1037
|
+
return await retry(fn, policy);
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
if (err instanceof TypeError) {
|
|
1040
|
+
throw new AudDConnectionError(`Network error: ${err.message}`, err);
|
|
1041
|
+
}
|
|
1042
|
+
if (err !== null && typeof err === "object" && err.name === "AbortError") {
|
|
1043
|
+
throw new AudDConnectionError("Request was aborted (timeout)", err);
|
|
1044
|
+
}
|
|
1045
|
+
throw err;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
var CustomCatalog = class {
|
|
1049
|
+
constructor(http, mutatingPolicy) {
|
|
1050
|
+
this.http = http;
|
|
1051
|
+
this.mutatingPolicy = mutatingPolicy;
|
|
1052
|
+
}
|
|
1053
|
+
http;
|
|
1054
|
+
mutatingPolicy;
|
|
1055
|
+
/**
|
|
1056
|
+
* **This is NOT how you submit audio for music recognition.** For
|
|
1057
|
+
* recognition, use `audd.recognize()` (or `audd.recognizeEnterprise()` for
|
|
1058
|
+
* files longer than 25 seconds). This method adds a song to your
|
|
1059
|
+
* **private fingerprint catalog** so AudD's recognition can later identify
|
|
1060
|
+
* *your own* tracks for *your account only*. Requires special access —
|
|
1061
|
+
* contact api@audd.io if you need it enabled.
|
|
1062
|
+
*
|
|
1063
|
+
* Calling this again with the same `audioId` re-fingerprints that slot.
|
|
1064
|
+
* There is no public list/delete endpoint; track `audioId` ↔ song
|
|
1065
|
+
* mappings on your side.
|
|
1066
|
+
*/
|
|
1067
|
+
async add(opts) {
|
|
1068
|
+
const reopen = prepareSource(opts.source);
|
|
1069
|
+
const audioId = String(opts.audioId);
|
|
1070
|
+
const resp = await runRetried2(async () => {
|
|
1071
|
+
const prepared = await reopen();
|
|
1072
|
+
const fields = { ...prepared.fields, audio_id: audioId };
|
|
1073
|
+
return this.http.postForm(UPLOAD_URL, fields);
|
|
1074
|
+
}, this.mutatingPolicy);
|
|
1075
|
+
decodeSuccess2(resp.jsonBody, resp.httpStatus, resp.requestId);
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
// src/advanced.ts
|
|
1080
|
+
var API_BASE2 = "https://api.audd.io";
|
|
1081
|
+
async function runRetried3(fn, policy) {
|
|
1082
|
+
try {
|
|
1083
|
+
return await retry(fn, policy);
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
if (err instanceof TypeError) {
|
|
1086
|
+
throw new AudDConnectionError(`Network error: ${err.message}`, err);
|
|
1087
|
+
}
|
|
1088
|
+
if (err !== null && typeof err === "object" && err.name === "AbortError") {
|
|
1089
|
+
throw new AudDConnectionError("Request was aborted (timeout)", err);
|
|
1090
|
+
}
|
|
1091
|
+
throw err;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
var Advanced = class {
|
|
1095
|
+
/**
|
|
1096
|
+
* @param recognitionPolicy The findLyrics endpoint is metered and shouldn't
|
|
1097
|
+
* double-bill on post-upload read timeout: Advanced uses RECOGNITION retry
|
|
1098
|
+
* policy, not READ.
|
|
1099
|
+
*/
|
|
1100
|
+
constructor(http, recognitionPolicy) {
|
|
1101
|
+
this.http = http;
|
|
1102
|
+
this.recognitionPolicy = recognitionPolicy;
|
|
1103
|
+
}
|
|
1104
|
+
http;
|
|
1105
|
+
recognitionPolicy;
|
|
1106
|
+
/** Find lyrics by free-text query. Returns a list of matches (possibly empty). */
|
|
1107
|
+
async findLyrics(query) {
|
|
1108
|
+
const body = await this.rawRequest("findLyrics", { q: query });
|
|
1109
|
+
if (body["status"] === "error") {
|
|
1110
|
+
raiseFromErrorResponse(body, {
|
|
1111
|
+
httpStatus: 200,
|
|
1112
|
+
requestId: null
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
const result = body["result"];
|
|
1116
|
+
if (!Array.isArray(result)) return [];
|
|
1117
|
+
return result.map(parseLyricsResult);
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Hit any AudD endpoint by method name and return the raw JSON object.
|
|
1121
|
+
* Useful for endpoints not yet wrapped by typed methods on this SDK.
|
|
1122
|
+
*/
|
|
1123
|
+
async rawRequest(method, params = {}) {
|
|
1124
|
+
const fields = {};
|
|
1125
|
+
for (const [k, v] of Object.entries(params)) {
|
|
1126
|
+
if (v === void 0) continue;
|
|
1127
|
+
fields[k] = String(v);
|
|
1128
|
+
}
|
|
1129
|
+
const resp = await runRetried3(
|
|
1130
|
+
() => this.http.postForm(`${API_BASE2}/${method}/`, fields),
|
|
1131
|
+
this.recognitionPolicy
|
|
1132
|
+
);
|
|
1133
|
+
const body = resp.jsonBody;
|
|
1134
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
1135
|
+
throw new AudDSerializationError("Unparseable response", resp.rawText);
|
|
1136
|
+
}
|
|
1137
|
+
return body;
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
// src/client.ts
|
|
1142
|
+
var API_BASE3 = "https://api.audd.io";
|
|
1143
|
+
var ENTERPRISE_BASE = "https://enterprise.audd.io";
|
|
1144
|
+
var DEPRECATED_PARAMS_CODE = 51;
|
|
1145
|
+
var HTTP_CLIENT_ERROR_FLOOR = 400;
|
|
1146
|
+
var TOKEN_ENV_VAR = "AUDD_API_TOKEN";
|
|
1147
|
+
function safeEmit(hook, event) {
|
|
1148
|
+
if (hook === void 0) return;
|
|
1149
|
+
try {
|
|
1150
|
+
hook(event);
|
|
1151
|
+
} catch {
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
function resolveToken(apiToken) {
|
|
1155
|
+
if (apiToken !== void 0 && apiToken !== "") return apiToken;
|
|
1156
|
+
const env = (typeof process !== "undefined" ? process.env[TOKEN_ENV_VAR] : void 0) ?? "";
|
|
1157
|
+
if (env !== "") return env;
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
`AudD apiToken not supplied and ${TOKEN_ENV_VAR} env var is unset. Get a token at https://dashboard.audd.io and pass it as new AudD("<token>") (or new AudD({ apiToken: "<token>" })), or set AUDD_API_TOKEN.`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
function formatReturn(value) {
|
|
1163
|
+
if (value === void 0) return void 0;
|
|
1164
|
+
return Array.isArray(value) ? value.join(",") : value;
|
|
1165
|
+
}
|
|
1166
|
+
function buildEnterpriseFields(opts) {
|
|
1167
|
+
const fields = {};
|
|
1168
|
+
const ret = formatReturn(opts.return);
|
|
1169
|
+
if (ret !== void 0) fields["return"] = ret;
|
|
1170
|
+
if (opts.skip !== void 0) fields["skip"] = String(opts.skip);
|
|
1171
|
+
if (opts.every !== void 0) fields["every"] = String(opts.every);
|
|
1172
|
+
if (opts.limit !== void 0) fields["limit"] = String(opts.limit);
|
|
1173
|
+
if (opts.skipFirstSeconds !== void 0)
|
|
1174
|
+
fields["skip_first_seconds"] = String(opts.skipFirstSeconds);
|
|
1175
|
+
if (opts.useTimecode !== void 0) fields["use_timecode"] = opts.useTimecode ? "true" : "false";
|
|
1176
|
+
if (opts.accurateOffsets !== void 0)
|
|
1177
|
+
fields["accurate_offsets"] = opts.accurateOffsets ? "true" : "false";
|
|
1178
|
+
return fields;
|
|
1179
|
+
}
|
|
1180
|
+
function isDeprecationPassThrough(body) {
|
|
1181
|
+
return body.error?.error_code === DEPRECATED_PARAMS_CODE && body.result != null;
|
|
1182
|
+
}
|
|
1183
|
+
function maybeWarnAndStrip(body) {
|
|
1184
|
+
if (!isDeprecationPassThrough(body)) return;
|
|
1185
|
+
const msg = body.error?.error_message ?? "Deprecated parameter used";
|
|
1186
|
+
console.warn(`audd: deprecated parameter \u2014 ${msg}`);
|
|
1187
|
+
delete body.error;
|
|
1188
|
+
body.status = "success";
|
|
1189
|
+
}
|
|
1190
|
+
function decodeOrRaise(resp) {
|
|
1191
|
+
const body = resp.jsonBody;
|
|
1192
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
1193
|
+
if (resp.httpStatus >= HTTP_CLIENT_ERROR_FLOOR) {
|
|
1194
|
+
throw new AudDServerError({
|
|
1195
|
+
errorCode: 0,
|
|
1196
|
+
message: `HTTP ${String(resp.httpStatus)} with non-JSON response body`,
|
|
1197
|
+
httpStatus: resp.httpStatus,
|
|
1198
|
+
requestId: resp.requestId,
|
|
1199
|
+
rawResponse: resp.rawText
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
throw new AudDSerializationError("Unparseable response", resp.rawText);
|
|
1203
|
+
}
|
|
1204
|
+
const obj = body;
|
|
1205
|
+
maybeWarnAndStrip(obj);
|
|
1206
|
+
if (obj.status === "error") {
|
|
1207
|
+
raiseFromErrorResponse(obj, {
|
|
1208
|
+
httpStatus: resp.httpStatus,
|
|
1209
|
+
requestId: resp.requestId
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
if (obj.status === "success") {
|
|
1213
|
+
return obj;
|
|
1214
|
+
}
|
|
1215
|
+
throw new AudDServerError({
|
|
1216
|
+
errorCode: 0,
|
|
1217
|
+
message: `Unexpected response status: ${JSON.stringify(obj.status)}`,
|
|
1218
|
+
httpStatus: resp.httpStatus,
|
|
1219
|
+
requestId: resp.requestId,
|
|
1220
|
+
rawResponse: obj
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
function decodeRecognize(resp) {
|
|
1224
|
+
const body = decodeOrRaise(resp);
|
|
1225
|
+
const result = body["result"];
|
|
1226
|
+
if (result == null) return null;
|
|
1227
|
+
return parseRecognitionResult(result);
|
|
1228
|
+
}
|
|
1229
|
+
function decodeEnterprise(resp) {
|
|
1230
|
+
const body = decodeOrRaise(resp);
|
|
1231
|
+
const chunks = body["result"];
|
|
1232
|
+
if (!Array.isArray(chunks)) return [];
|
|
1233
|
+
const out = [];
|
|
1234
|
+
for (const c of chunks) {
|
|
1235
|
+
const chunk = parseEnterpriseChunkResult(c);
|
|
1236
|
+
out.push(...chunk.songs);
|
|
1237
|
+
}
|
|
1238
|
+
return out;
|
|
1239
|
+
}
|
|
1240
|
+
async function runRetried4(fn, policy) {
|
|
1241
|
+
try {
|
|
1242
|
+
return await retry(fn, policy);
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
if (err instanceof TypeError) {
|
|
1245
|
+
throw new AudDConnectionError(`Network error: ${err.message}`, err);
|
|
1246
|
+
}
|
|
1247
|
+
if (err !== null && typeof err === "object" && err.name === "AbortError") {
|
|
1248
|
+
throw new AudDConnectionError("Request was aborted (timeout)", err);
|
|
1249
|
+
}
|
|
1250
|
+
throw err;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
var AudD = class {
|
|
1254
|
+
_http;
|
|
1255
|
+
_enterpriseHttp;
|
|
1256
|
+
_maxRetries;
|
|
1257
|
+
_backoffFactorMs;
|
|
1258
|
+
_apiToken;
|
|
1259
|
+
_onEvent;
|
|
1260
|
+
_streams;
|
|
1261
|
+
_customCatalog;
|
|
1262
|
+
_advanced;
|
|
1263
|
+
constructor(arg, extra) {
|
|
1264
|
+
const opts = typeof arg === "string" ? { apiToken: arg, ...extra ?? {} } : arg ?? {};
|
|
1265
|
+
const token = resolveToken(opts.apiToken);
|
|
1266
|
+
this._apiToken = token;
|
|
1267
|
+
this._onEvent = opts.onEvent;
|
|
1268
|
+
this._maxRetries = opts.maxRetries ?? 3;
|
|
1269
|
+
this._backoffFactorMs = opts.backoffFactorMs ?? 500;
|
|
1270
|
+
this._http = new HttpClient({
|
|
1271
|
+
apiToken: token,
|
|
1272
|
+
...opts.fetch !== void 0 ? { fetch: opts.fetch } : {}
|
|
1273
|
+
});
|
|
1274
|
+
this._enterpriseHttp = new HttpClient({
|
|
1275
|
+
apiToken: token,
|
|
1276
|
+
...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
|
|
1277
|
+
defaultTimeoutMs: ENTERPRISE_TIMEOUT_MS
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
/** Current api_token. Returns the in-effect token after any rotations. */
|
|
1281
|
+
get apiToken() {
|
|
1282
|
+
return this._apiToken;
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Rotate the api_token used for subsequent requests. In-flight requests
|
|
1286
|
+
* continue with the old token (no abort).
|
|
1287
|
+
*/
|
|
1288
|
+
setApiToken(newToken) {
|
|
1289
|
+
if (typeof newToken !== "string" || newToken === "") {
|
|
1290
|
+
throw new Error("setApiToken requires a non-empty string");
|
|
1291
|
+
}
|
|
1292
|
+
this._apiToken = newToken;
|
|
1293
|
+
this._http.setApiToken(newToken);
|
|
1294
|
+
this._enterpriseHttp.setApiToken(newToken);
|
|
1295
|
+
}
|
|
1296
|
+
policyFor(retryClass) {
|
|
1297
|
+
return {
|
|
1298
|
+
...defaultPolicy(retryClass),
|
|
1299
|
+
maxAttempts: this._maxRetries,
|
|
1300
|
+
backoffFactorMs: this._backoffFactorMs
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
/** Sub-namespace for stream management + longpoll. Lazy-instantiated. */
|
|
1304
|
+
get streams() {
|
|
1305
|
+
if (this._streams === void 0) {
|
|
1306
|
+
this._streams = new Streams(
|
|
1307
|
+
this._http,
|
|
1308
|
+
this.policyFor("read"),
|
|
1309
|
+
this.policyFor("mutating"),
|
|
1310
|
+
this._apiToken
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
return this._streams;
|
|
1314
|
+
}
|
|
1315
|
+
/** Sub-namespace for the private fingerprint catalog. NOT for recognition. */
|
|
1316
|
+
get customCatalog() {
|
|
1317
|
+
if (this._customCatalog === void 0) {
|
|
1318
|
+
this._customCatalog = new CustomCatalog(this._http, this.policyFor("mutating"));
|
|
1319
|
+
}
|
|
1320
|
+
return this._customCatalog;
|
|
1321
|
+
}
|
|
1322
|
+
/** Lyrics search + raw-request escape hatch. */
|
|
1323
|
+
get advanced() {
|
|
1324
|
+
if (this._advanced === void 0) {
|
|
1325
|
+
this._advanced = new Advanced(this._http, this.policyFor("recognition"));
|
|
1326
|
+
}
|
|
1327
|
+
return this._advanced;
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Recognize a short audio clip (≤25s) from a URL, file path, Blob, or bytes.
|
|
1331
|
+
*
|
|
1332
|
+
* Returns `null` when the server returns `status=success` with `result=null`
|
|
1333
|
+
* (no match) — distinct from a thrown error.
|
|
1334
|
+
*/
|
|
1335
|
+
async recognize(source, opts = {}) {
|
|
1336
|
+
const reopen = prepareSource(source);
|
|
1337
|
+
const ret = formatReturn(opts.return);
|
|
1338
|
+
const market = opts.market;
|
|
1339
|
+
const policy = this.policyFor("recognition");
|
|
1340
|
+
const url = `${API_BASE3}/`;
|
|
1341
|
+
const startedAt = Date.now();
|
|
1342
|
+
safeEmit(this._onEvent, {
|
|
1343
|
+
kind: "request",
|
|
1344
|
+
method: "recognize",
|
|
1345
|
+
url,
|
|
1346
|
+
requestId: null,
|
|
1347
|
+
httpStatus: null,
|
|
1348
|
+
elapsedMs: null,
|
|
1349
|
+
errorCode: null,
|
|
1350
|
+
extras: {}
|
|
1351
|
+
});
|
|
1352
|
+
let resp;
|
|
1353
|
+
try {
|
|
1354
|
+
resp = await runRetried4(async () => {
|
|
1355
|
+
const prepared = await reopen();
|
|
1356
|
+
const fields = { ...prepared.fields };
|
|
1357
|
+
if (ret !== void 0) fields["return"] = ret;
|
|
1358
|
+
if (market !== void 0) fields["market"] = market;
|
|
1359
|
+
return this._http.postForm(url, fields, {
|
|
1360
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
1361
|
+
...opts.signal !== void 0 ? { signal: opts.signal } : {}
|
|
1362
|
+
});
|
|
1363
|
+
}, policy);
|
|
1364
|
+
} catch (err) {
|
|
1365
|
+
safeEmit(this._onEvent, {
|
|
1366
|
+
kind: "exception",
|
|
1367
|
+
method: "recognize",
|
|
1368
|
+
url,
|
|
1369
|
+
requestId: null,
|
|
1370
|
+
httpStatus: null,
|
|
1371
|
+
elapsedMs: Date.now() - startedAt,
|
|
1372
|
+
errorCode: null,
|
|
1373
|
+
extras: { name: err instanceof Error ? err.name : String(err) }
|
|
1374
|
+
});
|
|
1375
|
+
throw err;
|
|
1376
|
+
}
|
|
1377
|
+
safeEmit(this._onEvent, {
|
|
1378
|
+
kind: "response",
|
|
1379
|
+
method: "recognize",
|
|
1380
|
+
url,
|
|
1381
|
+
requestId: resp.requestId,
|
|
1382
|
+
httpStatus: resp.httpStatus,
|
|
1383
|
+
elapsedMs: Date.now() - startedAt,
|
|
1384
|
+
errorCode: null,
|
|
1385
|
+
extras: {}
|
|
1386
|
+
});
|
|
1387
|
+
return decodeRecognize(resp);
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Enterprise recognition (long files). Returns a flat array of matches
|
|
1391
|
+
* across all chunks in the upstream response.
|
|
1392
|
+
*
|
|
1393
|
+
* Recommended: pass `limit: N` to cap result count when developing.
|
|
1394
|
+
*/
|
|
1395
|
+
async recognizeEnterprise(source, opts = {}) {
|
|
1396
|
+
const reopen = prepareSource(source);
|
|
1397
|
+
const extra = buildEnterpriseFields(opts);
|
|
1398
|
+
const policy = this.policyFor("recognition");
|
|
1399
|
+
const resp = await runRetried4(async () => {
|
|
1400
|
+
const prepared = await reopen();
|
|
1401
|
+
const fields = { ...prepared.fields, ...extra };
|
|
1402
|
+
return this._enterpriseHttp.postForm(`${ENTERPRISE_BASE}/`, fields, {
|
|
1403
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
1404
|
+
...opts.signal !== void 0 ? { signal: opts.signal } : {}
|
|
1405
|
+
});
|
|
1406
|
+
}, policy);
|
|
1407
|
+
return decodeEnterprise(resp);
|
|
1408
|
+
}
|
|
1409
|
+
/** Release any underlying resources. (Currently a no-op for `fetch`-based transport.) */
|
|
1410
|
+
close() {
|
|
1411
|
+
}
|
|
1412
|
+
async [Symbol.asyncDispose]() {
|
|
1413
|
+
this.close();
|
|
1414
|
+
await Promise.resolve();
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
exports.AudD = AudD;
|
|
1419
|
+
exports.AudDAPIError = AudDAPIError;
|
|
1420
|
+
exports.AudDAuthenticationError = AudDAuthenticationError;
|
|
1421
|
+
exports.AudDBlockedError = AudDBlockedError;
|
|
1422
|
+
exports.AudDConnectionError = AudDConnectionError;
|
|
1423
|
+
exports.AudDCustomCatalogAccessError = AudDCustomCatalogAccessError;
|
|
1424
|
+
exports.AudDError = AudDError;
|
|
1425
|
+
exports.AudDInvalidAudioError = AudDInvalidAudioError;
|
|
1426
|
+
exports.AudDInvalidRequestError = AudDInvalidRequestError;
|
|
1427
|
+
exports.AudDNeedsUpdateError = AudDNeedsUpdateError;
|
|
1428
|
+
exports.AudDNotReleasedError = AudDNotReleasedError;
|
|
1429
|
+
exports.AudDQuotaError = AudDQuotaError;
|
|
1430
|
+
exports.AudDRateLimitError = AudDRateLimitError;
|
|
1431
|
+
exports.AudDSerializationError = AudDSerializationError;
|
|
1432
|
+
exports.AudDServerError = AudDServerError;
|
|
1433
|
+
exports.AudDStreamLimitError = AudDStreamLimitError;
|
|
1434
|
+
exports.AudDSubscriptionError = AudDSubscriptionError;
|
|
1435
|
+
exports.DuplicateReturnParameterError = DuplicateReturnParameterError;
|
|
1436
|
+
exports.VERSION = VERSION;
|
|
1437
|
+
exports.addReturnToUrl = addReturnToUrl;
|
|
1438
|
+
exports.deriveLongpollCategory = deriveLongpollCategory;
|
|
1439
|
+
exports.errorClassForCode = errorClassForCode;
|
|
1440
|
+
exports.parseCallback = parseStreamCallback;
|
|
1441
|
+
//# sourceMappingURL=index.cjs.map
|
|
1442
|
+
//# sourceMappingURL=index.cjs.map
|