subformer 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/LICENSE +21 -0
- package/README.md +301 -0
- package/dist/index.d.mts +403 -0
- package/dist/index.d.ts +403 -0
- package/dist/index.js +442 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +410 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
NotFoundError: () => NotFoundError,
|
|
25
|
+
RateLimitError: () => RateLimitError,
|
|
26
|
+
Subformer: () => Subformer,
|
|
27
|
+
SubformerError: () => SubformerError,
|
|
28
|
+
ValidationError: () => ValidationError
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/errors.ts
|
|
33
|
+
var SubformerError = class extends Error {
|
|
34
|
+
constructor(message, options) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "SubformerError";
|
|
37
|
+
this.statusCode = options?.statusCode;
|
|
38
|
+
this.code = options?.code;
|
|
39
|
+
this.data = options?.data;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var AuthenticationError = class extends SubformerError {
|
|
43
|
+
constructor(message = "Invalid or missing API key") {
|
|
44
|
+
super(message, { statusCode: 401, code: "UNAUTHORIZED" });
|
|
45
|
+
this.name = "AuthenticationError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var NotFoundError = class extends SubformerError {
|
|
49
|
+
constructor(message = "Resource not found") {
|
|
50
|
+
super(message, { statusCode: 404, code: "NOT_FOUND" });
|
|
51
|
+
this.name = "NotFoundError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var RateLimitError = class extends SubformerError {
|
|
55
|
+
constructor(message = "Rate limit exceeded") {
|
|
56
|
+
super(message, { statusCode: 429, code: "RATE_LIMIT_EXCEEDED" });
|
|
57
|
+
this.name = "RateLimitError";
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var ValidationError = class extends SubformerError {
|
|
61
|
+
constructor(message, data) {
|
|
62
|
+
super(message, { statusCode: 400, code: "BAD_REQUEST", data });
|
|
63
|
+
this.name = "ValidationError";
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/client.ts
|
|
68
|
+
var DEFAULT_BASE_URL = "https://api.subformer.com/v1";
|
|
69
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
70
|
+
var Subformer = class {
|
|
71
|
+
constructor(options) {
|
|
72
|
+
this.apiKey = options.apiKey;
|
|
73
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
74
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
75
|
+
}
|
|
76
|
+
async request(method, path, options) {
|
|
77
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
78
|
+
if (options?.params) {
|
|
79
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
80
|
+
if (value !== void 0) {
|
|
81
|
+
url.searchParams.set(key, String(value));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(url.toString(), {
|
|
89
|
+
method,
|
|
90
|
+
headers: {
|
|
91
|
+
"x-api-key": this.apiKey,
|
|
92
|
+
"Content-Type": "application/json"
|
|
93
|
+
},
|
|
94
|
+
body: options?.body ? JSON.stringify(options.body) : void 0,
|
|
95
|
+
signal: controller.signal
|
|
96
|
+
});
|
|
97
|
+
clearTimeout(timeoutId);
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
await this.handleError(response);
|
|
100
|
+
}
|
|
101
|
+
if (response.status === 204) {
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
const data = await response.json();
|
|
105
|
+
return this.transformDates(data);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
clearTimeout(timeoutId);
|
|
108
|
+
if (error instanceof SubformerError) {
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
112
|
+
throw new SubformerError("Request timeout");
|
|
113
|
+
}
|
|
114
|
+
throw new SubformerError(
|
|
115
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async handleError(response) {
|
|
120
|
+
let message;
|
|
121
|
+
let code;
|
|
122
|
+
let data;
|
|
123
|
+
try {
|
|
124
|
+
const json = await response.json();
|
|
125
|
+
message = json.message ?? response.statusText;
|
|
126
|
+
code = json.code;
|
|
127
|
+
data = json.data;
|
|
128
|
+
} catch {
|
|
129
|
+
message = response.statusText;
|
|
130
|
+
}
|
|
131
|
+
switch (response.status) {
|
|
132
|
+
case 401:
|
|
133
|
+
throw new AuthenticationError(message);
|
|
134
|
+
case 404:
|
|
135
|
+
throw new NotFoundError(message);
|
|
136
|
+
case 429:
|
|
137
|
+
throw new RateLimitError(message);
|
|
138
|
+
case 400:
|
|
139
|
+
throw new ValidationError(message, data);
|
|
140
|
+
default:
|
|
141
|
+
throw new SubformerError(message, {
|
|
142
|
+
statusCode: response.status,
|
|
143
|
+
code,
|
|
144
|
+
data
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
transformDates(obj) {
|
|
149
|
+
if (obj === null || obj === void 0) {
|
|
150
|
+
return obj;
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(obj)) {
|
|
153
|
+
return obj.map((item) => this.transformDates(item));
|
|
154
|
+
}
|
|
155
|
+
if (typeof obj === "object") {
|
|
156
|
+
const result = {};
|
|
157
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
158
|
+
if (typeof value === "string" && (key.endsWith("At") || key.endsWith("On")) && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
|
|
159
|
+
result[key] = new Date(value);
|
|
160
|
+
} else {
|
|
161
|
+
result[key] = this.transformDates(value);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
return obj;
|
|
167
|
+
}
|
|
168
|
+
// ==================== Dubbing ====================
|
|
169
|
+
/**
|
|
170
|
+
* Create a video dubbing job.
|
|
171
|
+
*
|
|
172
|
+
* @param options - Dubbing options
|
|
173
|
+
* @returns The created job
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const job = await client.dub({
|
|
178
|
+
* source: 'youtube',
|
|
179
|
+
* url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',
|
|
180
|
+
* language: 'es-ES'
|
|
181
|
+
* });
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
async dub(options) {
|
|
185
|
+
const response = await this.request("POST", "/dub", {
|
|
186
|
+
body: {
|
|
187
|
+
type: options.source,
|
|
188
|
+
url: options.url,
|
|
189
|
+
toLanguage: options.language,
|
|
190
|
+
disableWatermark: options.disableWatermark
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return response.job;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get list of supported languages for dubbing.
|
|
197
|
+
*
|
|
198
|
+
* @returns List of language codes
|
|
199
|
+
*/
|
|
200
|
+
async getLanguages() {
|
|
201
|
+
return this.request("GET", "/metadata/dub/languages");
|
|
202
|
+
}
|
|
203
|
+
// ==================== Jobs ====================
|
|
204
|
+
/**
|
|
205
|
+
* Get a job by ID.
|
|
206
|
+
*
|
|
207
|
+
* @param jobId - The job ID
|
|
208
|
+
* @returns The job
|
|
209
|
+
*/
|
|
210
|
+
async getJob(jobId) {
|
|
211
|
+
return this.request("GET", `/jobs/${jobId}`);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* List jobs for the authenticated user.
|
|
215
|
+
*
|
|
216
|
+
* @param options - List options
|
|
217
|
+
* @returns Paginated list of jobs
|
|
218
|
+
*/
|
|
219
|
+
async listJobs(options = {}) {
|
|
220
|
+
return this.request("GET", "/jobs", {
|
|
221
|
+
params: {
|
|
222
|
+
offset: options.offset,
|
|
223
|
+
limit: options.limit,
|
|
224
|
+
type: options.type
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Delete jobs by IDs.
|
|
230
|
+
*
|
|
231
|
+
* @param jobIds - List of job IDs to delete (max 50)
|
|
232
|
+
* @returns True if successful
|
|
233
|
+
*/
|
|
234
|
+
async deleteJobs(jobIds) {
|
|
235
|
+
const response = await this.request(
|
|
236
|
+
"DELETE",
|
|
237
|
+
"/jobs",
|
|
238
|
+
{
|
|
239
|
+
body: { jobIds }
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
return response.success;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Wait for a job to complete.
|
|
246
|
+
*
|
|
247
|
+
* @param jobId - The job ID
|
|
248
|
+
* @param options - Wait options
|
|
249
|
+
* @returns The completed job
|
|
250
|
+
* @throws {Error} If the job doesn't complete within the timeout
|
|
251
|
+
*/
|
|
252
|
+
async waitForJob(jobId, options = {}) {
|
|
253
|
+
const pollInterval = (options.pollInterval ?? 2) * 1e3;
|
|
254
|
+
const timeout = options.timeout ? options.timeout * 1e3 : void 0;
|
|
255
|
+
const startTime = Date.now();
|
|
256
|
+
while (true) {
|
|
257
|
+
const job = await this.getJob(jobId);
|
|
258
|
+
if (job.state === "completed" || job.state === "failed" || job.state === "cancelled") {
|
|
259
|
+
return job;
|
|
260
|
+
}
|
|
261
|
+
if (timeout && Date.now() - startTime > timeout) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`Job ${jobId} did not complete within ${options.timeout} seconds`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// ==================== Voice Cloning ====================
|
|
270
|
+
/**
|
|
271
|
+
* Create a voice cloning job.
|
|
272
|
+
*
|
|
273
|
+
* @param options - Voice cloning options
|
|
274
|
+
* @returns The created job
|
|
275
|
+
*/
|
|
276
|
+
async cloneVoice(options) {
|
|
277
|
+
const response = await this.request("POST", "/voice/clone", {
|
|
278
|
+
body: {
|
|
279
|
+
sourceAudioUrl: options.sourceAudioUrl,
|
|
280
|
+
targetVoice: options.targetVoice.mode === "preset" ? { mode: "preset", presetVoiceId: options.targetVoice.presetVoiceId } : { mode: "upload", targetAudioUrl: options.targetVoice.targetAudioUrl }
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
return response.job;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Create a voice synthesis (text-to-speech) job.
|
|
287
|
+
*
|
|
288
|
+
* @param options - Voice synthesis options
|
|
289
|
+
* @returns The created job
|
|
290
|
+
*/
|
|
291
|
+
async synthesizeVoice(options) {
|
|
292
|
+
const response = await this.request(
|
|
293
|
+
"POST",
|
|
294
|
+
"/voice/synthesize",
|
|
295
|
+
{
|
|
296
|
+
body: {
|
|
297
|
+
text: options.text,
|
|
298
|
+
targetVoice: options.targetVoice.mode === "preset" ? { mode: "preset", presetVoiceId: options.targetVoice.presetVoiceId } : { mode: "upload", targetAudioUrl: options.targetVoice.targetAudioUrl }
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
return response.job;
|
|
303
|
+
}
|
|
304
|
+
// ==================== Voice Library ====================
|
|
305
|
+
/**
|
|
306
|
+
* List all voices in the user's voice library.
|
|
307
|
+
*
|
|
308
|
+
* @returns List of voices
|
|
309
|
+
*/
|
|
310
|
+
async listVoices() {
|
|
311
|
+
return this.request("GET", "/voices");
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Get a voice by ID.
|
|
315
|
+
*
|
|
316
|
+
* @param voiceId - The voice ID
|
|
317
|
+
* @returns The voice
|
|
318
|
+
*/
|
|
319
|
+
async getVoice(voiceId) {
|
|
320
|
+
return this.request("GET", `/voices/${voiceId}`);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Create a new voice in the voice library.
|
|
324
|
+
*
|
|
325
|
+
* @param options - Create voice options
|
|
326
|
+
* @returns The created voice
|
|
327
|
+
*/
|
|
328
|
+
async createVoice(options) {
|
|
329
|
+
return this.request("POST", "/voices", {
|
|
330
|
+
body: {
|
|
331
|
+
name: options.name,
|
|
332
|
+
audioUrl: options.audioUrl,
|
|
333
|
+
gender: options.gender,
|
|
334
|
+
duration: options.duration
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Update a voice in the voice library.
|
|
340
|
+
*
|
|
341
|
+
* @param options - Update voice options
|
|
342
|
+
* @returns The updated voice
|
|
343
|
+
*/
|
|
344
|
+
async updateVoice(options) {
|
|
345
|
+
return this.request("PUT", `/voices/${options.voiceId}`, {
|
|
346
|
+
body: {
|
|
347
|
+
voiceId: options.voiceId,
|
|
348
|
+
name: options.name,
|
|
349
|
+
gender: options.gender
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Delete a voice from the voice library.
|
|
355
|
+
*
|
|
356
|
+
* @param voiceId - The voice ID
|
|
357
|
+
* @returns True if successful
|
|
358
|
+
*/
|
|
359
|
+
async deleteVoice(voiceId) {
|
|
360
|
+
const response = await this.request(
|
|
361
|
+
"DELETE",
|
|
362
|
+
`/voices/${voiceId}`,
|
|
363
|
+
{
|
|
364
|
+
body: { voiceId }
|
|
365
|
+
}
|
|
366
|
+
);
|
|
367
|
+
return response.success;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Generate a presigned URL for uploading voice audio.
|
|
371
|
+
*
|
|
372
|
+
* @param options - Upload URL options
|
|
373
|
+
* @returns Upload URL details
|
|
374
|
+
*/
|
|
375
|
+
async generateVoiceUploadUrl(options) {
|
|
376
|
+
return this.request("POST", "/voices/upload-url", {
|
|
377
|
+
body: {
|
|
378
|
+
fileName: options.fileName,
|
|
379
|
+
contentType: options.contentType
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
// ==================== Billing ====================
|
|
384
|
+
/**
|
|
385
|
+
* Get current billing period usage statistics.
|
|
386
|
+
*
|
|
387
|
+
* @returns Current usage including credits, plan limits, and subscription details
|
|
388
|
+
*/
|
|
389
|
+
async getUsage() {
|
|
390
|
+
return this.request("GET", "/billing/usage");
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Get daily usage statistics for the past 30 days.
|
|
394
|
+
*
|
|
395
|
+
* @returns List of daily usage records grouped by task type
|
|
396
|
+
*/
|
|
397
|
+
async getUsageHistory() {
|
|
398
|
+
return this.request("GET", "/billing/usage-history");
|
|
399
|
+
}
|
|
400
|
+
// ==================== Users ====================
|
|
401
|
+
/**
|
|
402
|
+
* Get the currently authenticated user's profile.
|
|
403
|
+
*
|
|
404
|
+
* @returns User profile information
|
|
405
|
+
*/
|
|
406
|
+
async getMe() {
|
|
407
|
+
return this.request("GET", "/users/me");
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Update the currently authenticated user's profile.
|
|
411
|
+
*
|
|
412
|
+
* @param options - Update user options
|
|
413
|
+
* @returns Updated user profile
|
|
414
|
+
*/
|
|
415
|
+
async updateMe(options) {
|
|
416
|
+
const response = await this.request("PUT", "/users/me", {
|
|
417
|
+
body: {
|
|
418
|
+
name: options.name,
|
|
419
|
+
email: options.email
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
return response.user;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Get the current rate limit status for creating dubbing jobs.
|
|
426
|
+
*
|
|
427
|
+
* @returns Rate limit status including remaining count and limit
|
|
428
|
+
*/
|
|
429
|
+
async getRateLimit() {
|
|
430
|
+
return this.request("GET", "/users/me/rate-limit");
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
434
|
+
0 && (module.exports = {
|
|
435
|
+
AuthenticationError,
|
|
436
|
+
NotFoundError,
|
|
437
|
+
RateLimitError,
|
|
438
|
+
Subformer,
|
|
439
|
+
SubformerError,
|
|
440
|
+
ValidationError
|
|
441
|
+
});
|
|
442
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["/**\n * Subformer TypeScript/JavaScript SDK\n * AI-powered video dubbing and voice cloning\n */\n\nexport { Subformer } from \"./client\";\nexport type { SubformerOptions } from \"./client\";\n\nexport {\n SubformerError,\n AuthenticationError,\n NotFoundError,\n RateLimitError,\n ValidationError,\n} from \"./errors\";\n\nexport type {\n CloneVoiceOptions,\n CreateVoiceOptions,\n DailyUsage,\n DubOptions,\n DubSource,\n GenerateVoiceUploadUrlOptions,\n Job,\n JobMetadata,\n JobProgress,\n JobState,\n JobType,\n Language,\n ListJobsOptions,\n PaginatedJobs,\n PresetVoice,\n RateLimit,\n SynthesizeVoiceOptions,\n TargetVoice,\n UpdateUserOptions,\n UpdateVoiceOptions,\n UploadedVoice,\n UploadUrl,\n Usage,\n UsageData,\n User,\n Voice,\n WaitForJobOptions,\n} from \"./types\";\n","/**\n * Exceptions for Subformer SDK\n */\n\n/** Base error for Subformer SDK */\nexport class SubformerError extends Error {\n readonly statusCode?: number;\n readonly code?: string;\n readonly data?: unknown;\n\n constructor(\n message: string,\n options?: {\n statusCode?: number;\n code?: string;\n data?: unknown;\n }\n ) {\n super(message);\n this.name = \"SubformerError\";\n this.statusCode = options?.statusCode;\n this.code = options?.code;\n this.data = options?.data;\n }\n}\n\n/** Raised when API authentication fails */\nexport class AuthenticationError extends SubformerError {\n constructor(message = \"Invalid or missing API key\") {\n super(message, { statusCode: 401, code: \"UNAUTHORIZED\" });\n this.name = \"AuthenticationError\";\n }\n}\n\n/** Raised when a resource is not found */\nexport class NotFoundError extends SubformerError {\n constructor(message = \"Resource not found\") {\n super(message, { statusCode: 404, code: \"NOT_FOUND\" });\n this.name = \"NotFoundError\";\n }\n}\n\n/** Raised when rate limit is exceeded */\nexport class RateLimitError extends SubformerError {\n constructor(message = \"Rate limit exceeded\") {\n super(message, { statusCode: 429, code: \"RATE_LIMIT_EXCEEDED\" });\n this.name = \"RateLimitError\";\n }\n}\n\n/** Raised when request validation fails */\nexport class ValidationError extends SubformerError {\n constructor(message: string, data?: unknown) {\n super(message, { statusCode: 400, code: \"BAD_REQUEST\", data });\n this.name = \"ValidationError\";\n }\n}\n","/**\n * Subformer API client\n */\n\nimport {\n AuthenticationError,\n NotFoundError,\n RateLimitError,\n SubformerError,\n ValidationError,\n} from \"./errors\";\nimport type {\n CloneVoiceOptions,\n CreateVoiceOptions,\n DailyUsage,\n DubOptions,\n GenerateVoiceUploadUrlOptions,\n Job,\n Language,\n ListJobsOptions,\n PaginatedJobs,\n RateLimit,\n SynthesizeVoiceOptions,\n UpdateUserOptions,\n UpdateVoiceOptions,\n UploadUrl,\n Usage,\n User,\n Voice,\n WaitForJobOptions,\n} from \"./types\";\n\nconst DEFAULT_BASE_URL = \"https://api.subformer.com/v1\";\nconst DEFAULT_TIMEOUT = 30000; // 30 seconds\n\nexport interface SubformerOptions {\n /** Your Subformer API key */\n apiKey: string;\n /** Base URL for the API (default: https://api.subformer.com/v1) */\n baseUrl?: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n}\n\n/**\n * Subformer API client for video dubbing, voice cloning, and text-to-speech.\n *\n * @example\n * ```typescript\n * import { Subformer } from 'subformer';\n *\n * const client = new Subformer({ apiKey: 'sk_subformer_...' });\n *\n * // Create a dubbing job\n * const job = await client.dub({\n * source: 'youtube',\n * url: 'https://youtube.com/watch?v=VIDEO_ID',\n * language: 'es-ES'\n * });\n *\n * // Wait for completion\n * const result = await client.waitForJob(job.id);\n * console.log('Done!', result.output);\n * ```\n */\nexport class Subformer {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(options: SubformerOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n }\n\n private async request<T>(\n method: string,\n path: string,\n options?: {\n body?: unknown;\n params?: Record<string, string | number | boolean | undefined>;\n }\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n\n if (options?.params) {\n for (const [key, value] of Object.entries(options.params)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url.toString(), {\n method,\n headers: {\n \"x-api-key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const data = await response.json();\n return this.transformDates(data) as T;\n } catch (error) {\n clearTimeout(timeoutId);\n if (error instanceof SubformerError) {\n throw error;\n }\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new SubformerError(\"Request timeout\");\n }\n throw new SubformerError(\n error instanceof Error ? error.message : \"Unknown error\"\n );\n }\n }\n\n private async handleError(response: Response): Promise<never> {\n let message: string;\n let code: string | undefined;\n let data: unknown;\n\n try {\n const json = (await response.json()) as Record<string, unknown>;\n message = (json.message as string) ?? response.statusText;\n code = json.code as string | undefined;\n data = json.data;\n } catch {\n message = response.statusText;\n }\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(message);\n case 404:\n throw new NotFoundError(message);\n case 429:\n throw new RateLimitError(message);\n case 400:\n throw new ValidationError(message, data);\n default:\n throw new SubformerError(message, {\n statusCode: response.status,\n code,\n data,\n });\n }\n }\n\n private transformDates(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.transformDates(item));\n }\n\n if (typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (\n typeof value === \"string\" &&\n (key.endsWith(\"At\") || key.endsWith(\"On\")) &&\n /^\\d{4}-\\d{2}-\\d{2}T/.test(value)\n ) {\n result[key] = new Date(value);\n } else {\n result[key] = this.transformDates(value);\n }\n }\n return result;\n }\n\n return obj;\n }\n\n // ==================== Dubbing ====================\n\n /**\n * Create a video dubbing job.\n *\n * @param options - Dubbing options\n * @returns The created job\n *\n * @example\n * ```typescript\n * const job = await client.dub({\n * source: 'youtube',\n * url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',\n * language: 'es-ES'\n * });\n * ```\n */\n async dub(options: DubOptions): Promise<Job> {\n const response = await this.request<{ job: Job }>(\"POST\", \"/dub\", {\n body: {\n type: options.source,\n url: options.url,\n toLanguage: options.language,\n disableWatermark: options.disableWatermark,\n },\n });\n return response.job;\n }\n\n /**\n * Get list of supported languages for dubbing.\n *\n * @returns List of language codes\n */\n async getLanguages(): Promise<Language[]> {\n return this.request<Language[]>(\"GET\", \"/metadata/dub/languages\");\n }\n\n // ==================== Jobs ====================\n\n /**\n * Get a job by ID.\n *\n * @param jobId - The job ID\n * @returns The job\n */\n async getJob(jobId: string): Promise<Job> {\n return this.request<Job>(\"GET\", `/jobs/${jobId}`);\n }\n\n /**\n * List jobs for the authenticated user.\n *\n * @param options - List options\n * @returns Paginated list of jobs\n */\n async listJobs(options: ListJobsOptions = {}): Promise<PaginatedJobs> {\n return this.request<PaginatedJobs>(\"GET\", \"/jobs\", {\n params: {\n offset: options.offset,\n limit: options.limit,\n type: options.type,\n },\n });\n }\n\n /**\n * Delete jobs by IDs.\n *\n * @param jobIds - List of job IDs to delete (max 50)\n * @returns True if successful\n */\n async deleteJobs(jobIds: string[]): Promise<boolean> {\n const response = await this.request<{ success: boolean }>(\n \"DELETE\",\n \"/jobs\",\n {\n body: { jobIds },\n }\n );\n return response.success;\n }\n\n /**\n * Wait for a job to complete.\n *\n * @param jobId - The job ID\n * @param options - Wait options\n * @returns The completed job\n * @throws {Error} If the job doesn't complete within the timeout\n */\n async waitForJob(jobId: string, options: WaitForJobOptions = {}): Promise<Job> {\n const pollInterval = (options.pollInterval ?? 2) * 1000;\n const timeout = options.timeout ? options.timeout * 1000 : undefined;\n const startTime = Date.now();\n\n while (true) {\n const job = await this.getJob(jobId);\n\n if (\n job.state === \"completed\" ||\n job.state === \"failed\" ||\n job.state === \"cancelled\"\n ) {\n return job;\n }\n\n if (timeout && Date.now() - startTime > timeout) {\n throw new Error(\n `Job ${jobId} did not complete within ${options.timeout} seconds`\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, pollInterval));\n }\n }\n\n // ==================== Voice Cloning ====================\n\n /**\n * Create a voice cloning job.\n *\n * @param options - Voice cloning options\n * @returns The created job\n */\n async cloneVoice(options: CloneVoiceOptions): Promise<Job> {\n const response = await this.request<{ job: Job }>(\"POST\", \"/voice/clone\", {\n body: {\n sourceAudioUrl: options.sourceAudioUrl,\n targetVoice:\n options.targetVoice.mode === \"preset\"\n ? { mode: \"preset\", presetVoiceId: options.targetVoice.presetVoiceId }\n : { mode: \"upload\", targetAudioUrl: options.targetVoice.targetAudioUrl },\n },\n });\n return response.job;\n }\n\n /**\n * Create a voice synthesis (text-to-speech) job.\n *\n * @param options - Voice synthesis options\n * @returns The created job\n */\n async synthesizeVoice(options: SynthesizeVoiceOptions): Promise<Job> {\n const response = await this.request<{ job: Job }>(\n \"POST\",\n \"/voice/synthesize\",\n {\n body: {\n text: options.text,\n targetVoice:\n options.targetVoice.mode === \"preset\"\n ? { mode: \"preset\", presetVoiceId: options.targetVoice.presetVoiceId }\n : { mode: \"upload\", targetAudioUrl: options.targetVoice.targetAudioUrl },\n },\n }\n );\n return response.job;\n }\n\n // ==================== Voice Library ====================\n\n /**\n * List all voices in the user's voice library.\n *\n * @returns List of voices\n */\n async listVoices(): Promise<Voice[]> {\n return this.request<Voice[]>(\"GET\", \"/voices\");\n }\n\n /**\n * Get a voice by ID.\n *\n * @param voiceId - The voice ID\n * @returns The voice\n */\n async getVoice(voiceId: string): Promise<Voice> {\n return this.request<Voice>(\"GET\", `/voices/${voiceId}`);\n }\n\n /**\n * Create a new voice in the voice library.\n *\n * @param options - Create voice options\n * @returns The created voice\n */\n async createVoice(options: CreateVoiceOptions): Promise<Voice> {\n return this.request<Voice>(\"POST\", \"/voices\", {\n body: {\n name: options.name,\n audioUrl: options.audioUrl,\n gender: options.gender,\n duration: options.duration,\n },\n });\n }\n\n /**\n * Update a voice in the voice library.\n *\n * @param options - Update voice options\n * @returns The updated voice\n */\n async updateVoice(options: UpdateVoiceOptions): Promise<Voice> {\n return this.request<Voice>(\"PUT\", `/voices/${options.voiceId}`, {\n body: {\n voiceId: options.voiceId,\n name: options.name,\n gender: options.gender,\n },\n });\n }\n\n /**\n * Delete a voice from the voice library.\n *\n * @param voiceId - The voice ID\n * @returns True if successful\n */\n async deleteVoice(voiceId: string): Promise<boolean> {\n const response = await this.request<{ success: boolean }>(\n \"DELETE\",\n `/voices/${voiceId}`,\n {\n body: { voiceId },\n }\n );\n return response.success;\n }\n\n /**\n * Generate a presigned URL for uploading voice audio.\n *\n * @param options - Upload URL options\n * @returns Upload URL details\n */\n async generateVoiceUploadUrl(options: GenerateVoiceUploadUrlOptions): Promise<UploadUrl> {\n return this.request<UploadUrl>(\"POST\", \"/voices/upload-url\", {\n body: {\n fileName: options.fileName,\n contentType: options.contentType,\n },\n });\n }\n\n // ==================== Billing ====================\n\n /**\n * Get current billing period usage statistics.\n *\n * @returns Current usage including credits, plan limits, and subscription details\n */\n async getUsage(): Promise<Usage> {\n return this.request<Usage>(\"GET\", \"/billing/usage\");\n }\n\n /**\n * Get daily usage statistics for the past 30 days.\n *\n * @returns List of daily usage records grouped by task type\n */\n async getUsageHistory(): Promise<DailyUsage[]> {\n return this.request<DailyUsage[]>(\"GET\", \"/billing/usage-history\");\n }\n\n // ==================== Users ====================\n\n /**\n * Get the currently authenticated user's profile.\n *\n * @returns User profile information\n */\n async getMe(): Promise<User> {\n return this.request<User>(\"GET\", \"/users/me\");\n }\n\n /**\n * Update the currently authenticated user's profile.\n *\n * @param options - Update user options\n * @returns Updated user profile\n */\n async updateMe(options: UpdateUserOptions): Promise<User> {\n const response = await this.request<{ user: User }>(\"PUT\", \"/users/me\", {\n body: {\n name: options.name,\n email: options.email,\n },\n });\n return response.user;\n }\n\n /**\n * Get the current rate limit status for creating dubbing jobs.\n *\n * @returns Rate limit status including remaining count and limit\n */\n async getRateLimit(): Promise<RateLimit> {\n return this.request<RateLimit>(\"GET\", \"/users/me/rate-limit\");\n }\n}\n"],"mappings":"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","names":[]}
|