audiopod 2.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 +189 -0
- package/dist/index.d.mts +780 -0
- package/dist/index.d.ts +780 -0
- package/dist/index.js +956 -0
- package/dist/index.mjs +913 -0
- package/package.json +63 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import FormData from "form-data";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var AudioPodError = class _AudioPodError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "AudioPodError";
|
|
12
|
+
Object.setPrototypeOf(this, _AudioPodError.prototype);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var AuthenticationError = class _AuthenticationError extends AudioPodError {
|
|
16
|
+
constructor(message = "Authentication failed") {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "AuthenticationError";
|
|
19
|
+
Object.setPrototypeOf(this, _AuthenticationError.prototype);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var APIError = class _APIError extends AudioPodError {
|
|
23
|
+
constructor(message, statusCode) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "APIError";
|
|
26
|
+
this.statusCode = statusCode;
|
|
27
|
+
Object.setPrototypeOf(this, _APIError.prototype);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var RateLimitError = class _RateLimitError extends AudioPodError {
|
|
31
|
+
constructor(message = "Rate limit exceeded") {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "RateLimitError";
|
|
34
|
+
Object.setPrototypeOf(this, _RateLimitError.prototype);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var InsufficientBalanceError = class _InsufficientBalanceError extends AudioPodError {
|
|
38
|
+
constructor(message = "Insufficient wallet balance", requiredCents, availableCents) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "InsufficientBalanceError";
|
|
41
|
+
this.requiredCents = requiredCents;
|
|
42
|
+
this.availableCents = availableCents;
|
|
43
|
+
Object.setPrototypeOf(this, _InsufficientBalanceError.prototype);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/resources/transcription.ts
|
|
48
|
+
var POLL_INTERVAL = 3e3;
|
|
49
|
+
var DEFAULT_TIMEOUT = 6e5;
|
|
50
|
+
var Transcription = class {
|
|
51
|
+
constructor(client) {
|
|
52
|
+
this.client = client;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create a transcription job from URL(s)
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const job = await client.transcription.create({
|
|
60
|
+
* url: 'https://youtube.com/watch?v=...',
|
|
61
|
+
* speakerDiarization: true,
|
|
62
|
+
* });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
async create(params) {
|
|
66
|
+
const urls = params.urls || (params.url ? [params.url] : []);
|
|
67
|
+
if (urls.length === 0) {
|
|
68
|
+
throw new Error("At least one URL is required");
|
|
69
|
+
}
|
|
70
|
+
return this.client.post("/api/v1/transcribe/transcribe", {
|
|
71
|
+
source_urls: urls,
|
|
72
|
+
language: params.language,
|
|
73
|
+
enable_speaker_diarization: params.speakerDiarization ?? false,
|
|
74
|
+
min_speakers: params.minSpeakers,
|
|
75
|
+
max_speakers: params.maxSpeakers,
|
|
76
|
+
enable_word_timestamps: params.wordTimestamps ?? true
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Upload a file for transcription
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const job = await client.transcription.upload({
|
|
85
|
+
* file: './audio.mp3',
|
|
86
|
+
* language: 'en',
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
async upload(params) {
|
|
91
|
+
return this.client.upload("/api/v1/transcribe/transcribe-upload", params.file, "files", {
|
|
92
|
+
language: params.language,
|
|
93
|
+
enable_speaker_diarization: params.speakerDiarization ?? false
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get a transcription job by ID
|
|
98
|
+
*/
|
|
99
|
+
async get(jobId) {
|
|
100
|
+
return this.client.get(`/api/v1/transcribe/jobs/${jobId}`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* List transcription jobs
|
|
104
|
+
*/
|
|
105
|
+
async list(params = {}) {
|
|
106
|
+
return this.client.get("/api/v1/transcribe/jobs", {
|
|
107
|
+
offset: params.skip ?? 0,
|
|
108
|
+
limit: params.limit ?? 50,
|
|
109
|
+
status: params.status
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Delete a transcription job
|
|
114
|
+
*/
|
|
115
|
+
async delete(jobId) {
|
|
116
|
+
await this.client.delete(`/api/v1/transcribe/jobs/${jobId}`);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get transcript content
|
|
120
|
+
*
|
|
121
|
+
* @param format - Output format: 'json', 'txt', 'srt', 'vtt'
|
|
122
|
+
*/
|
|
123
|
+
async getTranscript(jobId, format = "json") {
|
|
124
|
+
return this.client.get(`/api/v1/transcribe/jobs/${jobId}/transcript`, { format });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Wait for a transcription job to complete
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* const job = await client.transcription.create({ url: '...' });
|
|
132
|
+
* const result = await client.transcription.waitForCompletion(job.id);
|
|
133
|
+
* console.log(result.status); // 'COMPLETED'
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
async waitForCompletion(jobId, timeout = DEFAULT_TIMEOUT) {
|
|
137
|
+
const startTime = Date.now();
|
|
138
|
+
while (Date.now() - startTime < timeout) {
|
|
139
|
+
const job = await this.get(jobId);
|
|
140
|
+
if (job.status === "COMPLETED") {
|
|
141
|
+
return job;
|
|
142
|
+
}
|
|
143
|
+
if (job.status === "FAILED") {
|
|
144
|
+
throw new Error(`Transcription failed: ${job.error_message || "Unknown error"}`);
|
|
145
|
+
}
|
|
146
|
+
if (job.status === "CANCELLED") {
|
|
147
|
+
throw new Error("Transcription was cancelled");
|
|
148
|
+
}
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
150
|
+
}
|
|
151
|
+
throw new Error(`Transcription timed out after ${timeout / 1e3}s`);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Create and wait for completion in one call
|
|
155
|
+
*/
|
|
156
|
+
async transcribe(params) {
|
|
157
|
+
const job = await this.create(params);
|
|
158
|
+
return this.waitForCompletion(job.id || job.job_id);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/resources/voice.ts
|
|
163
|
+
var POLL_INTERVAL2 = 2e3;
|
|
164
|
+
var DEFAULT_TIMEOUT2 = 3e5;
|
|
165
|
+
var Voice = class {
|
|
166
|
+
constructor(client) {
|
|
167
|
+
this.client = client;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* List all voices
|
|
171
|
+
*/
|
|
172
|
+
async list() {
|
|
173
|
+
return this.client.get("/api/v1/voice/voices");
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get a voice by ID
|
|
177
|
+
*/
|
|
178
|
+
async get(voiceId) {
|
|
179
|
+
return this.client.get(`/api/v1/voice/voices/${voiceId}`);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Create a new voice clone
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* const voice = await client.voice.create({
|
|
187
|
+
* name: 'My Voice',
|
|
188
|
+
* audioFile: './sample.mp3',
|
|
189
|
+
* });
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
async create(params) {
|
|
193
|
+
return this.client.upload("/api/v1/voice/voices", params.audioFile, "audio_file", {
|
|
194
|
+
name: params.name,
|
|
195
|
+
description: params.description
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Delete a voice
|
|
200
|
+
*/
|
|
201
|
+
async delete(voiceId) {
|
|
202
|
+
await this.client.delete(`/api/v1/voice/voices/${voiceId}`);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Generate speech from text
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* const job = await client.voice.speak({
|
|
210
|
+
* text: 'Hello, world!',
|
|
211
|
+
* voiceId: 123,
|
|
212
|
+
* });
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
async speak(params) {
|
|
216
|
+
return this.client.post("/api/v1/voice/tts/generate", {
|
|
217
|
+
text: params.text,
|
|
218
|
+
voice_id: params.voiceId,
|
|
219
|
+
speed: params.speed ?? 1
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get TTS job status
|
|
224
|
+
*/
|
|
225
|
+
async getJob(jobId) {
|
|
226
|
+
return this.client.get(`/api/v1/voice/tts/status/${jobId}`);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Wait for TTS job to complete
|
|
230
|
+
*/
|
|
231
|
+
async waitForCompletion(jobId, timeout = DEFAULT_TIMEOUT2) {
|
|
232
|
+
const startTime = Date.now();
|
|
233
|
+
while (Date.now() - startTime < timeout) {
|
|
234
|
+
const job = await this.getJob(jobId);
|
|
235
|
+
if (job.status === "COMPLETED") {
|
|
236
|
+
return job;
|
|
237
|
+
}
|
|
238
|
+
if (job.status === "FAILED") {
|
|
239
|
+
throw new Error(`TTS generation failed: ${job.error_message || "Unknown error"}`);
|
|
240
|
+
}
|
|
241
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL2));
|
|
242
|
+
}
|
|
243
|
+
throw new Error(`TTS generation timed out after ${timeout / 1e3}s`);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Generate speech and wait for completion
|
|
247
|
+
*/
|
|
248
|
+
async generate(params) {
|
|
249
|
+
const job = await this.speak(params);
|
|
250
|
+
return this.waitForCompletion(job.id);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/resources/music.ts
|
|
255
|
+
var POLL_INTERVAL3 = 5e3;
|
|
256
|
+
var DEFAULT_TIMEOUT3 = 6e5;
|
|
257
|
+
var Music = class {
|
|
258
|
+
constructor(client) {
|
|
259
|
+
this.client = client;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Generate music from text
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* const job = await client.music.create({
|
|
267
|
+
* prompt: 'Upbeat electronic dance music',
|
|
268
|
+
* duration: 30,
|
|
269
|
+
* });
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
async create(params) {
|
|
273
|
+
const task = params.task || (params.lyrics ? "text2music" : "prompt2instrumental");
|
|
274
|
+
const endpoint = `/api/v1/music/${task}`;
|
|
275
|
+
return this.client.post(endpoint, {
|
|
276
|
+
prompt: params.prompt,
|
|
277
|
+
lyrics: params.lyrics,
|
|
278
|
+
audio_duration: params.duration ?? 30,
|
|
279
|
+
display_name: params.displayName,
|
|
280
|
+
genre_preset: params.genre
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Generate instrumental music
|
|
285
|
+
*/
|
|
286
|
+
async instrumental(prompt, duration = 30) {
|
|
287
|
+
return this.create({ prompt, duration, task: "prompt2instrumental" });
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Generate a song with vocals
|
|
291
|
+
*/
|
|
292
|
+
async song(prompt, lyrics, duration = 60) {
|
|
293
|
+
return this.create({ prompt, lyrics, duration, task: "text2music" });
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Generate rap music
|
|
297
|
+
*/
|
|
298
|
+
async rap(prompt, lyrics, duration = 60) {
|
|
299
|
+
return this.create({ prompt, lyrics, duration, task: "text2rap" });
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get a music job by ID
|
|
303
|
+
*/
|
|
304
|
+
async get(jobId) {
|
|
305
|
+
return this.client.get(`/api/v1/music/jobs/${jobId}/status`);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* List music jobs
|
|
309
|
+
*/
|
|
310
|
+
async list(params = {}) {
|
|
311
|
+
return this.client.get("/api/v1/music/jobs", {
|
|
312
|
+
skip: params.skip ?? 0,
|
|
313
|
+
limit: params.limit ?? 50,
|
|
314
|
+
task: params.task,
|
|
315
|
+
status: params.status
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Delete a music job
|
|
320
|
+
*/
|
|
321
|
+
async delete(jobId) {
|
|
322
|
+
await this.client.delete(`/api/v1/music/jobs/${jobId}`);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Wait for a music job to complete
|
|
326
|
+
*/
|
|
327
|
+
async waitForCompletion(jobId, timeout = DEFAULT_TIMEOUT3) {
|
|
328
|
+
const startTime = Date.now();
|
|
329
|
+
while (Date.now() - startTime < timeout) {
|
|
330
|
+
const job = await this.get(jobId);
|
|
331
|
+
if (job.status === "COMPLETED") {
|
|
332
|
+
return job;
|
|
333
|
+
}
|
|
334
|
+
if (job.status === "FAILED") {
|
|
335
|
+
throw new Error(`Music generation failed: ${job.error_message || "Unknown error"}`);
|
|
336
|
+
}
|
|
337
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL3));
|
|
338
|
+
}
|
|
339
|
+
throw new Error(`Music generation timed out after ${timeout / 1e3}s`);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Generate music and wait for completion
|
|
343
|
+
*/
|
|
344
|
+
async generate(params) {
|
|
345
|
+
const job = await this.create(params);
|
|
346
|
+
return this.waitForCompletion(job.id);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get available genre presets
|
|
350
|
+
*/
|
|
351
|
+
async getPresets() {
|
|
352
|
+
return this.client.get("/api/v1/music/presets");
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// src/resources/stems.ts
|
|
357
|
+
var POLL_INTERVAL4 = 3e3;
|
|
358
|
+
var DEFAULT_TIMEOUT4 = 6e5;
|
|
359
|
+
var StemExtraction = class {
|
|
360
|
+
constructor(client) {
|
|
361
|
+
this.client = client;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Extract stems from audio using simple mode selection.
|
|
365
|
+
*
|
|
366
|
+
* @param params.file - Path to local audio file (MP3, WAV, FLAC, M4A, OGG)
|
|
367
|
+
* @param params.url - URL of audio/video (YouTube, SoundCloud, direct link)
|
|
368
|
+
* @param params.mode - Separation mode:
|
|
369
|
+
* - "single": Extract one stem (specify stem param)
|
|
370
|
+
* - "two": Vocals + Instrumental
|
|
371
|
+
* - "four": Vocals, Drums, Bass, Other (default)
|
|
372
|
+
* - "six": Vocals, Drums, Bass, Guitar, Piano, Other
|
|
373
|
+
* - "producer": 8 stems with kick, snare, hihat
|
|
374
|
+
* - "studio": 12 stems for professional mixing
|
|
375
|
+
* - "mastering": 16 stems maximum detail
|
|
376
|
+
* @param params.stem - For mode="single", which stem to extract
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```typescript
|
|
380
|
+
* // From URL with 6 stems
|
|
381
|
+
* const job = await client.stems.extract({
|
|
382
|
+
* url: 'https://youtube.com/watch?v=VIDEO_ID',
|
|
383
|
+
* mode: 'six'
|
|
384
|
+
* });
|
|
385
|
+
*
|
|
386
|
+
* // From file with 4 stems
|
|
387
|
+
* const job = await client.stems.extract({
|
|
388
|
+
* file: './song.mp3',
|
|
389
|
+
* mode: 'four'
|
|
390
|
+
* });
|
|
391
|
+
*
|
|
392
|
+
* // Extract only vocals
|
|
393
|
+
* const job = await client.stems.extract({
|
|
394
|
+
* url: 'https://youtube.com/watch?v=VIDEO_ID',
|
|
395
|
+
* mode: 'single',
|
|
396
|
+
* stem: 'vocals'
|
|
397
|
+
* });
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
async extract(params) {
|
|
401
|
+
const mode = params.mode || "four";
|
|
402
|
+
if (!params.file && !params.url) {
|
|
403
|
+
throw new Error("Either file or url must be provided");
|
|
404
|
+
}
|
|
405
|
+
if (mode === "single" && !params.stem) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
"stem parameter required for mode='single'. Options: vocals, drums, bass, guitar, piano, other"
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
const additionalFields = { mode };
|
|
411
|
+
if (params.stem) {
|
|
412
|
+
additionalFields.stem = params.stem;
|
|
413
|
+
}
|
|
414
|
+
if (params.file) {
|
|
415
|
+
return this.client.upload(
|
|
416
|
+
"/api/v1/stem-extraction/api/extract",
|
|
417
|
+
params.file,
|
|
418
|
+
"file",
|
|
419
|
+
additionalFields
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (params.url) {
|
|
423
|
+
additionalFields.url = params.url;
|
|
424
|
+
return this.client.postForm(
|
|
425
|
+
"/api/v1/stem-extraction/api/extract",
|
|
426
|
+
additionalFields
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
throw new Error("Either file or url must be provided");
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get the status of a stem extraction job.
|
|
433
|
+
*
|
|
434
|
+
* @param jobId - The job ID returned from extract()
|
|
435
|
+
* @returns Job status with download_urls when completed
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```typescript
|
|
439
|
+
* const status = await client.stems.status(5512);
|
|
440
|
+
* if (status.status === 'COMPLETED') {
|
|
441
|
+
* console.log(status.download_urls);
|
|
442
|
+
* }
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
async status(jobId) {
|
|
446
|
+
return this.client.get(`/api/v1/stem-extraction/status/${jobId}`);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Get a stem extraction job by ID
|
|
450
|
+
*/
|
|
451
|
+
async get(jobId) {
|
|
452
|
+
return this.client.get(`/api/v1/stem-extraction/jobs/${jobId}`);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* List stem extraction jobs
|
|
456
|
+
*
|
|
457
|
+
* @param params.skip - Number of jobs to skip (pagination)
|
|
458
|
+
* @param params.limit - Maximum jobs to return (default: 50)
|
|
459
|
+
* @param params.status - Filter by status
|
|
460
|
+
*/
|
|
461
|
+
async list(params = {}) {
|
|
462
|
+
return this.client.get("/api/v1/stem-extraction/jobs", {
|
|
463
|
+
skip: params.skip ?? 0,
|
|
464
|
+
limit: params.limit ?? 50,
|
|
465
|
+
status: params.status
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Delete a stem extraction job
|
|
470
|
+
*/
|
|
471
|
+
async delete(jobId) {
|
|
472
|
+
await this.client.delete(`/api/v1/stem-extraction/jobs/${jobId}`);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Wait for stem extraction to complete.
|
|
476
|
+
*
|
|
477
|
+
* @param jobId - The job ID to wait for
|
|
478
|
+
* @param timeout - Maximum wait time in milliseconds (default: 600000)
|
|
479
|
+
* @returns Completed job with download_urls
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```typescript
|
|
483
|
+
* const job = await client.stems.extract({ url: '...', mode: 'six' });
|
|
484
|
+
* const result = await client.stems.waitForCompletion(job.id);
|
|
485
|
+
*
|
|
486
|
+
* for (const [stem, url] of Object.entries(result.download_urls)) {
|
|
487
|
+
* console.log(`${stem}: ${url}`);
|
|
488
|
+
* }
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
async waitForCompletion(jobId, timeout = DEFAULT_TIMEOUT4) {
|
|
492
|
+
const startTime = Date.now();
|
|
493
|
+
while (Date.now() - startTime < timeout) {
|
|
494
|
+
const job = await this.status(jobId);
|
|
495
|
+
if (job.status === "COMPLETED") {
|
|
496
|
+
return job;
|
|
497
|
+
}
|
|
498
|
+
if (job.status === "FAILED") {
|
|
499
|
+
throw new Error(
|
|
500
|
+
`Stem extraction failed: ${job.error_message || "Unknown error"}`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL4));
|
|
504
|
+
}
|
|
505
|
+
throw new Error(`Stem extraction timed out after ${timeout / 1e3}s`);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Extract stems and wait for completion (convenience method).
|
|
509
|
+
*
|
|
510
|
+
* @example
|
|
511
|
+
* ```typescript
|
|
512
|
+
* // One-liner: extract and wait
|
|
513
|
+
* const result = await client.stems.separate({
|
|
514
|
+
* url: 'https://youtube.com/watch?v=VIDEO_ID',
|
|
515
|
+
* mode: 'six'
|
|
516
|
+
* });
|
|
517
|
+
* console.log(result.download_urls.vocals);
|
|
518
|
+
* ```
|
|
519
|
+
*/
|
|
520
|
+
async separate(params, timeout = DEFAULT_TIMEOUT4) {
|
|
521
|
+
const job = await this.extract(params);
|
|
522
|
+
return this.waitForCompletion(job.id, timeout);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Get available separation modes.
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* ```typescript
|
|
529
|
+
* const modes = await client.stems.modes();
|
|
530
|
+
* modes.modes.forEach(m => {
|
|
531
|
+
* console.log(`${m.mode}: ${m.description}`);
|
|
532
|
+
* });
|
|
533
|
+
* ```
|
|
534
|
+
*/
|
|
535
|
+
async modes() {
|
|
536
|
+
return this.client.get("/api/v1/stem-extraction/modes");
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// src/resources/denoiser.ts
|
|
541
|
+
var POLL_INTERVAL5 = 3e3;
|
|
542
|
+
var DEFAULT_TIMEOUT5 = 6e5;
|
|
543
|
+
var Denoiser = class {
|
|
544
|
+
constructor(client) {
|
|
545
|
+
this.client = client;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Denoise audio/video
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* ```typescript
|
|
552
|
+
* // Denoise from file
|
|
553
|
+
* const job = await client.denoiser.create({
|
|
554
|
+
* file: './noisy-audio.mp3',
|
|
555
|
+
* });
|
|
556
|
+
*
|
|
557
|
+
* // Denoise from URL
|
|
558
|
+
* const job = await client.denoiser.create({
|
|
559
|
+
* url: 'https://example.com/audio.mp3',
|
|
560
|
+
* });
|
|
561
|
+
* ```
|
|
562
|
+
*/
|
|
563
|
+
async create(params) {
|
|
564
|
+
if (params.file) {
|
|
565
|
+
return this.client.upload("/api/v1/denoiser/denoise", params.file, "file");
|
|
566
|
+
}
|
|
567
|
+
if (params.url) {
|
|
568
|
+
return this.client.post("/api/v1/denoiser/denoise", {
|
|
569
|
+
url: params.url
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
throw new Error("Either file or url must be provided");
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Get a denoise job by ID
|
|
576
|
+
*/
|
|
577
|
+
async get(jobId) {
|
|
578
|
+
return this.client.get(`/api/v1/denoiser/jobs/${jobId}`);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* List denoise jobs
|
|
582
|
+
*/
|
|
583
|
+
async list(params = {}) {
|
|
584
|
+
return this.client.get("/api/v1/denoiser/jobs", {
|
|
585
|
+
skip: params.skip ?? 0,
|
|
586
|
+
limit: params.limit ?? 50,
|
|
587
|
+
status: params.status
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Delete a denoise job
|
|
592
|
+
*/
|
|
593
|
+
async delete(jobId) {
|
|
594
|
+
await this.client.delete(`/api/v1/denoiser/jobs/${jobId}`);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Wait for denoising to complete
|
|
598
|
+
*/
|
|
599
|
+
async waitForCompletion(jobId, timeout = DEFAULT_TIMEOUT5) {
|
|
600
|
+
const startTime = Date.now();
|
|
601
|
+
while (Date.now() - startTime < timeout) {
|
|
602
|
+
const job = await this.get(jobId);
|
|
603
|
+
if (job.status === "COMPLETED") {
|
|
604
|
+
return job;
|
|
605
|
+
}
|
|
606
|
+
if (job.status === "FAILED") {
|
|
607
|
+
throw new Error(`Denoising failed: ${job.error_message || "Unknown error"}`);
|
|
608
|
+
}
|
|
609
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL5));
|
|
610
|
+
}
|
|
611
|
+
throw new Error(`Denoising timed out after ${timeout / 1e3}s`);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Denoise and wait for completion
|
|
615
|
+
*/
|
|
616
|
+
async denoise(params) {
|
|
617
|
+
const job = await this.create(params);
|
|
618
|
+
return this.waitForCompletion(job.id);
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// src/resources/speaker.ts
|
|
623
|
+
var POLL_INTERVAL6 = 3e3;
|
|
624
|
+
var DEFAULT_TIMEOUT6 = 6e5;
|
|
625
|
+
var Speaker = class {
|
|
626
|
+
constructor(client) {
|
|
627
|
+
this.client = client;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Create a speaker diarization job
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```typescript
|
|
634
|
+
* // Diarize from file
|
|
635
|
+
* const job = await client.speaker.diarize({
|
|
636
|
+
* file: './meeting.mp3',
|
|
637
|
+
* numSpeakers: 3,
|
|
638
|
+
* });
|
|
639
|
+
*
|
|
640
|
+
* // Diarize from URL
|
|
641
|
+
* const job = await client.speaker.diarize({
|
|
642
|
+
* url: 'https://youtube.com/watch?v=...',
|
|
643
|
+
* });
|
|
644
|
+
* ```
|
|
645
|
+
*/
|
|
646
|
+
async diarize(params) {
|
|
647
|
+
if (params.file) {
|
|
648
|
+
return this.client.upload("/api/v1/speaker/diarize", params.file, "file", {
|
|
649
|
+
num_speakers: params.numSpeakers
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
if (params.url) {
|
|
653
|
+
return this.client.post("/api/v1/speaker/diarize", {
|
|
654
|
+
url: params.url,
|
|
655
|
+
num_speakers: params.numSpeakers
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
throw new Error("Either file or url must be provided");
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get a speaker job by ID
|
|
662
|
+
*/
|
|
663
|
+
async get(jobId) {
|
|
664
|
+
return this.client.get(`/api/v1/speaker/jobs/${jobId}`);
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* List speaker jobs
|
|
668
|
+
*/
|
|
669
|
+
async list(params = {}) {
|
|
670
|
+
return this.client.get("/api/v1/speaker/jobs", {
|
|
671
|
+
skip: params.skip ?? 0,
|
|
672
|
+
limit: params.limit ?? 50,
|
|
673
|
+
status: params.status,
|
|
674
|
+
job_type: params.jobType
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Delete a speaker job
|
|
679
|
+
*/
|
|
680
|
+
async delete(jobId) {
|
|
681
|
+
await this.client.delete(`/api/v1/speaker/jobs/${jobId}`);
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Wait for speaker job to complete
|
|
685
|
+
*/
|
|
686
|
+
async waitForCompletion(jobId, timeout = DEFAULT_TIMEOUT6) {
|
|
687
|
+
const startTime = Date.now();
|
|
688
|
+
while (Date.now() - startTime < timeout) {
|
|
689
|
+
const job = await this.get(jobId);
|
|
690
|
+
if (job.status === "COMPLETED") {
|
|
691
|
+
return job;
|
|
692
|
+
}
|
|
693
|
+
if (job.status === "FAILED") {
|
|
694
|
+
throw new Error(`Speaker processing failed: ${job.error_message || "Unknown error"}`);
|
|
695
|
+
}
|
|
696
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL6));
|
|
697
|
+
}
|
|
698
|
+
throw new Error(`Speaker processing timed out after ${timeout / 1e3}s`);
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Diarize and wait for completion
|
|
702
|
+
*/
|
|
703
|
+
async identify(params) {
|
|
704
|
+
const job = await this.diarize(params);
|
|
705
|
+
return this.waitForCompletion(job.id);
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// src/resources/wallet.ts
|
|
710
|
+
var Wallet = class {
|
|
711
|
+
constructor(client) {
|
|
712
|
+
this.client = client;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Get current wallet balance
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* ```typescript
|
|
719
|
+
* const balance = await client.wallet.getBalance();
|
|
720
|
+
* console.log(`Balance: ${balance.balance_usd}`);
|
|
721
|
+
* ```
|
|
722
|
+
*/
|
|
723
|
+
async getBalance() {
|
|
724
|
+
return this.client.get("/api/v1/api-wallet/balance");
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Get usage history
|
|
728
|
+
*/
|
|
729
|
+
async getUsage(params = {}) {
|
|
730
|
+
return this.client.get("/api/v1/api-wallet/usage", {
|
|
731
|
+
page: params.page ?? 1,
|
|
732
|
+
limit: params.limit ?? 50,
|
|
733
|
+
api_key_id: params.apiKeyId
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get pricing information for all services
|
|
738
|
+
*/
|
|
739
|
+
async getPricing() {
|
|
740
|
+
return this.client.get("/api/v1/api-wallet/pricing");
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Estimate cost for an operation
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```typescript
|
|
747
|
+
* const estimate = await client.wallet.estimateCost({
|
|
748
|
+
* serviceType: 'stem_extraction',
|
|
749
|
+
* durationSeconds: 300,
|
|
750
|
+
* });
|
|
751
|
+
* console.log(`Estimated cost: ${estimate.cost_usd}`);
|
|
752
|
+
* ```
|
|
753
|
+
*/
|
|
754
|
+
async estimateCost(params) {
|
|
755
|
+
return this.client.post("/api/v1/api-wallet/estimate", {
|
|
756
|
+
service_type: params.serviceType,
|
|
757
|
+
duration_seconds: params.durationSeconds
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Check if balance is sufficient for an operation
|
|
762
|
+
*/
|
|
763
|
+
async checkBalance(params) {
|
|
764
|
+
return this.client.post("/api/v1/api-wallet/check-balance", {
|
|
765
|
+
service_type: params.serviceType,
|
|
766
|
+
duration_seconds: params.durationSeconds
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// src/client.ts
|
|
772
|
+
var VERSION = "2.1.0";
|
|
773
|
+
var DEFAULT_BASE_URL = "https://api.audiopod.ai";
|
|
774
|
+
var DEFAULT_TIMEOUT7 = 6e4;
|
|
775
|
+
var AudioPod = class {
|
|
776
|
+
constructor(options = {}) {
|
|
777
|
+
this._apiKey = options.apiKey || process.env.AUDIOPOD_API_KEY || "";
|
|
778
|
+
if (!this._apiKey) {
|
|
779
|
+
throw new AuthenticationError(
|
|
780
|
+
"API key is required. Pass apiKey option or set AUDIOPOD_API_KEY environment variable."
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
if (!this._apiKey.startsWith("ap_")) {
|
|
784
|
+
throw new AuthenticationError(
|
|
785
|
+
"Invalid API key format. AudioPod API keys start with 'ap_'"
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
this._axios = axios.create({
|
|
789
|
+
baseURL: options.baseUrl || DEFAULT_BASE_URL,
|
|
790
|
+
timeout: options.timeout || DEFAULT_TIMEOUT7,
|
|
791
|
+
headers: {
|
|
792
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
793
|
+
"X-API-Key": this._apiKey,
|
|
794
|
+
"Content-Type": "application/json",
|
|
795
|
+
"User-Agent": `audiopod-node/${VERSION}`,
|
|
796
|
+
Accept: "application/json"
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
this._axios.interceptors.response.use(
|
|
800
|
+
(response) => response,
|
|
801
|
+
(error) => this._handleError(error)
|
|
802
|
+
);
|
|
803
|
+
this.transcription = new Transcription(this);
|
|
804
|
+
this.voice = new Voice(this);
|
|
805
|
+
this.music = new Music(this);
|
|
806
|
+
this.stems = new StemExtraction(this);
|
|
807
|
+
this.denoiser = new Denoiser(this);
|
|
808
|
+
this.speaker = new Speaker(this);
|
|
809
|
+
this.wallet = new Wallet(this);
|
|
810
|
+
}
|
|
811
|
+
_handleError(error) {
|
|
812
|
+
if (error.response) {
|
|
813
|
+
const status = error.response.status;
|
|
814
|
+
const data = error.response.data;
|
|
815
|
+
const message = data?.detail || data?.message || error.message;
|
|
816
|
+
switch (status) {
|
|
817
|
+
case 401:
|
|
818
|
+
throw new AuthenticationError(message || "Invalid API key");
|
|
819
|
+
case 402:
|
|
820
|
+
throw new InsufficientBalanceError(
|
|
821
|
+
message || "Insufficient wallet balance",
|
|
822
|
+
data?.required_cents,
|
|
823
|
+
data?.available_cents
|
|
824
|
+
);
|
|
825
|
+
case 429:
|
|
826
|
+
throw new RateLimitError(message || "Rate limit exceeded");
|
|
827
|
+
default:
|
|
828
|
+
throw new APIError(message, status);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
throw new APIError(error.message);
|
|
832
|
+
}
|
|
833
|
+
/** @internal Make a GET request */
|
|
834
|
+
async get(endpoint, params) {
|
|
835
|
+
const response = await this._axios.get(endpoint, { params });
|
|
836
|
+
return response.data;
|
|
837
|
+
}
|
|
838
|
+
/** @internal Make a POST request */
|
|
839
|
+
async post(endpoint, data) {
|
|
840
|
+
const response = await this._axios.post(endpoint, data);
|
|
841
|
+
return response.data;
|
|
842
|
+
}
|
|
843
|
+
/** @internal Make a POST request with form data (non-file) */
|
|
844
|
+
async postForm(endpoint, data) {
|
|
845
|
+
const formData = new FormData();
|
|
846
|
+
for (const [key, value] of Object.entries(data)) {
|
|
847
|
+
if (value !== void 0 && value !== null) {
|
|
848
|
+
formData.append(key, String(value));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
const response = await this._axios.post(endpoint, formData, {
|
|
852
|
+
headers: formData.getHeaders()
|
|
853
|
+
});
|
|
854
|
+
return response.data;
|
|
855
|
+
}
|
|
856
|
+
/** @internal Make a DELETE request */
|
|
857
|
+
async delete(endpoint) {
|
|
858
|
+
const response = await this._axios.delete(endpoint);
|
|
859
|
+
return response.data;
|
|
860
|
+
}
|
|
861
|
+
/** @internal Upload a file */
|
|
862
|
+
async upload(endpoint, filePath, fieldName = "file", additionalFields) {
|
|
863
|
+
const formData = new FormData();
|
|
864
|
+
const filename = path.basename(filePath);
|
|
865
|
+
formData.append(fieldName, fs.createReadStream(filePath), filename);
|
|
866
|
+
if (additionalFields) {
|
|
867
|
+
for (const [key, value] of Object.entries(additionalFields)) {
|
|
868
|
+
if (value !== void 0 && value !== null) {
|
|
869
|
+
formData.append(
|
|
870
|
+
key,
|
|
871
|
+
typeof value === "object" ? JSON.stringify(value) : String(value)
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
const response = await this._axios.post(endpoint, formData, {
|
|
877
|
+
headers: formData.getHeaders()
|
|
878
|
+
});
|
|
879
|
+
return response.data;
|
|
880
|
+
}
|
|
881
|
+
/** @internal Upload with buffer */
|
|
882
|
+
async uploadBuffer(endpoint, buffer, filename, fieldName = "file", additionalFields) {
|
|
883
|
+
const formData = new FormData();
|
|
884
|
+
formData.append(fieldName, buffer, { filename });
|
|
885
|
+
if (additionalFields) {
|
|
886
|
+
for (const [key, value] of Object.entries(additionalFields)) {
|
|
887
|
+
if (value !== void 0 && value !== null) {
|
|
888
|
+
formData.append(
|
|
889
|
+
key,
|
|
890
|
+
typeof value === "object" ? JSON.stringify(value) : String(value)
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const response = await this._axios.post(endpoint, formData, {
|
|
896
|
+
headers: formData.getHeaders()
|
|
897
|
+
});
|
|
898
|
+
return response.data;
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
// src/index.ts
|
|
903
|
+
var VERSION2 = "2.1.0";
|
|
904
|
+
export {
|
|
905
|
+
APIError,
|
|
906
|
+
AudioPod,
|
|
907
|
+
AudioPodError,
|
|
908
|
+
AuthenticationError,
|
|
909
|
+
InsufficientBalanceError,
|
|
910
|
+
RateLimitError,
|
|
911
|
+
VERSION2 as VERSION,
|
|
912
|
+
AudioPod as default
|
|
913
|
+
};
|