songbrain 0.1.2 → 0.3.0
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/README.md +51 -5
- package/dist/index.cjs +110 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +152 -15
- package/dist/index.d.ts +152 -15
- package/dist/index.js +110 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,12 +8,15 @@ var SongbrainError = class extends Error {
|
|
|
8
8
|
code;
|
|
9
9
|
/** The parsed response body, when there was one. */
|
|
10
10
|
body;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/** The `Songbrain-Request-Id` of the failed request (`req_…`), or null. */
|
|
12
|
+
requestId;
|
|
13
|
+
constructor(status, code, message, body, requestId = null) {
|
|
14
|
+
super(requestId ? `${message} (request_id: ${requestId})` : message);
|
|
13
15
|
this.name = new.target.name;
|
|
14
16
|
this.status = status;
|
|
15
17
|
this.code = code;
|
|
16
18
|
this.body = body;
|
|
19
|
+
this.requestId = requestId;
|
|
17
20
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
18
21
|
}
|
|
19
22
|
};
|
|
@@ -26,8 +29,8 @@ var NotFound = class extends SongbrainError {
|
|
|
26
29
|
var RateLimited = class extends SongbrainError {
|
|
27
30
|
/** Seconds to wait, from the Retry-After header (null if absent). */
|
|
28
31
|
retryAfter;
|
|
29
|
-
constructor(status, code, message, body, retryAfter = null) {
|
|
30
|
-
super(status, code, message, body);
|
|
32
|
+
constructor(status, code, message, body, retryAfter = null, requestId = null) {
|
|
33
|
+
super(status, code, message, body, requestId);
|
|
31
34
|
this.retryAfter = retryAfter;
|
|
32
35
|
}
|
|
33
36
|
};
|
|
@@ -51,10 +54,12 @@ var WaitTimeout = class extends SongbrainError {
|
|
|
51
54
|
};
|
|
52
55
|
|
|
53
56
|
// src/version.ts
|
|
54
|
-
var VERSION = "0.
|
|
57
|
+
var VERSION = "0.3.0";
|
|
55
58
|
|
|
56
59
|
// src/client.ts
|
|
57
60
|
var DEFAULT_BASE_URL = "https://api.songbrain.ai/v1";
|
|
61
|
+
var REQUEST_ID_HEADER = "Songbrain-Request-Id";
|
|
62
|
+
var IDEMPOTENCY_HEADER = "Idempotency-Key";
|
|
58
63
|
var MAX_RETRY_WAIT_SEC = 60;
|
|
59
64
|
var MIME = {
|
|
60
65
|
mp3: "audio/mpeg",
|
|
@@ -99,12 +104,47 @@ function retryAfterSeconds(value) {
|
|
|
99
104
|
return Number.isNaN(when) ? null : Math.max(0, (when - Date.now()) / 1e3);
|
|
100
105
|
}
|
|
101
106
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
107
|
+
function intHeader(value) {
|
|
108
|
+
if (value === null || value === "") return null;
|
|
109
|
+
const n = Number(value);
|
|
110
|
+
return Number.isFinite(n) ? Math.trunc(n) : null;
|
|
111
|
+
}
|
|
112
|
+
function rateLimitFrom(headers) {
|
|
113
|
+
const limit = intHeader(headers.get("X-RateLimit-Limit"));
|
|
114
|
+
if (limit === null) return null;
|
|
115
|
+
return {
|
|
116
|
+
limit,
|
|
117
|
+
remaining: intHeader(headers.get("X-RateLimit-Remaining")),
|
|
118
|
+
reset: intHeader(headers.get("X-RateLimit-Reset"))
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function newIdempotencyKey() {
|
|
122
|
+
const c = globalThis.crypto;
|
|
123
|
+
if (typeof c?.randomUUID === "function") return c.randomUUID();
|
|
124
|
+
try {
|
|
125
|
+
const { randomUUID } = await import('crypto');
|
|
126
|
+
return randomUUID();
|
|
127
|
+
} catch {
|
|
128
|
+
const hex = Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16));
|
|
129
|
+
hex[12] = "4";
|
|
130
|
+
hex[16] = (parseInt(hex[16], 16) & 3 | 8).toString(16);
|
|
131
|
+
const h = hex.join("");
|
|
132
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
102
135
|
var Songbrain = class {
|
|
103
136
|
apiKey;
|
|
104
137
|
baseUrl;
|
|
105
138
|
timeoutMs;
|
|
106
139
|
maxRetries;
|
|
107
140
|
fetchImpl;
|
|
141
|
+
/**
|
|
142
|
+
* `{ limit, remaining, reset }` from the `X-RateLimit-*` headers of the last response
|
|
143
|
+
* that had them (null before that). `reset` is seconds until the window has room.
|
|
144
|
+
*/
|
|
145
|
+
lastRateLimit = null;
|
|
146
|
+
/** The `Songbrain-Request-Id` of the last response (null before the first one). */
|
|
147
|
+
lastRequestId = null;
|
|
108
148
|
/** @internal Overridable in tests. */
|
|
109
149
|
_sleep = sleep;
|
|
110
150
|
constructor(options = {}) {
|
|
@@ -117,21 +157,30 @@ var Songbrain = class {
|
|
|
117
157
|
this.fetchImpl = f;
|
|
118
158
|
}
|
|
119
159
|
async analyze(options) {
|
|
120
|
-
const { file, audioUrl } = options;
|
|
121
|
-
if (file
|
|
160
|
+
const { file, audioUrl, test } = options;
|
|
161
|
+
if (file !== void 0 && audioUrl !== void 0) {
|
|
122
162
|
throw new TypeError("Pass exactly one of `file` or `audioUrl`.");
|
|
123
163
|
}
|
|
164
|
+
if (file === void 0 && audioUrl === void 0 && !test) {
|
|
165
|
+
throw new TypeError("Pass exactly one of `file` or `audioUrl` (or `test: true`).");
|
|
166
|
+
}
|
|
124
167
|
const fields = {};
|
|
125
168
|
if (options.title !== void 0) fields.title = options.title;
|
|
126
169
|
if (options.artist !== void 0) fields.artist = options.artist;
|
|
127
170
|
if (options.webhookUrl !== void 0) fields.webhook_url = options.webhookUrl;
|
|
128
171
|
if (options.externalRef !== void 0) fields.external_ref = options.externalRef;
|
|
172
|
+
if (options.lyrics !== void 0) fields.lyrics = options.lyrics;
|
|
173
|
+
const headers = { [IDEMPOTENCY_HEADER]: options.idempotencyKey || await newIdempotencyKey() };
|
|
129
174
|
let created;
|
|
130
|
-
if (
|
|
131
|
-
|
|
175
|
+
if (file === void 0) {
|
|
176
|
+
const json = audioUrl !== void 0 ? { audio_url: audioUrl, ...fields } : { ...fields };
|
|
177
|
+
if (test) json.test = true;
|
|
178
|
+
created = await this.request("POST", "/songs", { json, headers });
|
|
132
179
|
} else {
|
|
180
|
+
if (test) fields.test = "true";
|
|
133
181
|
const { blob, name } = await toUpload(file, options.filename);
|
|
134
182
|
created = await this.request("POST", "/songs", {
|
|
183
|
+
headers,
|
|
135
184
|
form: () => {
|
|
136
185
|
const form = new FormData();
|
|
137
186
|
form.append("file", blob, name);
|
|
@@ -175,9 +224,28 @@ var Songbrain = class {
|
|
|
175
224
|
shotPlan(songId) {
|
|
176
225
|
return this.request("GET", `/songs/${encodeURIComponent(songId)}/shot-plan`);
|
|
177
226
|
}
|
|
178
|
-
/**
|
|
227
|
+
/** One page of your songs, newest first: `{ object: "list", data, has_more, next_cursor }`. */
|
|
179
228
|
listSongs(options = {}) {
|
|
180
|
-
return this.request("GET", "/songs", {
|
|
229
|
+
return this.request("GET", "/songs", {
|
|
230
|
+
query: { limit: String(options.limit ?? 20), starting_after: options.startingAfter }
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* All your songs, newest first, fetching pages as needed.
|
|
235
|
+
*
|
|
236
|
+
* ```ts
|
|
237
|
+
* for await (const item of sb.iterSongs()) console.log(item.id, item.status);
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
async *iterSongs(options = {}) {
|
|
241
|
+
let cursor = options.startingAfter;
|
|
242
|
+
for (; ; ) {
|
|
243
|
+
const page = await this.listSongs({ limit: options.pageSize ?? 100, startingAfter: cursor });
|
|
244
|
+
const data = page.data ?? [];
|
|
245
|
+
yield* data;
|
|
246
|
+
cursor = page.next_cursor ?? data[data.length - 1]?.id;
|
|
247
|
+
if (!page.has_more || !cursor) return;
|
|
248
|
+
}
|
|
181
249
|
}
|
|
182
250
|
/** Delete a song's audio and analysis now. 409 `still_processing` while it runs. */
|
|
183
251
|
deleteSong(songId) {
|
|
@@ -192,6 +260,20 @@ var Songbrain = class {
|
|
|
192
260
|
pricing() {
|
|
193
261
|
return this.request("GET", "/pricing");
|
|
194
262
|
}
|
|
263
|
+
// ── webhooks ─────────────────────────────────────────────────────────────
|
|
264
|
+
/**
|
|
265
|
+
* Send a signed `ping` event to `url` right now:
|
|
266
|
+
* `{ delivered, status_code, latency_ms, event_id }`. Checks your receiver before real songs.
|
|
267
|
+
*/
|
|
268
|
+
testWebhook(url) {
|
|
269
|
+
return this.request("POST", "/webhooks/test", { json: { url } });
|
|
270
|
+
}
|
|
271
|
+
/** The last webhook delivery attempts for your account, newest first. */
|
|
272
|
+
webhookDeliveries(options = {}) {
|
|
273
|
+
return this.request("GET", "/webhooks/deliveries", {
|
|
274
|
+
query: { limit: String(options.limit ?? 20) }
|
|
275
|
+
});
|
|
276
|
+
}
|
|
195
277
|
// ── examples (no key) ────────────────────────────────────────────────────
|
|
196
278
|
/** Example analyses of Songbrain's own songs. No key needed. */
|
|
197
279
|
examples() {
|
|
@@ -212,13 +294,15 @@ var Songbrain = class {
|
|
|
212
294
|
async request(method, path, opts = {}) {
|
|
213
295
|
const url = new URL(this.baseUrl + path);
|
|
214
296
|
for (const [k, v] of Object.entries(opts.query ?? {})) if (v !== void 0) url.searchParams.set(k, v);
|
|
215
|
-
const
|
|
297
|
+
const hasKey = Boolean(opts.headers?.[IDEMPOTENCY_HEADER]);
|
|
298
|
+
const idempotent = method !== "POST" || hasKey;
|
|
216
299
|
for (let attempt = 0; ; attempt++) {
|
|
217
300
|
const headers = {
|
|
218
301
|
Accept: "application/json",
|
|
219
302
|
"User-Agent": `songbrain-js/${VERSION}`
|
|
220
303
|
};
|
|
221
304
|
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
305
|
+
Object.assign(headers, opts.headers);
|
|
222
306
|
let body;
|
|
223
307
|
if (opts.json) {
|
|
224
308
|
headers["Content-Type"] = "application/json";
|
|
@@ -237,6 +321,9 @@ var Songbrain = class {
|
|
|
237
321
|
const message = err instanceof Error ? err.message : String(err);
|
|
238
322
|
throw new SongbrainError(0, "connection_error", message);
|
|
239
323
|
}
|
|
324
|
+
this.lastRequestId = res.headers.get(REQUEST_ID_HEADER) || null;
|
|
325
|
+
const rateLimit = rateLimitFrom(res.headers);
|
|
326
|
+
if (rateLimit) this.lastRateLimit = rateLimit;
|
|
240
327
|
if (res.ok) {
|
|
241
328
|
const text = await res.text();
|
|
242
329
|
if (!text) return {};
|
|
@@ -247,10 +334,12 @@ var Songbrain = class {
|
|
|
247
334
|
}
|
|
248
335
|
}
|
|
249
336
|
const error = await errorFrom(res);
|
|
250
|
-
const
|
|
337
|
+
const inProgress = hasKey && res.status === 409 && error.code === "idempotency_in_progress";
|
|
338
|
+
const retryable = res.status === 429 || inProgress || res.status >= 500 && (idempotent || res.status === 502 || res.status === 503);
|
|
251
339
|
if (retryable && attempt < this.maxRetries) {
|
|
252
340
|
const after = error instanceof RateLimited ? error.retryAfter : null;
|
|
253
|
-
|
|
341
|
+
let delayMs = after !== null ? after * 1e3 : this.backoffMs(attempt);
|
|
342
|
+
if (inProgress) delayMs = Math.max(1e3, delayMs);
|
|
254
343
|
if (delayMs <= MAX_RETRY_WAIT_SEC * 1e3) {
|
|
255
344
|
await this._sleep(delayMs);
|
|
256
345
|
continue;
|
|
@@ -269,6 +358,7 @@ async function errorFrom(res) {
|
|
|
269
358
|
let body;
|
|
270
359
|
let code = `http_${res.status}`;
|
|
271
360
|
let message = text.trim().slice(0, 500) || res.statusText || `HTTP ${res.status}`;
|
|
361
|
+
let requestId = res.headers.get(REQUEST_ID_HEADER) || null;
|
|
272
362
|
try {
|
|
273
363
|
body = JSON.parse(text);
|
|
274
364
|
} catch {
|
|
@@ -280,6 +370,7 @@ async function errorFrom(res) {
|
|
|
280
370
|
const e = err;
|
|
281
371
|
if (e.code) code = String(e.code);
|
|
282
372
|
if (e.message) message = String(e.message);
|
|
373
|
+
if (e.request_id) requestId = String(e.request_id);
|
|
283
374
|
} else if ("detail" in body) {
|
|
284
375
|
code = "validation_error";
|
|
285
376
|
message = JSON.stringify(body.detail);
|
|
@@ -287,15 +378,15 @@ async function errorFrom(res) {
|
|
|
287
378
|
}
|
|
288
379
|
switch (res.status) {
|
|
289
380
|
case 401:
|
|
290
|
-
return new AuthenticationError(401, code, message, body);
|
|
381
|
+
return new AuthenticationError(401, code, message, body, requestId);
|
|
291
382
|
case 402:
|
|
292
|
-
return new InsufficientCredits(402, code, message, body);
|
|
383
|
+
return new InsufficientCredits(402, code, message, body, requestId);
|
|
293
384
|
case 404:
|
|
294
|
-
return new NotFound(404, code, message, body);
|
|
385
|
+
return new NotFound(404, code, message, body, requestId);
|
|
295
386
|
case 429:
|
|
296
|
-
return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get("Retry-After")));
|
|
387
|
+
return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get("Retry-After")), requestId);
|
|
297
388
|
default:
|
|
298
|
-
return new SongbrainError(res.status, code, message, body);
|
|
389
|
+
return new SongbrainError(res.status, code, message, body, requestId);
|
|
299
390
|
}
|
|
300
391
|
}
|
|
301
392
|
async function toUpload(file, filename) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/webhooks.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Every API error has the shape `{ error: { code, message } }`. The client\n * turns it into a `SongbrainError` (or a subclass) with `status`, `code` and\n * `message`.\n */\nexport class SongbrainError extends Error {\n /** HTTP status, or 0 when the error did not come from an HTTP response. */\n readonly status: number;\n /** Machine-readable code, e.g. \"invalid_url\" or \"rate_limited\". */\n readonly code: string;\n /** The parsed response body, when there was one. */\n readonly body: unknown;\n\n constructor(status: number, code: string, message: string, body?: unknown) {\n super(message);\n this.name = new.target.name;\n this.status = status;\n this.code = code;\n this.body = body;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** 401: the API key is missing, invalid or revoked. */\nexport class AuthenticationError extends SongbrainError {}\n\n/** 402: the free songs for this month are used up and the balance is below one song. */\nexport class InsufficientCredits extends SongbrainError {}\n\n/** 404: unknown id, or the song belongs to another account. */\nexport class NotFound extends SongbrainError {}\n\n/** 429: too many requests, too many songs in flight, daily cap or busy pipeline. */\nexport class RateLimited extends SongbrainError {\n /** Seconds to wait, from the Retry-After header (null if absent). */\n readonly retryAfter: number | null;\n\n constructor(status: number, code: string, message: string, body?: unknown, retryAfter: number | null = null) {\n super(status, code, message, body);\n this.retryAfter = retryAfter;\n }\n}\n\n/** The song was accepted but its analysis failed. Paid credits are refunded automatically. */\nexport class AnalysisFailed extends SongbrainError {\n readonly songId: string;\n\n constructor(songId: string, code: string, message: string, body?: unknown) {\n super(0, code, message, body);\n this.songId = songId;\n }\n}\n\n/** `analyze({ wait: true })` gave up before the song was done. It keeps processing on the server. */\nexport class WaitTimeout extends SongbrainError {\n readonly songId: string;\n\n constructor(songId: string, timeoutMs: number) {\n super(\n 0,\n \"wait_timeout\",\n `Song ${songId} was not done after ${Math.round(timeoutMs / 1000)} s. It is still processing; fetch it later with getSong(\"${songId}\").`,\n );\n this.songId = songId;\n }\n}\n","export const VERSION = \"0.1.2\";\n","import {\n AnalysisFailed,\n AuthenticationError,\n InsufficientCredits,\n NotFound,\n RateLimited,\n SongbrainError,\n WaitTimeout,\n} from \"./errors.js\";\nimport type {\n Account,\n DeleteResult,\n ExampleList,\n Pricing,\n SectionName,\n Song,\n SongCreated,\n SongList,\n SongShotPlan,\n} from \"./types.js\";\nimport { VERSION } from \"./version.js\";\n\nexport const DEFAULT_BASE_URL = \"https://api.songbrain.ai/v1\";\n\n/** A Blob/File, a Buffer or bytes, or a local file path (Node). */\nexport type FileInput = Blob | Uint8Array | ArrayBuffer | string;\n\nexport interface SongbrainOptions {\n /** Your key (`sb_live_…`). Defaults to `process.env.SONGBRAIN_API_KEY`. The example endpoints work without a key. */\n apiKey?: string;\n /** Defaults to `process.env.SONGBRAIN_BASE_URL` or https://api.songbrain.ai/v1. */\n baseUrl?: string;\n /** Timeout for each HTTP request in ms (uploads included). Default 60 000. */\n timeoutMs?: number;\n /** How often to retry 429 and 5xx responses, with backoff. Default 3. */\n maxRetries?: number;\n /** A custom fetch implementation. Defaults to the global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface AnalyzeOptions {\n /** A Blob/File, a Buffer or bytes, or a local file path. Pass this or `audioUrl`. */\n file?: FileInput;\n /** A public http(s) URL of the audio file. Pass this or `file`. */\n audioUrl?: string;\n title?: string;\n artist?: string;\n /** Receives `song.done` / `song.failed`. */\n webhookUrl?: string;\n /** Your own id, echoed back in responses and webhooks. */\n externalRef?: string;\n /** File name for an upload. Its extension must match the audio format. Taken from the path or File name, or guessed from the bytes. */\n filename?: string;\n /** Wait until the song is done and return the full document (default true). */\n wait?: boolean;\n /** Time between status checks while waiting. Default 5 000. */\n pollIntervalMs?: number;\n /** Total time to wait before throwing `WaitTimeout`. Default 300 000. */\n timeoutMs?: number;\n}\n\nexport interface WaitOptions {\n pollIntervalMs?: number;\n timeoutMs?: number;\n}\n\nexport interface GetSongOptions {\n /** \"summary\" drops word timings and beat arrays (about 3x smaller). */\n view?: \"full\" | \"summary\";\n /** Only these sections, e.g. [\"song_dna\", \"shot_plan\"]. */\n include?: SectionName[] | string;\n}\n\ntype Json = Record<string, unknown>;\n\nconst MAX_RETRY_WAIT_SEC = 60;\nconst MIME: Record<string, string> = {\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n flac: \"audio/flac\",\n ogg: \"audio/ogg\",\n m4a: \"audio/mp4\",\n aac: \"audio/aac\",\n aiff: \"audio/aiff\",\n aif: \"audio/aiff\",\n};\n\nfunction env(name: string): string | undefined {\n const p = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n return p?.env?.[name] || undefined;\n}\n\n/** Guess the audio format from the first bytes (the API checks the extension against the contents). */\nexport function sniffExtension(head: Uint8Array): string | null {\n const ascii = (from: number, to: number) => String.fromCharCode(...head.subarray(from, to));\n if (ascii(0, 3) === \"ID3\") return \"mp3\";\n if (ascii(0, 4) === \"RIFF\" && ascii(8, 12) === \"WAVE\") return \"wav\";\n if (ascii(0, 4) === \"fLaC\") return \"flac\";\n if (ascii(0, 4) === \"OggS\") return \"ogg\";\n if (ascii(0, 4) === \"FORM\" && [\"AIFF\", \"AIFC\"].includes(ascii(8, 12))) return \"aiff\";\n if (ascii(4, 8) === \"ftyp\") return \"m4a\";\n const b0 = head[0];\n const b1 = head[1];\n if (b0 === 0xff && b1 !== undefined && (b1 & 0xe0) === 0xe0) return (b1 & 0x06) === 0 ? \"aac\" : \"mp3\";\n return null;\n}\n\nfunction extOf(name: string): string {\n const base = name.split(/[\\\\/]/).pop() ?? \"\";\n const i = base.lastIndexOf(\".\");\n return i > 0 ? base.slice(i + 1).toLowerCase() : \"\";\n}\n\nfunction baseName(name: string): string {\n return name.split(/[\\\\/]/).pop() ?? name;\n}\n\nfunction retryAfterSeconds(value: string | null): number | null {\n if (!value) return null;\n const n = Number(value);\n if (Number.isFinite(n)) return Math.max(0, n);\n const when = Date.parse(value);\n return Number.isNaN(when) ? null : Math.max(0, (when - Date.now()) / 1000);\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/**\n * Client for the Songbrain API. Every method resolves to the JSON body of\n * the response.\n *\n * ```ts\n * const sb = new Songbrain(); // reads SONGBRAIN_API_KEY\n * const song = await sb.analyze({ file: \"song.mp3\" });\n * for (const s of song.shot_plan?.clip?.scenes ?? []) console.log(s.start_sec, s.prompt);\n * ```\n */\nexport class Songbrain {\n readonly apiKey: string | undefined;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n readonly maxRetries: number;\n private readonly fetchImpl: typeof fetch;\n /** @internal Overridable in tests. */\n _sleep: (ms: number) => Promise<void> = sleep;\n\n constructor(options: SongbrainOptions = {}) {\n this.apiKey = options.apiKey !== undefined ? options.apiKey || undefined : env(\"SONGBRAIN_API_KEY\");\n this.baseUrl = (options.baseUrl ?? env(\"SONGBRAIN_BASE_URL\") ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeoutMs = options.timeoutMs ?? 60_000;\n this.maxRetries = Math.max(0, options.maxRetries ?? 3);\n const f = options.fetch ?? (globalThis.fetch as typeof fetch | undefined);\n if (!f) throw new Error(\"No global fetch found. Use Node 18+ or pass `fetch` in the options.\");\n this.fetchImpl = f;\n }\n\n // ── songs ────────────────────────────────────────────────────────────────\n\n /**\n * Analyse a song from a file or a public URL. Pass exactly one of `file` or `audioUrl`.\n * Typically 60-90 s. With `wait: true` (default) resolves to the full document;\n * with `wait: false` to the 202 body `{ id, status: \"processing\", eta_sec, billing }`.\n */\n analyze(options: AnalyzeOptions & { wait: false }): Promise<SongCreated>;\n analyze(options: AnalyzeOptions & { wait?: true }): Promise<Song>;\n analyze(options: AnalyzeOptions): Promise<Song | SongCreated>;\n async analyze(options: AnalyzeOptions): Promise<Song | SongCreated> {\n const { file, audioUrl } = options;\n if ((file === undefined) === (audioUrl === undefined)) {\n throw new TypeError(\"Pass exactly one of `file` or `audioUrl`.\");\n }\n const fields: Record<string, string> = {};\n if (options.title !== undefined) fields.title = options.title;\n if (options.artist !== undefined) fields.artist = options.artist;\n if (options.webhookUrl !== undefined) fields.webhook_url = options.webhookUrl;\n if (options.externalRef !== undefined) fields.external_ref = options.externalRef;\n\n let created: SongCreated;\n if (audioUrl !== undefined) {\n created = await this.request<SongCreated>(\"POST\", \"/songs\", { json: { audio_url: audioUrl, ...fields } });\n } else {\n const { blob, name } = await toUpload(file as FileInput, options.filename);\n created = await this.request<SongCreated>(\"POST\", \"/songs\", {\n form: () => {\n const form = new FormData();\n form.append(\"file\", blob, name);\n for (const [k, v] of Object.entries(fields)) form.append(k, v);\n return form;\n },\n });\n }\n if (options.wait === false) return created;\n return this.waitFor(created.id, {\n pollIntervalMs: options.pollIntervalMs,\n timeoutMs: options.timeoutMs,\n });\n }\n\n /** Poll GET /songs/{id} until the song is done. Throws `AnalysisFailed` or `WaitTimeout`. */\n async waitFor(songId: string, options: WaitOptions = {}): Promise<Song> {\n const timeoutMs = options.timeoutMs ?? 300_000;\n const interval = Math.max(500, options.pollIntervalMs ?? 5_000);\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const doc = await this.getSong(songId);\n if (doc.status === \"done\") return doc;\n if (doc.status === \"failed\") {\n throw new AnalysisFailed(\n songId,\n doc.error?.code ?? \"analysis_failed\",\n doc.error?.message ?? \"The analysis failed. Paid credits were refunded automatically.\",\n doc,\n );\n }\n const remaining = deadline - Date.now();\n if (remaining <= 0) throw new WaitTimeout(songId, timeoutMs);\n await this._sleep(Math.min(interval, remaining));\n }\n }\n\n /** Status while processing, then the full document. */\n getSong(songId: string, options: GetSongOptions = {}): Promise<Song> {\n return this.request<Song>(\"GET\", `/songs/${encodeURIComponent(songId)}`, { query: docQuery(options) });\n }\n\n /** Only the story and the shot plan: `{ id, status, song, story, shot_plan }`. */\n shotPlan(songId: string): Promise<SongShotPlan> {\n return this.request<SongShotPlan>(\"GET\", `/songs/${encodeURIComponent(songId)}/shot-plan`);\n }\n\n /** Your songs, newest first. `limit` is 1-100 (default 20). */\n listSongs(options: { limit?: number } = {}): Promise<SongList> {\n return this.request<SongList>(\"GET\", \"/songs\", { query: { limit: String(options.limit ?? 20) } });\n }\n\n /** Delete a song's audio and analysis now. 409 `still_processing` while it runs. */\n deleteSong(songId: string): Promise<DeleteResult> {\n return this.request<DeleteResult>(\"DELETE\", `/songs/${encodeURIComponent(songId)}`);\n }\n\n // ── account ──────────────────────────────────────────────────────────────\n\n /** Free songs left this month, credit balance and price per song. */\n account(): Promise<Account> {\n return this.request<Account>(\"GET\", \"/account\");\n }\n\n /** Prices and limits. No key needed. */\n pricing(): Promise<Pricing> {\n return this.request<Pricing>(\"GET\", \"/pricing\");\n }\n\n // ── examples (no key) ────────────────────────────────────────────────────\n\n /** Example analyses of Songbrain's own songs. No key needed. */\n examples(): Promise<ExampleList> {\n return this.request<ExampleList>(\"GET\", \"/examples\");\n }\n\n /** A full example document, in the same format as `getSong`. No key needed. */\n example(exampleId: string, options: GetSongOptions = {}): Promise<Song> {\n return this.request<Song>(\"GET\", `/examples/${encodeURIComponent(exampleId)}`, { query: docQuery(options) });\n }\n\n /** An example story and shot plan, in the same format as `shotPlan`. No key needed. */\n exampleShotPlan(exampleId: string): Promise<SongShotPlan> {\n return this.request<SongShotPlan>(\"GET\", `/examples/${encodeURIComponent(exampleId)}/shot-plan`);\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private backoffMs(attempt: number): number {\n return Math.min(500 * 2 ** attempt, 8_000) + Math.random() * 250;\n }\n\n private async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n opts: { query?: Record<string, string | undefined>; json?: Json; form?: () => FormData } = {},\n ): Promise<T> {\n const url = new URL(this.baseUrl + path);\n for (const [k, v] of Object.entries(opts.query ?? {})) if (v !== undefined) url.searchParams.set(k, v);\n const idempotent = method !== \"POST\";\n\n for (let attempt = 0; ; attempt++) {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"User-Agent\": `songbrain-js/${VERSION}`,\n };\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;\n let body: BodyInit | undefined;\n if (opts.json) {\n headers[\"Content-Type\"] = \"application/json\";\n body = JSON.stringify(opts.json);\n } else if (opts.form) {\n body = opts.form(); // fresh FormData per attempt; fetch sets the boundary\n }\n\n let res: Response;\n try {\n res = await this.fetchImpl(url, { method, headers, body, signal: AbortSignal.timeout(this.timeoutMs) });\n } catch (err) {\n // Network errors and timeouts: only resend if nothing can have been created.\n if (idempotent && attempt < this.maxRetries) {\n await this._sleep(this.backoffMs(attempt));\n continue;\n }\n const message = err instanceof Error ? err.message : String(err);\n throw new SongbrainError(0, \"connection_error\", message);\n }\n\n if (res.ok) {\n const text = await res.text();\n if (!text) return {} as T;\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new SongbrainError(res.status, \"invalid_response\", \"Response is not JSON.\");\n }\n }\n\n const error = await errorFrom(res);\n // 429 never started any work. For POST only retry gateway errors (502/503).\n const retryable =\n res.status === 429 || (res.status >= 500 && (idempotent || res.status === 502 || res.status === 503));\n if (retryable && attempt < this.maxRetries) {\n const after = error instanceof RateLimited ? error.retryAfter : null;\n const delayMs = after !== null ? after * 1000 : this.backoffMs(attempt);\n if (delayMs <= MAX_RETRY_WAIT_SEC * 1000) {\n await this._sleep(delayMs);\n continue;\n }\n }\n throw error;\n }\n }\n}\n\nfunction docQuery(options: GetSongOptions): Record<string, string | undefined> {\n const include = Array.isArray(options.include) ? options.include.join(\",\") : options.include;\n return { view: options.view, include };\n}\n\nasync function errorFrom(res: Response): Promise<SongbrainError> {\n const text = await res.text().catch(() => \"\");\n let body: unknown;\n let code = `http_${res.status}`;\n let message = text.trim().slice(0, 500) || res.statusText || `HTTP ${res.status}`;\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n if (body && typeof body === \"object\") {\n const err = (body as { error?: unknown }).error;\n if (err && typeof err === \"object\") {\n const e = err as { code?: unknown; message?: unknown };\n if (e.code) code = String(e.code);\n if (e.message) message = String(e.message);\n } else if (\"detail\" in (body as object)) {\n code = \"validation_error\";\n message = JSON.stringify((body as { detail: unknown }).detail);\n }\n }\n switch (res.status) {\n case 401:\n return new AuthenticationError(401, code, message, body);\n case 402:\n return new InsufficientCredits(402, code, message, body);\n case 404:\n return new NotFound(404, code, message, body);\n case 429:\n return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get(\"Retry-After\")));\n default:\n return new SongbrainError(res.status, code, message, body);\n }\n}\n\nasync function toUpload(file: FileInput, filename?: string): Promise<{ blob: Blob; name: string }> {\n let blob: Blob;\n let sourceName: string | undefined;\n if (typeof file === \"string\") {\n const { readFile } = await import(\"node:fs/promises\");\n const data = await readFile(file);\n blob = new Blob([data as BlobPart]);\n sourceName = baseName(file);\n } else if (file instanceof ArrayBuffer) {\n blob = new Blob([file]);\n } else if (file instanceof Uint8Array) {\n blob = new Blob([file as BlobPart]);\n } else if (typeof Blob !== \"undefined\" && file instanceof Blob) {\n blob = file;\n const n = (file as Blob & { name?: unknown }).name;\n if (typeof n === \"string\" && n) sourceName = baseName(n);\n } else {\n throw new TypeError(\"`file` must be a Blob, a Buffer/Uint8Array, an ArrayBuffer or a file path.\");\n }\n\n let name = filename ?? sourceName;\n if (!name || !extOf(name)) {\n const head = new Uint8Array(await blob.slice(0, 16).arrayBuffer());\n const ext = sniffExtension(head);\n if (!ext) {\n throw new TypeError('Could not tell the audio format. Pass `filename` with an extension, e.g. filename: \"song.mp3\".');\n }\n name = `${(name ?? \"song\").replace(/\\.$/, \"\")}.${ext}`;\n }\n const type = MIME[extOf(name)];\n if (type && !blob.type) blob = new Blob([blob], { type });\n return { blob, name };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport type { WebhookEvent } from \"./types.js\";\n\n/** The header Songbrain signs webhooks with. */\nexport const SIGNATURE_HEADER = \"Songbrain-Signature\";\n\ntype RawBody = string | Uint8Array | ArrayBuffer;\n\nfunction toBuffer(body: RawBody): Buffer {\n if (typeof body === \"string\") return Buffer.from(body, \"utf8\");\n if (body instanceof ArrayBuffer) return Buffer.from(new Uint8Array(body));\n return Buffer.from(body.buffer, body.byteOffset, body.byteLength);\n}\n\nfunction parseHeader(header: string): { t: number | null; v1: string[] } {\n let t: number | null = null;\n const v1: string[] = [];\n for (const part of header.split(\",\")) {\n const i = part.indexOf(\"=\");\n if (i < 0) continue;\n const key = part.slice(0, i).trim();\n const value = part.slice(i + 1).trim();\n if (key === \"t\") {\n if (!/^\\d+$/.test(value)) return { t: null, v1: [] };\n t = Number(value);\n } else if (key === \"v1\" && value) {\n v1.push(value.toLowerCase());\n }\n }\n return { t, v1 };\n}\n\nfunction mac(body: Buffer, t: number, secret: string): string {\n return createHmac(\"sha256\", secret).update(`${t}.`).update(body).digest(\"hex\");\n}\n\n/**\n * Check a `Songbrain-Signature: t=<unix>,v1=<hex>` header, where v1 is the\n * HMAC-SHA256 of `\"<t>.<raw body>\"` with your key's webhook secret.\n *\n * Pass the raw request body exactly as received, before any JSON parsing.\n * Returns false (never throws) for a missing, malformed, wrong or old signature.\n *\n * @param toleranceSec Maximum age in seconds (default 300). 0 disables the check.\n */\nexport function verifyWebhook(\n rawBody: RawBody,\n header: string | null | undefined,\n secret: string,\n toleranceSec = 300,\n): boolean {\n if (!header || !secret) return false;\n const { t, v1 } = parseHeader(header);\n if (t === null || v1.length === 0) return false;\n if (toleranceSec > 0 && Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;\n const expected = Buffer.from(mac(toBuffer(rawBody), t, secret), \"utf8\");\n return v1.some((sig) => {\n const given = Buffer.from(sig, \"utf8\");\n return given.length === expected.length && timingSafeEqual(given, expected);\n });\n}\n\n/**\n * Verify the signature and return the parsed event:\n * `{ type: \"song.done\" | \"song.failed\" | \"account.low_balance\", created, data }`.\n * Throws if the signature is not valid.\n */\nexport function constructWebhookEvent<T = Record<string, unknown>>(\n rawBody: RawBody,\n header: string | null | undefined,\n secret: string,\n toleranceSec = 300,\n): WebhookEvent<T> {\n if (!verifyWebhook(rawBody, header, secret, toleranceSec)) {\n throw new Error(\"Invalid Songbrain-Signature\");\n }\n return JSON.parse(toBuffer(rawBody).toString(\"utf8\")) as WebhookEvent<T>;\n}\n\n/** Build a signature header value. Useful to test your webhook handler. */\nexport function signWebhook(rawBody: RawBody, secret: string, timestampSec = Math.floor(Date.now() / 1000)): string {\n return `t=${timestampSec},v1=${mac(toBuffer(rawBody), timestampSec, secret)}`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/webhooks.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Every API error has the shape `{ error: { code, message, request_id } }`.\n * The client turns it into a `SongbrainError` (or a subclass) with `status`,\n * `code`, `message` and `requestId`. The message ends with\n * ` (request_id: req_…)` when there is one; quote it when you contact support.\n */\nexport class SongbrainError extends Error {\n /** HTTP status, or 0 when the error did not come from an HTTP response. */\n readonly status: number;\n /** Machine-readable code, e.g. \"invalid_url\" or \"rate_limited\". */\n readonly code: string;\n /** The parsed response body, when there was one. */\n readonly body: unknown;\n /** The `Songbrain-Request-Id` of the failed request (`req_…`), or null. */\n readonly requestId: string | null;\n\n constructor(status: number, code: string, message: string, body?: unknown, requestId: string | null = null) {\n super(requestId ? `${message} (request_id: ${requestId})` : message);\n this.name = new.target.name;\n this.status = status;\n this.code = code;\n this.body = body;\n this.requestId = requestId;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** 401: the API key is missing, invalid or revoked. */\nexport class AuthenticationError extends SongbrainError {}\n\n/** 402: the free songs for this month are used up and the balance is below one song. */\nexport class InsufficientCredits extends SongbrainError {}\n\n/** 404: unknown id, or the song belongs to another account. */\nexport class NotFound extends SongbrainError {}\n\n/** 429: too many requests, too many songs in flight, daily cap or busy pipeline. */\nexport class RateLimited extends SongbrainError {\n /** Seconds to wait, from the Retry-After header (null if absent). */\n readonly retryAfter: number | null;\n\n constructor(\n status: number,\n code: string,\n message: string,\n body?: unknown,\n retryAfter: number | null = null,\n requestId: string | null = null,\n ) {\n super(status, code, message, body, requestId);\n this.retryAfter = retryAfter;\n }\n}\n\n/** The song was accepted but its analysis failed. Paid credits are refunded automatically. */\nexport class AnalysisFailed extends SongbrainError {\n readonly songId: string;\n\n constructor(songId: string, code: string, message: string, body?: unknown) {\n super(0, code, message, body);\n this.songId = songId;\n }\n}\n\n/** `analyze({ wait: true })` gave up before the song was done. It keeps processing on the server. */\nexport class WaitTimeout extends SongbrainError {\n readonly songId: string;\n\n constructor(songId: string, timeoutMs: number) {\n super(\n 0,\n \"wait_timeout\",\n `Song ${songId} was not done after ${Math.round(timeoutMs / 1000)} s. It is still processing; fetch it later with getSong(\"${songId}\").`,\n );\n this.songId = songId;\n }\n}\n","export const VERSION = \"0.3.0\";\n","import {\n AnalysisFailed,\n AuthenticationError,\n InsufficientCredits,\n NotFound,\n RateLimited,\n SongbrainError,\n WaitTimeout,\n} from \"./errors.js\";\nimport type {\n Account,\n DeleteResult,\n ExampleList,\n Pricing,\n RateLimitInfo,\n SectionName,\n Song,\n SongCreated,\n SongList,\n SongListItem,\n SongShotPlan,\n WebhookDeliveryList,\n WebhookTestResult,\n} from \"./types.js\";\nimport { VERSION } from \"./version.js\";\n\nexport const DEFAULT_BASE_URL = \"https://api.songbrain.ai/v1\";\nconst REQUEST_ID_HEADER = \"Songbrain-Request-Id\";\nconst IDEMPOTENCY_HEADER = \"Idempotency-Key\";\n\n/** A Blob/File, a Buffer or bytes, or a local file path (Node). */\nexport type FileInput = Blob | Uint8Array | ArrayBuffer | string;\n\nexport interface SongbrainOptions {\n /** Your key (`sb_live_…`). Defaults to `process.env.SONGBRAIN_API_KEY`. The example endpoints work without a key. */\n apiKey?: string;\n /** Defaults to `process.env.SONGBRAIN_BASE_URL` or https://api.songbrain.ai/v1. */\n baseUrl?: string;\n /** Timeout for each HTTP request in ms (uploads included). Default 60 000. */\n timeoutMs?: number;\n /**\n * How often to retry network errors, 429 and 5xx responses, with backoff. Default 3.\n * Song creation is retried too, because every create sends an `Idempotency-Key`.\n */\n maxRetries?: number;\n /** A custom fetch implementation. Defaults to the global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface AnalyzeOptions {\n /** A Blob/File, a Buffer or bytes, or a local file path. Pass this or `audioUrl`. */\n file?: FileInput;\n /** A public http(s) URL of the audio file. Pass this or `file`. */\n audioUrl?: string;\n title?: string;\n artist?: string;\n /** Receives `song.done` / `song.failed`. */\n webhookUrl?: string;\n /** Your own id, echoed back in responses and webhooks. */\n externalRef?: string;\n /** File name for an upload. Its extension must match the audio format. Taken from the path or File name, or guessed from the bytes. */\n filename?: string;\n /**\n * The song's lyrics as plain text (up to 20,000 characters), e.g. from Suno. The\n * analysis then uses your exact words on the transcription's timing\n * (`lyrics.source` = \"provided_lyrics\"); words the singer can't be heard on are\n * left out, never guessed.\n */\n lyrics?: string;\n /**\n * Test mode: free, never charged, no audio needed. The song is done right away and\n * returns the Sugar Rush example analysis with `livemode: false`. `song.done` is\n * still sent to `webhookUrl`. Made for CI and integration tests.\n */\n test?: boolean;\n /**\n * Sent as `Idempotency-Key`. By default a random UUID per call, reused across the\n * client's own retries, so a retried create never makes a second song. Pass your\n * own (e.g. your job id) to make retries across processes safe too (24 h window).\n */\n idempotencyKey?: string;\n /** Wait until the song is done and return the full document (default true). */\n wait?: boolean;\n /** Time between status checks while waiting. Default 5 000. */\n pollIntervalMs?: number;\n /** Total time to wait before throwing `WaitTimeout`. Default 300 000. */\n timeoutMs?: number;\n}\n\nexport interface WaitOptions {\n pollIntervalMs?: number;\n timeoutMs?: number;\n}\n\nexport interface ListSongsOptions {\n /** Page size, 1-100. Default 20. */\n limit?: number;\n /** A song id, usually the previous page's `next_cursor`. Returns the songs after it. */\n startingAfter?: string;\n}\n\nexport interface IterSongsOptions {\n /** Songs per request, 1-100. Default 100. */\n pageSize?: number;\n /** Start after this song id. */\n startingAfter?: string;\n}\n\nexport interface GetSongOptions {\n /** \"summary\" drops word timings and beat arrays (about 3x smaller). */\n view?: \"full\" | \"summary\";\n /** Only these sections, e.g. [\"song_dna\", \"shot_plan\"]. */\n include?: SectionName[] | string;\n}\n\ntype Json = Record<string, unknown>;\n\nconst MAX_RETRY_WAIT_SEC = 60;\nconst MIME: Record<string, string> = {\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n flac: \"audio/flac\",\n ogg: \"audio/ogg\",\n m4a: \"audio/mp4\",\n aac: \"audio/aac\",\n aiff: \"audio/aiff\",\n aif: \"audio/aiff\",\n};\n\nfunction env(name: string): string | undefined {\n const p = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n return p?.env?.[name] || undefined;\n}\n\n/** Guess the audio format from the first bytes (the API checks the extension against the contents). */\nexport function sniffExtension(head: Uint8Array): string | null {\n const ascii = (from: number, to: number) => String.fromCharCode(...head.subarray(from, to));\n if (ascii(0, 3) === \"ID3\") return \"mp3\";\n if (ascii(0, 4) === \"RIFF\" && ascii(8, 12) === \"WAVE\") return \"wav\";\n if (ascii(0, 4) === \"fLaC\") return \"flac\";\n if (ascii(0, 4) === \"OggS\") return \"ogg\";\n if (ascii(0, 4) === \"FORM\" && [\"AIFF\", \"AIFC\"].includes(ascii(8, 12))) return \"aiff\";\n if (ascii(4, 8) === \"ftyp\") return \"m4a\";\n const b0 = head[0];\n const b1 = head[1];\n if (b0 === 0xff && b1 !== undefined && (b1 & 0xe0) === 0xe0) return (b1 & 0x06) === 0 ? \"aac\" : \"mp3\";\n return null;\n}\n\nfunction extOf(name: string): string {\n const base = name.split(/[\\\\/]/).pop() ?? \"\";\n const i = base.lastIndexOf(\".\");\n return i > 0 ? base.slice(i + 1).toLowerCase() : \"\";\n}\n\nfunction baseName(name: string): string {\n return name.split(/[\\\\/]/).pop() ?? name;\n}\n\nfunction retryAfterSeconds(value: string | null): number | null {\n if (!value) return null;\n const n = Number(value);\n if (Number.isFinite(n)) return Math.max(0, n);\n const when = Date.parse(value);\n return Number.isNaN(when) ? null : Math.max(0, (when - Date.now()) / 1000);\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nfunction intHeader(value: string | null): number | null {\n if (value === null || value === \"\") return null;\n const n = Number(value);\n return Number.isFinite(n) ? Math.trunc(n) : null;\n}\n\nfunction rateLimitFrom(headers: Headers): RateLimitInfo | null {\n const limit = intHeader(headers.get(\"X-RateLimit-Limit\"));\n if (limit === null) return null;\n return {\n limit,\n remaining: intHeader(headers.get(\"X-RateLimit-Remaining\")),\n reset: intHeader(headers.get(\"X-RateLimit-Reset\")),\n };\n}\n\n/** A random UUID v4: Web Crypto where available (browsers, Node 19+), node:crypto on Node 18. */\nasync function newIdempotencyKey(): Promise<string> {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (typeof c?.randomUUID === \"function\") return c.randomUUID();\n try {\n const { randomUUID } = await import(\"node:crypto\");\n return randomUUID();\n } catch {\n const hex = Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16));\n hex[12] = \"4\";\n hex[16] = ((parseInt(hex[16] as string, 16) & 0x3) | 0x8).toString(16);\n const h = hex.join(\"\");\n return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n }\n}\n\n/**\n * Client for the Songbrain API. Every method resolves to the JSON body of\n * the response.\n *\n * ```ts\n * const sb = new Songbrain(); // reads SONGBRAIN_API_KEY\n * const song = await sb.analyze({ file: \"song.mp3\" });\n * for (const s of song.shot_plan?.clip?.scenes ?? []) console.log(s.start_sec, s.prompt);\n * ```\n */\nexport class Songbrain {\n readonly apiKey: string | undefined;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n readonly maxRetries: number;\n private readonly fetchImpl: typeof fetch;\n /**\n * `{ limit, remaining, reset }` from the `X-RateLimit-*` headers of the last response\n * that had them (null before that). `reset` is seconds until the window has room.\n */\n lastRateLimit: RateLimitInfo | null = null;\n /** The `Songbrain-Request-Id` of the last response (null before the first one). */\n lastRequestId: string | null = null;\n /** @internal Overridable in tests. */\n _sleep: (ms: number) => Promise<void> = sleep;\n\n constructor(options: SongbrainOptions = {}) {\n this.apiKey = options.apiKey !== undefined ? options.apiKey || undefined : env(\"SONGBRAIN_API_KEY\");\n this.baseUrl = (options.baseUrl ?? env(\"SONGBRAIN_BASE_URL\") ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeoutMs = options.timeoutMs ?? 60_000;\n this.maxRetries = Math.max(0, options.maxRetries ?? 3);\n const f = options.fetch ?? (globalThis.fetch as typeof fetch | undefined);\n if (!f) throw new Error(\"No global fetch found. Use Node 18+ or pass `fetch` in the options.\");\n this.fetchImpl = f;\n }\n\n // ── songs ────────────────────────────────────────────────────────────────\n\n /**\n * Analyse a song from a file or a public URL. Pass exactly one of `file` or `audioUrl`\n * (or neither with `test: true`).\n * Typically 60-90 s. With `wait: true` (default) resolves to the full document;\n * with `wait: false` to the 202 body `{ id, status: \"processing\", eta_sec, billing }`.\n */\n analyze(options: AnalyzeOptions & { wait: false }): Promise<SongCreated>;\n analyze(options: AnalyzeOptions & { wait?: true }): Promise<Song>;\n analyze(options: AnalyzeOptions): Promise<Song | SongCreated>;\n async analyze(options: AnalyzeOptions): Promise<Song | SongCreated> {\n const { file, audioUrl, test } = options;\n if (file !== undefined && audioUrl !== undefined) {\n throw new TypeError(\"Pass exactly one of `file` or `audioUrl`.\");\n }\n if (file === undefined && audioUrl === undefined && !test) {\n throw new TypeError(\"Pass exactly one of `file` or `audioUrl` (or `test: true`).\");\n }\n const fields: Record<string, string> = {};\n if (options.title !== undefined) fields.title = options.title;\n if (options.artist !== undefined) fields.artist = options.artist;\n if (options.webhookUrl !== undefined) fields.webhook_url = options.webhookUrl;\n if (options.externalRef !== undefined) fields.external_ref = options.externalRef;\n if (options.lyrics !== undefined) fields.lyrics = options.lyrics;\n const headers = { [IDEMPOTENCY_HEADER]: options.idempotencyKey || (await newIdempotencyKey()) };\n\n let created: SongCreated;\n if (file === undefined) {\n const json: Json = audioUrl !== undefined ? { audio_url: audioUrl, ...fields } : { ...fields };\n if (test) json.test = true;\n created = await this.request<SongCreated>(\"POST\", \"/songs\", { json, headers });\n } else {\n if (test) fields.test = \"true\";\n const { blob, name } = await toUpload(file, options.filename);\n created = await this.request<SongCreated>(\"POST\", \"/songs\", {\n headers,\n form: () => {\n const form = new FormData();\n form.append(\"file\", blob, name);\n for (const [k, v] of Object.entries(fields)) form.append(k, v);\n return form;\n },\n });\n }\n if (options.wait === false) return created;\n return this.waitFor(created.id, {\n pollIntervalMs: options.pollIntervalMs,\n timeoutMs: options.timeoutMs,\n });\n }\n\n /** Poll GET /songs/{id} until the song is done. Throws `AnalysisFailed` or `WaitTimeout`. */\n async waitFor(songId: string, options: WaitOptions = {}): Promise<Song> {\n const timeoutMs = options.timeoutMs ?? 300_000;\n const interval = Math.max(500, options.pollIntervalMs ?? 5_000);\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const doc = await this.getSong(songId);\n if (doc.status === \"done\") return doc;\n if (doc.status === \"failed\") {\n throw new AnalysisFailed(\n songId,\n doc.error?.code ?? \"analysis_failed\",\n doc.error?.message ?? \"The analysis failed. Paid credits were refunded automatically.\",\n doc,\n );\n }\n const remaining = deadline - Date.now();\n if (remaining <= 0) throw new WaitTimeout(songId, timeoutMs);\n await this._sleep(Math.min(interval, remaining));\n }\n }\n\n /** Status while processing, then the full document. */\n getSong(songId: string, options: GetSongOptions = {}): Promise<Song> {\n return this.request<Song>(\"GET\", `/songs/${encodeURIComponent(songId)}`, { query: docQuery(options) });\n }\n\n /** Only the story and the shot plan: `{ id, status, song, story, shot_plan }`. */\n shotPlan(songId: string): Promise<SongShotPlan> {\n return this.request<SongShotPlan>(\"GET\", `/songs/${encodeURIComponent(songId)}/shot-plan`);\n }\n\n /** One page of your songs, newest first: `{ object: \"list\", data, has_more, next_cursor }`. */\n listSongs(options: ListSongsOptions = {}): Promise<SongList> {\n return this.request<SongList>(\"GET\", \"/songs\", {\n query: { limit: String(options.limit ?? 20), starting_after: options.startingAfter },\n });\n }\n\n /**\n * All your songs, newest first, fetching pages as needed.\n *\n * ```ts\n * for await (const item of sb.iterSongs()) console.log(item.id, item.status);\n * ```\n */\n async *iterSongs(options: IterSongsOptions = {}): AsyncGenerator<SongListItem, void, undefined> {\n let cursor = options.startingAfter;\n for (;;) {\n const page = await this.listSongs({ limit: options.pageSize ?? 100, startingAfter: cursor });\n const data = page.data ?? [];\n yield* data;\n cursor = page.next_cursor ?? data[data.length - 1]?.id;\n if (!page.has_more || !cursor) return;\n }\n }\n\n /** Delete a song's audio and analysis now. 409 `still_processing` while it runs. */\n deleteSong(songId: string): Promise<DeleteResult> {\n return this.request<DeleteResult>(\"DELETE\", `/songs/${encodeURIComponent(songId)}`);\n }\n\n // ── account ──────────────────────────────────────────────────────────────\n\n /** Free songs left this month, credit balance and price per song. */\n account(): Promise<Account> {\n return this.request<Account>(\"GET\", \"/account\");\n }\n\n /** Prices and limits. No key needed. */\n pricing(): Promise<Pricing> {\n return this.request<Pricing>(\"GET\", \"/pricing\");\n }\n\n // ── webhooks ─────────────────────────────────────────────────────────────\n\n /**\n * Send a signed `ping` event to `url` right now:\n * `{ delivered, status_code, latency_ms, event_id }`. Checks your receiver before real songs.\n */\n testWebhook(url: string): Promise<WebhookTestResult> {\n return this.request<WebhookTestResult>(\"POST\", \"/webhooks/test\", { json: { url } });\n }\n\n /** The last webhook delivery attempts for your account, newest first. */\n webhookDeliveries(options: { limit?: number } = {}): Promise<WebhookDeliveryList> {\n return this.request<WebhookDeliveryList>(\"GET\", \"/webhooks/deliveries\", {\n query: { limit: String(options.limit ?? 20) },\n });\n }\n\n // ── examples (no key) ────────────────────────────────────────────────────\n\n /** Example analyses of Songbrain's own songs. No key needed. */\n examples(): Promise<ExampleList> {\n return this.request<ExampleList>(\"GET\", \"/examples\");\n }\n\n /** A full example document, in the same format as `getSong`. No key needed. */\n example(exampleId: string, options: GetSongOptions = {}): Promise<Song> {\n return this.request<Song>(\"GET\", `/examples/${encodeURIComponent(exampleId)}`, { query: docQuery(options) });\n }\n\n /** An example story and shot plan, in the same format as `shotPlan`. No key needed. */\n exampleShotPlan(exampleId: string): Promise<SongShotPlan> {\n return this.request<SongShotPlan>(\"GET\", `/examples/${encodeURIComponent(exampleId)}/shot-plan`);\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private backoffMs(attempt: number): number {\n return Math.min(500 * 2 ** attempt, 8_000) + Math.random() * 250;\n }\n\n private async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n opts: {\n query?: Record<string, string | undefined>;\n json?: Json;\n form?: () => FormData;\n headers?: Record<string, string>;\n } = {},\n ): Promise<T> {\n const url = new URL(this.baseUrl + path);\n for (const [k, v] of Object.entries(opts.query ?? {})) if (v !== undefined) url.searchParams.set(k, v);\n const hasKey = Boolean(opts.headers?.[IDEMPOTENCY_HEADER]);\n // A POST with an Idempotency-Key is safe to resend: the API returns the first answer again.\n const idempotent = method !== \"POST\" || hasKey;\n\n for (let attempt = 0; ; attempt++) {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"User-Agent\": `songbrain-js/${VERSION}`,\n };\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;\n Object.assign(headers, opts.headers);\n let body: BodyInit | undefined;\n if (opts.json) {\n headers[\"Content-Type\"] = \"application/json\";\n body = JSON.stringify(opts.json);\n } else if (opts.form) {\n body = opts.form(); // fresh FormData per attempt; fetch sets the boundary\n }\n\n let res: Response;\n try {\n res = await this.fetchImpl(url, { method, headers, body, signal: AbortSignal.timeout(this.timeoutMs) });\n } catch (err) {\n // Network errors and timeouts: only resend if nothing can have been created\n // or the request carries an Idempotency-Key.\n if (idempotent && attempt < this.maxRetries) {\n await this._sleep(this.backoffMs(attempt));\n continue;\n }\n const message = err instanceof Error ? err.message : String(err);\n throw new SongbrainError(0, \"connection_error\", message);\n }\n\n this.lastRequestId = res.headers.get(REQUEST_ID_HEADER) || null;\n const rateLimit = rateLimitFrom(res.headers);\n if (rateLimit) this.lastRateLimit = rateLimit;\n\n if (res.ok) {\n const text = await res.text();\n if (!text) return {} as T;\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new SongbrainError(res.status, \"invalid_response\", \"Response is not JSON.\");\n }\n }\n\n const error = await errorFrom(res);\n // 429 never started any work. For a POST without Idempotency-Key only retry\n // gateway errors (502/503).\n const inProgress = hasKey && res.status === 409 && error.code === \"idempotency_in_progress\";\n const retryable =\n res.status === 429 ||\n inProgress ||\n (res.status >= 500 && (idempotent || res.status === 502 || res.status === 503));\n if (retryable && attempt < this.maxRetries) {\n const after = error instanceof RateLimited ? error.retryAfter : null;\n let delayMs = after !== null ? after * 1000 : this.backoffMs(attempt);\n if (inProgress) delayMs = Math.max(1000, delayMs);\n if (delayMs <= MAX_RETRY_WAIT_SEC * 1000) {\n await this._sleep(delayMs);\n continue;\n }\n }\n throw error;\n }\n }\n}\n\nfunction docQuery(options: GetSongOptions): Record<string, string | undefined> {\n const include = Array.isArray(options.include) ? options.include.join(\",\") : options.include;\n return { view: options.view, include };\n}\n\nasync function errorFrom(res: Response): Promise<SongbrainError> {\n const text = await res.text().catch(() => \"\");\n let body: unknown;\n let code = `http_${res.status}`;\n let message = text.trim().slice(0, 500) || res.statusText || `HTTP ${res.status}`;\n let requestId = res.headers.get(REQUEST_ID_HEADER) || null;\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n if (body && typeof body === \"object\") {\n const err = (body as { error?: unknown }).error;\n if (err && typeof err === \"object\") {\n const e = err as { code?: unknown; message?: unknown; request_id?: unknown };\n if (e.code) code = String(e.code);\n if (e.message) message = String(e.message);\n if (e.request_id) requestId = String(e.request_id);\n } else if (\"detail\" in (body as object)) {\n // request validation, older API versions\n code = \"validation_error\";\n message = JSON.stringify((body as { detail: unknown }).detail);\n }\n }\n switch (res.status) {\n case 401:\n return new AuthenticationError(401, code, message, body, requestId);\n case 402:\n return new InsufficientCredits(402, code, message, body, requestId);\n case 404:\n return new NotFound(404, code, message, body, requestId);\n case 429:\n return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get(\"Retry-After\")), requestId);\n default:\n return new SongbrainError(res.status, code, message, body, requestId);\n }\n}\n\nasync function toUpload(file: FileInput, filename?: string): Promise<{ blob: Blob; name: string }> {\n let blob: Blob;\n let sourceName: string | undefined;\n if (typeof file === \"string\") {\n const { readFile } = await import(\"node:fs/promises\");\n const data = await readFile(file);\n blob = new Blob([data as BlobPart]);\n sourceName = baseName(file);\n } else if (file instanceof ArrayBuffer) {\n blob = new Blob([file]);\n } else if (file instanceof Uint8Array) {\n blob = new Blob([file as BlobPart]);\n } else if (typeof Blob !== \"undefined\" && file instanceof Blob) {\n blob = file;\n const n = (file as Blob & { name?: unknown }).name;\n if (typeof n === \"string\" && n) sourceName = baseName(n);\n } else {\n throw new TypeError(\"`file` must be a Blob, a Buffer/Uint8Array, an ArrayBuffer or a file path.\");\n }\n\n let name = filename ?? sourceName;\n if (!name || !extOf(name)) {\n const head = new Uint8Array(await blob.slice(0, 16).arrayBuffer());\n const ext = sniffExtension(head);\n if (!ext) {\n throw new TypeError('Could not tell the audio format. Pass `filename` with an extension, e.g. filename: \"song.mp3\".');\n }\n name = `${(name ?? \"song\").replace(/\\.$/, \"\")}.${ext}`;\n }\n const type = MIME[extOf(name)];\n if (type && !blob.type) blob = new Blob([blob], { type });\n return { blob, name };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport type { WebhookEvent } from \"./types.js\";\n\n/** The header Songbrain signs webhooks with. */\nexport const SIGNATURE_HEADER = \"Songbrain-Signature\";\n\ntype RawBody = string | Uint8Array | ArrayBuffer;\n\nfunction toBuffer(body: RawBody): Buffer {\n if (typeof body === \"string\") return Buffer.from(body, \"utf8\");\n if (body instanceof ArrayBuffer) return Buffer.from(new Uint8Array(body));\n return Buffer.from(body.buffer, body.byteOffset, body.byteLength);\n}\n\nfunction parseHeader(header: string): { t: number | null; v1: string[] } {\n let t: number | null = null;\n const v1: string[] = [];\n for (const part of header.split(\",\")) {\n const i = part.indexOf(\"=\");\n if (i < 0) continue;\n const key = part.slice(0, i).trim();\n const value = part.slice(i + 1).trim();\n if (key === \"t\") {\n if (!/^\\d+$/.test(value)) return { t: null, v1: [] };\n t = Number(value);\n } else if (key === \"v1\" && value) {\n v1.push(value.toLowerCase());\n }\n }\n return { t, v1 };\n}\n\nfunction mac(body: Buffer, t: number, secret: string): string {\n return createHmac(\"sha256\", secret).update(`${t}.`).update(body).digest(\"hex\");\n}\n\n/**\n * Check a `Songbrain-Signature: t=<unix>,v1=<hex>` header, where v1 is the\n * HMAC-SHA256 of `\"<t>.<raw body>\"` with your key's webhook secret.\n *\n * Pass the raw request body exactly as received, before any JSON parsing.\n * Returns false (never throws) for a missing, malformed, wrong or old signature.\n *\n * @param toleranceSec Maximum age in seconds (default 300). 0 disables the check.\n */\nexport function verifyWebhook(\n rawBody: RawBody,\n header: string | null | undefined,\n secret: string,\n toleranceSec = 300,\n): boolean {\n if (!header || !secret) return false;\n const { t, v1 } = parseHeader(header);\n if (t === null || v1.length === 0) return false;\n if (toleranceSec > 0 && Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;\n const expected = Buffer.from(mac(toBuffer(rawBody), t, secret), \"utf8\");\n return v1.some((sig) => {\n const given = Buffer.from(sig, \"utf8\");\n return given.length === expected.length && timingSafeEqual(given, expected);\n });\n}\n\n/**\n * Verify the signature and return the parsed event:\n * `{ id, type: \"song.done\" | \"song.failed\" | \"account.low_balance\" | \"ping\", created, livemode, data }`.\n * Throws if the signature is not valid.\n *\n * `id` (`evt_…`, also the `Songbrain-Event-Id` header) stays the same when\n * Songbrain retries an event (up to 10 attempts over about 3 days): store the\n * ids you have handled and skip repeats.\n */\nexport function constructWebhookEvent<T = Record<string, unknown>>(\n rawBody: RawBody,\n header: string | null | undefined,\n secret: string,\n toleranceSec = 300,\n): WebhookEvent<T> {\n if (!verifyWebhook(rawBody, header, secret, toleranceSec)) {\n throw new Error(\"Invalid Songbrain-Signature\");\n }\n return JSON.parse(toBuffer(rawBody).toString(\"utf8\")) as WebhookEvent<T>;\n}\n\n/** Build a signature header value. Useful to test your webhook handler. */\nexport function signWebhook(rawBody: RawBody, secret: string, timestampSec = Math.floor(Date.now() / 1000)): string {\n return `t=${timestampSec},v1=${mac(toBuffer(rawBody), timestampSec, secret)}`;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "songbrain",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Official TypeScript/JavaScript client for the Songbrain API: song in, video plan out. Song DNA, beat grid, best moments, timed lyrics, story and a beat-synced shot plan.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Songbrain <support@songbrain.ai> (https://www.songbrain.ai)",
|