@audialize/sdk 0.1.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 +263 -0
- package/dist/index.cjs +648 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +278 -0
- package/dist/index.d.ts +278 -0
- package/dist/index.js +626 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/http/errors.ts
|
|
4
|
+
var AudiolizeError = class extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(message, code) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = this.constructor.name;
|
|
9
|
+
this.code = code;
|
|
10
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var AudiolizeApiError = class extends AudiolizeError {
|
|
14
|
+
statusCode;
|
|
15
|
+
requestId;
|
|
16
|
+
constructor(message, code, statusCode, requestId) {
|
|
17
|
+
super(message, code);
|
|
18
|
+
this.statusCode = statusCode;
|
|
19
|
+
this.requestId = requestId;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var AuthenticationError = class extends AudiolizeApiError {
|
|
23
|
+
constructor(message, requestId) {
|
|
24
|
+
super(message, "AUTHENTICATION_ERROR", 401, requestId);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var AuthorizationError = class extends AudiolizeApiError {
|
|
28
|
+
constructor(message, requestId) {
|
|
29
|
+
super(message, "AUTHORIZATION_ERROR", 403, requestId);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var NotFoundError = class extends AudiolizeApiError {
|
|
33
|
+
constructor(message, requestId) {
|
|
34
|
+
super(message, "NOT_FOUND_ERROR", 404, requestId);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var RateLimitError = class extends AudiolizeApiError {
|
|
38
|
+
retryAfter;
|
|
39
|
+
constructor(message, retryAfter, requestId) {
|
|
40
|
+
super(message, "RATE_LIMIT_ERROR", 429, requestId);
|
|
41
|
+
this.retryAfter = retryAfter;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var PaywallError = class extends AudiolizeApiError {
|
|
45
|
+
constructor(message, requestId) {
|
|
46
|
+
super(message, "PAYWALL_ERROR", 402, requestId);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var ServerError = class extends AudiolizeApiError {
|
|
50
|
+
constructor(message, statusCode, requestId) {
|
|
51
|
+
super(message, "SERVER_ERROR", statusCode, requestId);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var AudiolizeUploadError = class extends AudiolizeError {
|
|
55
|
+
constructor(message) {
|
|
56
|
+
super(message, "UPLOAD_ERROR");
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var AudiolizeTimeoutError = class extends AudiolizeError {
|
|
60
|
+
constructor(message) {
|
|
61
|
+
super(message, "TIMEOUT_ERROR");
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var AudiolizeValidationError = class extends AudiolizeError {
|
|
65
|
+
constructor(message) {
|
|
66
|
+
super(message, "VALIDATION_ERROR");
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/http/HttpClient.ts
|
|
71
|
+
var HttpClient = class {
|
|
72
|
+
apiKey;
|
|
73
|
+
baseUrl;
|
|
74
|
+
timeout;
|
|
75
|
+
maxRetries;
|
|
76
|
+
fetchImpl;
|
|
77
|
+
constructor(options) {
|
|
78
|
+
if (!options.apiKey) {
|
|
79
|
+
throw new Error("API key is required");
|
|
80
|
+
}
|
|
81
|
+
this.apiKey = options.apiKey;
|
|
82
|
+
this.baseUrl = options.baseUrl || "https://api.audialize.com";
|
|
83
|
+
this.timeout = options.timeout || 3e4;
|
|
84
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
85
|
+
this.fetchImpl = options.fetch || globalThis.fetch.bind(globalThis);
|
|
86
|
+
}
|
|
87
|
+
async request(path, options = {}) {
|
|
88
|
+
const url = new URL(path, this.baseUrl).toString();
|
|
89
|
+
const headers = new Headers(options.headers);
|
|
90
|
+
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
91
|
+
if (!headers.has("Content-Type") && options.body && typeof options.body === "string") {
|
|
92
|
+
headers.set("Content-Type", "application/json");
|
|
93
|
+
}
|
|
94
|
+
let attempt = 0;
|
|
95
|
+
while (attempt <= this.maxRetries) {
|
|
96
|
+
try {
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
99
|
+
const response = await this.fetchImpl(url, {
|
|
100
|
+
...options,
|
|
101
|
+
headers,
|
|
102
|
+
signal: controller.signal
|
|
103
|
+
});
|
|
104
|
+
clearTimeout(timeoutId);
|
|
105
|
+
if (response.ok) {
|
|
106
|
+
if (response.status === 204) {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
return await response.json();
|
|
110
|
+
}
|
|
111
|
+
await this.handleErrorResponse(response);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error instanceof AudiolizeApiError) {
|
|
114
|
+
if (error instanceof RateLimitError && attempt < this.maxRetries) {
|
|
115
|
+
const delayMs = error.retryAfter ? error.retryAfter * 1e3 : this.getBackoffDelay(attempt);
|
|
116
|
+
await this.delay(delayMs);
|
|
117
|
+
attempt++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (error.statusCode >= 500 && attempt < this.maxRetries) {
|
|
121
|
+
await this.delay(this.getBackoffDelay(attempt));
|
|
122
|
+
attempt++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
128
|
+
if (attempt < this.maxRetries) {
|
|
129
|
+
await this.delay(this.getBackoffDelay(attempt));
|
|
130
|
+
attempt++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
throw new AudiolizeTimeoutError(`Request timeout after ${this.timeout}ms`);
|
|
134
|
+
}
|
|
135
|
+
if (attempt < this.maxRetries) {
|
|
136
|
+
await this.delay(this.getBackoffDelay(attempt));
|
|
137
|
+
attempt++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
throw new Error("Max retries exceeded");
|
|
144
|
+
}
|
|
145
|
+
async handleErrorResponse(response) {
|
|
146
|
+
let errorData = {};
|
|
147
|
+
try {
|
|
148
|
+
const parsed = await response.json();
|
|
149
|
+
if (isRecord(parsed)) {
|
|
150
|
+
errorData = parsed;
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
const errorMessage = typeof errorData.error === "string" ? errorData.error : void 0;
|
|
155
|
+
const fallbackMessage = typeof errorData.message === "string" ? errorData.message : void 0;
|
|
156
|
+
const message = errorMessage || fallbackMessage || response.statusText;
|
|
157
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
158
|
+
switch (response.status) {
|
|
159
|
+
case 401:
|
|
160
|
+
throw new AuthenticationError(message, requestId);
|
|
161
|
+
case 402:
|
|
162
|
+
throw new PaywallError(message, requestId);
|
|
163
|
+
case 403:
|
|
164
|
+
throw new AuthorizationError(message, requestId);
|
|
165
|
+
case 404:
|
|
166
|
+
throw new NotFoundError(message, requestId);
|
|
167
|
+
case 429: {
|
|
168
|
+
const retryAfterHeader = response.headers.get("retry-after");
|
|
169
|
+
const retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : void 0;
|
|
170
|
+
throw new RateLimitError(message, retryAfter, requestId);
|
|
171
|
+
}
|
|
172
|
+
default:
|
|
173
|
+
if (response.status >= 500) {
|
|
174
|
+
throw new ServerError(message, response.status, requestId);
|
|
175
|
+
}
|
|
176
|
+
throw new AudiolizeApiError(message, "API_ERROR", response.status, requestId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
delay(ms) {
|
|
180
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
181
|
+
}
|
|
182
|
+
getBackoffDelay(attempt) {
|
|
183
|
+
const baseDelay = 1e3;
|
|
184
|
+
const maxDelay = 6e4;
|
|
185
|
+
const delay2 = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
186
|
+
return delay2 * (0.5 + Math.random() * 0.5);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
function isRecord(value) {
|
|
190
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/workflows/JobPoller.ts
|
|
194
|
+
var JobPoller = class {
|
|
195
|
+
jobId;
|
|
196
|
+
jobsResource;
|
|
197
|
+
options;
|
|
198
|
+
currentInterval;
|
|
199
|
+
unchangedStepCount = 0;
|
|
200
|
+
lastStep = null;
|
|
201
|
+
isPolling = false;
|
|
202
|
+
listeners = {};
|
|
203
|
+
constructor(jobId, jobsResource, options) {
|
|
204
|
+
this.jobId = jobId;
|
|
205
|
+
this.jobsResource = jobsResource;
|
|
206
|
+
this.options = {
|
|
207
|
+
interval: options?.interval ?? 3e3,
|
|
208
|
+
maxInterval: options?.maxInterval ?? 15e3,
|
|
209
|
+
timeout: options?.timeout ?? 6e5,
|
|
210
|
+
backoff: options?.backoff ?? true
|
|
211
|
+
};
|
|
212
|
+
this.currentInterval = this.options.interval;
|
|
213
|
+
}
|
|
214
|
+
on(event, listener) {
|
|
215
|
+
if (!this.listeners[event]) {
|
|
216
|
+
this.listeners[event] = [];
|
|
217
|
+
}
|
|
218
|
+
this.listeners[event].push(listener);
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
emit(event, payload) {
|
|
222
|
+
if (event === "progress") {
|
|
223
|
+
const eventListeners2 = this.listeners.progress ?? [];
|
|
224
|
+
for (const listener of eventListeners2) {
|
|
225
|
+
listener(payload);
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (event === "completed") {
|
|
230
|
+
const eventListeners2 = this.listeners.completed ?? [];
|
|
231
|
+
for (const listener of eventListeners2) {
|
|
232
|
+
listener(payload);
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const eventListeners = this.listeners.failed ?? [];
|
|
237
|
+
for (const listener of eventListeners) {
|
|
238
|
+
listener(payload);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async start() {
|
|
242
|
+
if (this.isPolling) {
|
|
243
|
+
throw new Error("Polling is already in progress");
|
|
244
|
+
}
|
|
245
|
+
this.isPolling = true;
|
|
246
|
+
const startTime = Date.now();
|
|
247
|
+
while (this.isPolling) {
|
|
248
|
+
if (Date.now() - startTime > this.options.timeout) {
|
|
249
|
+
this.isPolling = false;
|
|
250
|
+
const error = new AudiolizeTimeoutError(`Job ${this.jobId} timed out after ${this.options.timeout}ms`);
|
|
251
|
+
this.emit("failed", error);
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const job = await this.jobsResource.get(this.jobId);
|
|
256
|
+
if (job.status === "completed") {
|
|
257
|
+
this.isPolling = false;
|
|
258
|
+
this.emit("completed", job);
|
|
259
|
+
return job;
|
|
260
|
+
}
|
|
261
|
+
if (job.status === "failed") {
|
|
262
|
+
this.isPolling = false;
|
|
263
|
+
const error = new Error(job.error || "Job failed");
|
|
264
|
+
this.emit("failed", error);
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
this.emit("progress", { step: job.currentStep, progress: job.progress });
|
|
268
|
+
if (this.options.backoff) {
|
|
269
|
+
if (job.currentStep === this.lastStep) {
|
|
270
|
+
this.unchangedStepCount++;
|
|
271
|
+
if (this.unchangedStepCount >= 3) {
|
|
272
|
+
this.currentInterval = Math.min(this.currentInterval * 2, this.options.maxInterval);
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
this.unchangedStepCount = 0;
|
|
276
|
+
this.currentInterval = this.options.interval;
|
|
277
|
+
this.lastStep = job.currentStep;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
await new Promise((resolve) => setTimeout(resolve, this.currentInterval));
|
|
281
|
+
} catch (error) {
|
|
282
|
+
this.isPolling = false;
|
|
283
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
284
|
+
this.emit("failed", err);
|
|
285
|
+
throw err;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
throw new Error("Polling stopped unexpectedly");
|
|
289
|
+
}
|
|
290
|
+
stop() {
|
|
291
|
+
this.isPolling = false;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// src/resources/jobs.ts
|
|
296
|
+
var JobsResource = class {
|
|
297
|
+
constructor(client) {
|
|
298
|
+
this.client = client;
|
|
299
|
+
}
|
|
300
|
+
async get(jobId) {
|
|
301
|
+
return this.client.request(`/jobs/${jobId}/status`);
|
|
302
|
+
}
|
|
303
|
+
async list(options) {
|
|
304
|
+
const query = new URLSearchParams();
|
|
305
|
+
if (options?.limit) query.set("limit", options.limit.toString());
|
|
306
|
+
if (options?.pageToken) query.set("pageToken", options.pageToken);
|
|
307
|
+
const path = `/jobs${query.toString() ? `?${query.toString()}` : ""}`;
|
|
308
|
+
return this.client.request(path);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Polls until the job reaches `completed` or `failed`, then returns the final job.
|
|
312
|
+
* Throws `AudiolizeTimeoutError` if the job does not complete within `options.timeout`.
|
|
313
|
+
*/
|
|
314
|
+
waitForCompletion(jobId, options) {
|
|
315
|
+
const pollConfig = {};
|
|
316
|
+
if (options?.interval !== void 0) pollConfig.interval = options.interval;
|
|
317
|
+
if (options?.maxInterval !== void 0) pollConfig.maxInterval = options.maxInterval;
|
|
318
|
+
if (options?.timeout !== void 0) pollConfig.timeout = options.timeout;
|
|
319
|
+
if (options?.backoff !== void 0) pollConfig.backoff = options.backoff;
|
|
320
|
+
const poller = new JobPoller(jobId, this, pollConfig);
|
|
321
|
+
if (options?.onProgress) {
|
|
322
|
+
poller.on("progress", ({ step, progress }) => {
|
|
323
|
+
options.onProgress(step, progress);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return poller.start();
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Returns a `JobPoller` instance for event-driven polling.
|
|
330
|
+
* Call `.start()` to begin, or listen to `progress`, `completed`, `failed` events.
|
|
331
|
+
*/
|
|
332
|
+
poll(jobId, options) {
|
|
333
|
+
return new JobPoller(jobId, this, options);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// src/resources/languages.ts
|
|
338
|
+
var LanguagesResource = class {
|
|
339
|
+
constructor(client) {
|
|
340
|
+
this.client = client;
|
|
341
|
+
}
|
|
342
|
+
async list() {
|
|
343
|
+
return this.client.request("/supported-languages");
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// src/resources/subtitles.ts
|
|
348
|
+
var SubtitlesResource = class {
|
|
349
|
+
constructor(client) {
|
|
350
|
+
this.client = client;
|
|
351
|
+
}
|
|
352
|
+
async download(jobId, language, format) {
|
|
353
|
+
const response = await this.client.request("/presigned-url", {
|
|
354
|
+
method: "POST",
|
|
355
|
+
body: JSON.stringify({
|
|
356
|
+
type: "download",
|
|
357
|
+
jobId,
|
|
358
|
+
language,
|
|
359
|
+
format
|
|
360
|
+
})
|
|
361
|
+
});
|
|
362
|
+
const downloadResponse = await fetch(response.url);
|
|
363
|
+
if (!downloadResponse.ok) {
|
|
364
|
+
throw new Error(`Failed to download subtitles: ${downloadResponse.statusText}`);
|
|
365
|
+
}
|
|
366
|
+
if (format === "json") {
|
|
367
|
+
return downloadResponse.json();
|
|
368
|
+
}
|
|
369
|
+
return downloadResponse.text();
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// src/utils/retry.ts
|
|
374
|
+
function getBackoffDelay(attempt, baseDelay = 1e3, maxDelay = 6e4) {
|
|
375
|
+
const exponential = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
376
|
+
const jitter = 0.5 + Math.random() * 0.5;
|
|
377
|
+
return Math.floor(exponential * jitter);
|
|
378
|
+
}
|
|
379
|
+
function delay(ms) {
|
|
380
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
381
|
+
}
|
|
382
|
+
async function withRetry(fn, options = {}) {
|
|
383
|
+
const { maxRetries = 3, baseDelay = 1e3, maxDelay = 6e4, shouldRetry } = options;
|
|
384
|
+
let attempt = 0;
|
|
385
|
+
while (true) {
|
|
386
|
+
try {
|
|
387
|
+
return await fn();
|
|
388
|
+
} catch (error) {
|
|
389
|
+
const isRetryable = shouldRetry ? shouldRetry(error, attempt) : true;
|
|
390
|
+
if (!isRetryable || attempt >= maxRetries) {
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
await delay(getBackoffDelay(attempt, baseDelay, maxDelay));
|
|
394
|
+
attempt++;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/utils/multipart.ts
|
|
400
|
+
function splitIntoChunks(file, partUrls) {
|
|
401
|
+
const totalSize = file instanceof Blob ? file.size : file.length;
|
|
402
|
+
const partCount = partUrls.length;
|
|
403
|
+
const chunkSize = Math.ceil(totalSize / partCount);
|
|
404
|
+
const chunks = [];
|
|
405
|
+
for (let i = 0; i < partCount; i++) {
|
|
406
|
+
const start = i * chunkSize;
|
|
407
|
+
const end = Math.min(start + chunkSize, totalSize);
|
|
408
|
+
let data;
|
|
409
|
+
if (file instanceof Blob) {
|
|
410
|
+
data = file.slice(start, end);
|
|
411
|
+
} else {
|
|
412
|
+
data = file.subarray(start, end);
|
|
413
|
+
}
|
|
414
|
+
chunks.push({
|
|
415
|
+
partNumber: i + 1,
|
|
416
|
+
url: partUrls[i],
|
|
417
|
+
data,
|
|
418
|
+
size: end - start
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return Promise.resolve(chunks);
|
|
422
|
+
}
|
|
423
|
+
async function uploadPart(chunk) {
|
|
424
|
+
const response = await withRetry(
|
|
425
|
+
() => fetch(chunk.url, {
|
|
426
|
+
method: "PUT",
|
|
427
|
+
body: chunk.data,
|
|
428
|
+
headers: {
|
|
429
|
+
"Content-Length": chunk.size.toString()
|
|
430
|
+
}
|
|
431
|
+
}),
|
|
432
|
+
{ maxRetries: 3 }
|
|
433
|
+
);
|
|
434
|
+
if (!response.ok) {
|
|
435
|
+
throw new AudiolizeUploadError(
|
|
436
|
+
`Failed to upload part ${chunk.partNumber}: ${response.statusText}`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
const etag = response.headers.get("etag");
|
|
440
|
+
if (!etag) {
|
|
441
|
+
throw new AudiolizeUploadError(`No ETag returned for part ${chunk.partNumber}`);
|
|
442
|
+
}
|
|
443
|
+
return { partNumber: chunk.partNumber, etag };
|
|
444
|
+
}
|
|
445
|
+
async function uploadAllParts(chunks, concurrency = 4, onProgress) {
|
|
446
|
+
const totalBytes = chunks.reduce((sum, c) => sum + c.size, 0);
|
|
447
|
+
let uploadedBytes = 0;
|
|
448
|
+
const results = [];
|
|
449
|
+
for (let i = 0; i < chunks.length; i += concurrency) {
|
|
450
|
+
const batch = chunks.slice(i, i + concurrency);
|
|
451
|
+
const batchResults = await Promise.all(
|
|
452
|
+
batch.map(async (chunk) => {
|
|
453
|
+
const part = await uploadPart(chunk);
|
|
454
|
+
uploadedBytes += chunk.size;
|
|
455
|
+
onProgress?.(uploadedBytes, totalBytes);
|
|
456
|
+
return { part, index: chunk.partNumber - 1 };
|
|
457
|
+
})
|
|
458
|
+
);
|
|
459
|
+
for (const { part, index } of batchResults) {
|
|
460
|
+
results[index] = part;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return results;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/resources/files.ts
|
|
467
|
+
var DEFAULT_MULTIPART_THRESHOLD = 100 * 1024 * 1024;
|
|
468
|
+
var FilesResource = class {
|
|
469
|
+
constructor(client) {
|
|
470
|
+
this.client = client;
|
|
471
|
+
}
|
|
472
|
+
async upload(file, options) {
|
|
473
|
+
const response = await this.client.request("/presigned-url", {
|
|
474
|
+
method: "POST",
|
|
475
|
+
body: JSON.stringify({
|
|
476
|
+
type: "upload",
|
|
477
|
+
fileName: options.fileName,
|
|
478
|
+
languages: options.languages,
|
|
479
|
+
sourceLanguage: options.sourceLanguage,
|
|
480
|
+
transcriptionOptions: options.transcriptionOptions,
|
|
481
|
+
context: options.context,
|
|
482
|
+
estimatedMinutes: options.estimatedMinutes,
|
|
483
|
+
enableDubbing: options.enableDubbing,
|
|
484
|
+
enableSdh: options.enableSdh
|
|
485
|
+
})
|
|
486
|
+
});
|
|
487
|
+
const body = await resolveBody(file);
|
|
488
|
+
const uploadResponse = await withRetry(
|
|
489
|
+
() => fetch(response.uploadUrl, {
|
|
490
|
+
method: "PUT",
|
|
491
|
+
body,
|
|
492
|
+
headers: {
|
|
493
|
+
"Content-Type": options.contentType || "application/octet-stream"
|
|
494
|
+
}
|
|
495
|
+
}),
|
|
496
|
+
{ maxRetries: 3 }
|
|
497
|
+
);
|
|
498
|
+
if (!uploadResponse.ok) {
|
|
499
|
+
throw new AudiolizeUploadError(`Failed to upload file: ${uploadResponse.statusText}`);
|
|
500
|
+
}
|
|
501
|
+
options.onUploadProgress?.(100);
|
|
502
|
+
return { jobId: response.jobId };
|
|
503
|
+
}
|
|
504
|
+
async uploadMultipart(file, options) {
|
|
505
|
+
const fileSize = file instanceof Blob ? file.size : file.length;
|
|
506
|
+
if (fileSize === 0) {
|
|
507
|
+
throw new AudiolizeValidationError("Cannot multipart-upload an empty file.");
|
|
508
|
+
}
|
|
509
|
+
const presigned = await this.client.request("/presigned-url", {
|
|
510
|
+
method: "POST",
|
|
511
|
+
body: JSON.stringify({
|
|
512
|
+
type: "upload",
|
|
513
|
+
multipart: true,
|
|
514
|
+
fileSize,
|
|
515
|
+
fileName: options.fileName,
|
|
516
|
+
languages: options.languages,
|
|
517
|
+
sourceLanguage: options.sourceLanguage,
|
|
518
|
+
transcriptionOptions: options.transcriptionOptions,
|
|
519
|
+
context: options.context,
|
|
520
|
+
estimatedMinutes: options.estimatedMinutes,
|
|
521
|
+
enableDubbing: options.enableDubbing,
|
|
522
|
+
enableSdh: options.enableSdh
|
|
523
|
+
})
|
|
524
|
+
});
|
|
525
|
+
const chunks = await splitIntoChunks(file, presigned.partUrls);
|
|
526
|
+
const progressCallback = options.onUploadProgress;
|
|
527
|
+
const onProgress = progressCallback ? (uploaded, total) => progressCallback(Math.round(uploaded / total * 100)) : void 0;
|
|
528
|
+
const parts = await uploadAllParts(chunks, options.concurrency ?? 4, onProgress);
|
|
529
|
+
const completion = await this.client.request("/presigned-url", {
|
|
530
|
+
method: "POST",
|
|
531
|
+
body: JSON.stringify({
|
|
532
|
+
type: "complete-multipart",
|
|
533
|
+
uploadId: presigned.uploadId,
|
|
534
|
+
uploadKey: presigned.uploadKey,
|
|
535
|
+
parts
|
|
536
|
+
})
|
|
537
|
+
});
|
|
538
|
+
if (!completion.success) {
|
|
539
|
+
throw new AudiolizeUploadError("Multipart upload completion reported failure.");
|
|
540
|
+
}
|
|
541
|
+
return { jobId: presigned.jobId };
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
async function resolveBody(file) {
|
|
545
|
+
if (typeof file === "string") {
|
|
546
|
+
const { readFile } = await import('fs/promises');
|
|
547
|
+
return readFile(file);
|
|
548
|
+
}
|
|
549
|
+
return file;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// src/workflows/localize.ts
|
|
553
|
+
async function localize(client, options) {
|
|
554
|
+
const multipartThreshold = options.multipartThreshold ?? DEFAULT_MULTIPART_THRESHOLD;
|
|
555
|
+
const uploadOptions = {
|
|
556
|
+
fileName: options.fileName ?? (typeof options.file === "string" ? options.file.split("/").pop() ?? "upload" : "upload"),
|
|
557
|
+
languages: options.languages,
|
|
558
|
+
...options.sourceLanguage !== void 0 && { sourceLanguage: options.sourceLanguage },
|
|
559
|
+
...options.transcriptionOptions !== void 0 && { transcriptionOptions: options.transcriptionOptions },
|
|
560
|
+
...options.context !== void 0 && { context: options.context },
|
|
561
|
+
...options.estimatedMinutes !== void 0 && { estimatedMinutes: options.estimatedMinutes },
|
|
562
|
+
...options.enableDubbing !== void 0 && { enableDubbing: options.enableDubbing },
|
|
563
|
+
...options.enableSdh !== void 0 && { enableSdh: options.enableSdh },
|
|
564
|
+
...options.onUploadProgress !== void 0 && { onUploadProgress: options.onUploadProgress }
|
|
565
|
+
};
|
|
566
|
+
const fileSize = getFileSize(options.file);
|
|
567
|
+
let jobId;
|
|
568
|
+
if (fileSize !== null && fileSize >= multipartThreshold && typeof options.file !== "string") {
|
|
569
|
+
const result = await client.files.uploadMultipart(options.file, {
|
|
570
|
+
...uploadOptions,
|
|
571
|
+
concurrency: 4
|
|
572
|
+
});
|
|
573
|
+
jobId = result.jobId;
|
|
574
|
+
} else {
|
|
575
|
+
const result = await client.files.upload(options.file, uploadOptions);
|
|
576
|
+
jobId = result.jobId;
|
|
577
|
+
}
|
|
578
|
+
const poller = new JobPoller(jobId, client.jobs, options.pollOptions);
|
|
579
|
+
const progressCallback = options.onProgress;
|
|
580
|
+
if (progressCallback) {
|
|
581
|
+
poller.on("progress", ({ step, progress }) => {
|
|
582
|
+
progressCallback(step, progress);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
return poller.start();
|
|
586
|
+
}
|
|
587
|
+
function getFileSize(file) {
|
|
588
|
+
if (file instanceof Blob) return file.size;
|
|
589
|
+
if (Buffer.isBuffer(file)) return file.length;
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/client.ts
|
|
594
|
+
var AudiolizeClient = class {
|
|
595
|
+
http;
|
|
596
|
+
jobs;
|
|
597
|
+
languages;
|
|
598
|
+
subtitles;
|
|
599
|
+
files;
|
|
600
|
+
constructor(options) {
|
|
601
|
+
if (!options.apiKey) {
|
|
602
|
+
throw new Error("API key is required");
|
|
603
|
+
}
|
|
604
|
+
const httpOptions = {
|
|
605
|
+
apiKey: options.apiKey,
|
|
606
|
+
baseUrl: options.baseUrl || "https://api.audialize.com/v1"
|
|
607
|
+
};
|
|
608
|
+
if (options.timeout !== void 0) httpOptions.timeout = options.timeout;
|
|
609
|
+
if (options.maxRetries !== void 0) httpOptions.maxRetries = options.maxRetries;
|
|
610
|
+
this.http = new HttpClient(httpOptions);
|
|
611
|
+
this.jobs = new JobsResource(this.http);
|
|
612
|
+
this.languages = new LanguagesResource(this.http);
|
|
613
|
+
this.subtitles = new SubtitlesResource(this.http);
|
|
614
|
+
this.files = new FilesResource(this.http);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* High-level workflow to upload a file, create a job, and poll until completion.
|
|
618
|
+
* @param options LocalizeOptions
|
|
619
|
+
* @returns Promise<Job>
|
|
620
|
+
*/
|
|
621
|
+
async localize(options) {
|
|
622
|
+
return localize(this, options);
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
exports.AudiolizeApiError = AudiolizeApiError;
|
|
627
|
+
exports.AudiolizeClient = AudiolizeClient;
|
|
628
|
+
exports.AudiolizeError = AudiolizeError;
|
|
629
|
+
exports.AudiolizeTimeoutError = AudiolizeTimeoutError;
|
|
630
|
+
exports.AudiolizeUploadError = AudiolizeUploadError;
|
|
631
|
+
exports.AudiolizeValidationError = AudiolizeValidationError;
|
|
632
|
+
exports.AuthenticationError = AuthenticationError;
|
|
633
|
+
exports.AuthorizationError = AuthorizationError;
|
|
634
|
+
exports.DEFAULT_MULTIPART_THRESHOLD = DEFAULT_MULTIPART_THRESHOLD;
|
|
635
|
+
exports.FilesResource = FilesResource;
|
|
636
|
+
exports.JobPoller = JobPoller;
|
|
637
|
+
exports.JobsResource = JobsResource;
|
|
638
|
+
exports.LanguagesResource = LanguagesResource;
|
|
639
|
+
exports.NotFoundError = NotFoundError;
|
|
640
|
+
exports.PaywallError = PaywallError;
|
|
641
|
+
exports.RateLimitError = RateLimitError;
|
|
642
|
+
exports.ServerError = ServerError;
|
|
643
|
+
exports.SubtitlesResource = SubtitlesResource;
|
|
644
|
+
exports.getBackoffDelay = getBackoffDelay;
|
|
645
|
+
exports.localize = localize;
|
|
646
|
+
exports.withRetry = withRetry;
|
|
647
|
+
//# sourceMappingURL=index.cjs.map
|
|
648
|
+
//# sourceMappingURL=index.cjs.map
|