@poof-bg/js 1.1.0 → 1.2.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 +82 -1
- package/dist/index.cjs +168 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +143 -20
- package/dist/index.d.ts +143 -20
- package/dist/index.js +168 -22
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @poof-bg/js
|
|
2
2
|
|
|
3
|
-
Official TypeScript/JavaScript SDK for the [Poof](https://poof.bg) background removal API.
|
|
3
|
+
Official TypeScript/JavaScript SDK for the [Poof](https://poof.bg) [background removal API](https://poof.bg/background-removal-api): images and [video](https://poof.bg/video-background-removal-api).
|
|
4
|
+
|
|
5
|
+
Migrating from remove.bg? It shuts down on 1 December 2026 — see the [remove.bg alternative and migration guide](https://poof.bg/alternative/remove-bg).
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -24,6 +26,10 @@ const result = await poof.removeBackground(imageFile);
|
|
|
24
26
|
|
|
25
27
|
// result.data is an ArrayBuffer containing the processed image
|
|
26
28
|
// result.metadata contains processing info
|
|
29
|
+
|
|
30
|
+
// Remove background from a video (returns a job; wait for the result)
|
|
31
|
+
const done = await poof.removeBackground(videoFile, { wait: true });
|
|
32
|
+
// done.resultUrl is a direct download link to the transparent WebM
|
|
27
33
|
```
|
|
28
34
|
|
|
29
35
|
## Usage
|
|
@@ -108,6 +114,71 @@ const result = await poof.removeBackground(imageBuffer);
|
|
|
108
114
|
fs.writeFileSync('output.png', Buffer.from(result.data));
|
|
109
115
|
```
|
|
110
116
|
|
|
117
|
+
### Video
|
|
118
|
+
|
|
119
|
+
Videos go through the same `removeBackground()` call. Because a clip takes a while to process, the call returns a `Job` instead of bytes: wait for it with `waitForJob()` (or pass `wait: true`), then download `resultUrl`. Output is a transparent VP9 WebM by default; `format` can also be `mov` (ProRes 4444 with alpha) or `mp4` (H.264, composited on `bgColor`, default white). Audio is kept. Each started second of video costs 5 credits (10 above 30 fps), charged when the job is accepted and refunded if it fails.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { Poof } from '@poof-bg/js';
|
|
123
|
+
import fs from 'node:fs';
|
|
124
|
+
|
|
125
|
+
const poof = new Poof({ apiKey: process.env.POOF_API_KEY! });
|
|
126
|
+
|
|
127
|
+
// Submit the video: file path (Node.js), File, Blob, Buffer, or { url }
|
|
128
|
+
const job = await poof.removeBackground('./clip.mp4', { format: 'webm' });
|
|
129
|
+
console.log(`Job ${job.id}: ${job.durationSeconds}s, ${job.credits} credits`);
|
|
130
|
+
|
|
131
|
+
// Wait for it to finish (polls every 3 s by default; polling is free)
|
|
132
|
+
const done = await poof.waitForJob(job, {
|
|
133
|
+
onProgress: (j) => console.log(`${j.status} ${Math.round(j.progress * 100)}%`),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Download the result (no API key needed; the link is valid for 24 hours)
|
|
137
|
+
const response = await fetch(done.resultUrl!);
|
|
138
|
+
fs.writeFileSync('clip-nobg.webm', Buffer.from(await response.arrayBuffer()));
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Shorter: `wait: true` submits and waits in one call.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const done = await poof.removeBackground('./clip.mp4', {
|
|
145
|
+
format: 'mp4', // 'webm' (transparent) | 'mov' (transparent ProRes) | 'mp4' (opaque)
|
|
146
|
+
channels: 'rgba', // 'rgba' | 'rgb' | 'alpha' (grayscale matte video, no audio)
|
|
147
|
+
bgColor: '#00ff00', // Background for opaque output (mp4 or channels 'rgb'); default white
|
|
148
|
+
size: 'hd', // Longest side cap; video output never exceeds 1080p
|
|
149
|
+
wait: { pollIntervalMs: 5000, timeoutMs: 15 * 60 * 1000 },
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Large videos can be submitted by URL instead of uploading (up to 500 MB; uploads are capped at 100 MB). `{ url }` works for images too.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const job = await poof.removeBackground({ url: 'https://example.com/clip.mp4' });
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Check on a job later with `getJob()`:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
const job = await poof.getJob('job_8f3a1c2e9b7d4a10');
|
|
163
|
+
if (job.status === 'completed') console.log(job.resultUrl);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
When the input could be either an image or a video, use `isJob()` to tell the results apart:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { Poof, isJob } from '@poof-bg/js';
|
|
170
|
+
|
|
171
|
+
const result = await poof.removeBackground(upload);
|
|
172
|
+
if (isJob(result)) {
|
|
173
|
+
const done = await poof.waitForJob(result);
|
|
174
|
+
console.log('Video ready:', done.resultUrl);
|
|
175
|
+
} else {
|
|
176
|
+
fs.writeFileSync('output.png', Buffer.from(result.data));
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
A failed job makes `waitForJob()` throw a `PoofError` with code `job_failed`; the credits are refunded automatically. Jobs are limited to 5 minutes of video.
|
|
181
|
+
|
|
111
182
|
### Account Information
|
|
112
183
|
|
|
113
184
|
```typescript
|
|
@@ -154,6 +225,8 @@ try {
|
|
|
154
225
|
console.error('Rate limit exceeded, retry later');
|
|
155
226
|
} else if (error instanceof ValidationError) {
|
|
156
227
|
console.error('Invalid input:', error.details);
|
|
228
|
+
} else if (error instanceof PoofError && error.code === 'job_failed') {
|
|
229
|
+
console.error('Video job failed, credits refunded:', error.message);
|
|
157
230
|
} else if (error instanceof PoofError) {
|
|
158
231
|
console.error(`API error: ${error.message} (${error.code})`);
|
|
159
232
|
console.error(`Request ID: ${error.requestId}`);
|
|
@@ -183,6 +256,9 @@ const poof = new Poof({
|
|
|
183
256
|
| `Uint8Array` | ✅ | ✅ |
|
|
184
257
|
| `Buffer` | ❌ | ✅ |
|
|
185
258
|
| File path (string) | ❌ | ✅ |
|
|
259
|
+
| `{ url: string }` (public https URL) | ✅ | ✅ |
|
|
260
|
+
|
|
261
|
+
Images: PNG, JPEG, WebP, GIF (first frame), up to 20 MB. Videos: MP4, MOV, WebM, MKV, up to 100 MB uploaded or 500 MB by URL, up to 5 minutes.
|
|
186
262
|
|
|
187
263
|
## TypeScript
|
|
188
264
|
|
|
@@ -191,11 +267,16 @@ Full TypeScript support with exported types:
|
|
|
191
267
|
```typescript
|
|
192
268
|
import type {
|
|
193
269
|
PoofOptions,
|
|
270
|
+
MediaInput,
|
|
194
271
|
RemoveBackgroundOptions,
|
|
195
272
|
RemoveBackgroundResult,
|
|
196
273
|
ProcessingMetadata,
|
|
274
|
+
Job,
|
|
275
|
+
JobStatus,
|
|
276
|
+
WaitForJobOptions,
|
|
197
277
|
AccountInfo,
|
|
198
278
|
ImageFormat,
|
|
279
|
+
VideoFormat,
|
|
199
280
|
ImageSize,
|
|
200
281
|
Channels,
|
|
201
282
|
} from '@poof-bg/js';
|
package/dist/index.cjs
CHANGED
|
@@ -75,6 +75,12 @@ var ERROR_MAP = {
|
|
|
75
75
|
validation_error: ValidationError,
|
|
76
76
|
missing_image: ValidationError,
|
|
77
77
|
image_too_large: ValidationError,
|
|
78
|
+
invalid_image: ValidationError,
|
|
79
|
+
video_too_large: ValidationError,
|
|
80
|
+
video_too_long: ValidationError,
|
|
81
|
+
invalid_video: ValidationError,
|
|
82
|
+
job_not_found: ValidationError,
|
|
83
|
+
job_failed: PoofError,
|
|
78
84
|
upstream_error: ServerError,
|
|
79
85
|
internal_server_error: ServerError
|
|
80
86
|
};
|
|
@@ -96,6 +102,15 @@ function detectMimeType(buffer) {
|
|
|
96
102
|
if (buffer[0] === 71 && buffer[1] === 73 && buffer[2] === 70) {
|
|
97
103
|
return "image/gif";
|
|
98
104
|
}
|
|
105
|
+
if (buffer[4] === 102 && buffer[5] === 116 && buffer[6] === 121 && buffer[7] === 112) {
|
|
106
|
+
if (buffer[8] === 113 && buffer[9] === 116) {
|
|
107
|
+
return "video/quicktime";
|
|
108
|
+
}
|
|
109
|
+
return "video/mp4";
|
|
110
|
+
}
|
|
111
|
+
if (buffer[0] === 26 && buffer[1] === 69 && buffer[2] === 223 && buffer[3] === 163) {
|
|
112
|
+
return "video/webm";
|
|
113
|
+
}
|
|
99
114
|
return "application/octet-stream";
|
|
100
115
|
}
|
|
101
116
|
function getExtension(mimeType) {
|
|
@@ -103,7 +118,10 @@ function getExtension(mimeType) {
|
|
|
103
118
|
"image/png": "png",
|
|
104
119
|
"image/jpeg": "jpg",
|
|
105
120
|
"image/webp": "webp",
|
|
106
|
-
"image/gif": "gif"
|
|
121
|
+
"image/gif": "gif",
|
|
122
|
+
"video/mp4": "mp4",
|
|
123
|
+
"video/quicktime": "mov",
|
|
124
|
+
"video/webm": "webm"
|
|
107
125
|
};
|
|
108
126
|
return map[mimeType] || "bin";
|
|
109
127
|
}
|
|
@@ -136,7 +154,12 @@ async function toBlob(input) {
|
|
|
136
154
|
jpg: "image/jpeg",
|
|
137
155
|
jpeg: "image/jpeg",
|
|
138
156
|
webp: "image/webp",
|
|
139
|
-
gif: "image/gif"
|
|
157
|
+
gif: "image/gif",
|
|
158
|
+
mp4: "video/mp4",
|
|
159
|
+
m4v: "video/mp4",
|
|
160
|
+
mov: "video/quicktime",
|
|
161
|
+
webm: "video/webm",
|
|
162
|
+
mkv: "video/x-matroska"
|
|
140
163
|
};
|
|
141
164
|
const finalMimeType = mimeFromExt[ext] || mimeType;
|
|
142
165
|
return new Blob([buffer], { type: finalMimeType });
|
|
@@ -149,14 +172,26 @@ function getFilename(input, mimeType) {
|
|
|
149
172
|
}
|
|
150
173
|
if (typeof input === "string" && isNode) {
|
|
151
174
|
const parts = input.split(/[/\\]/);
|
|
152
|
-
return parts[parts.length - 1] ||
|
|
175
|
+
return parts[parts.length - 1] || defaultFilename(mimeType);
|
|
153
176
|
}
|
|
154
|
-
return
|
|
177
|
+
return defaultFilename(mimeType);
|
|
178
|
+
}
|
|
179
|
+
function defaultFilename(mimeType) {
|
|
180
|
+
const stem = mimeType.startsWith("video/") ? "video" : "image";
|
|
181
|
+
return `${stem}.${getExtension(mimeType)}`;
|
|
182
|
+
}
|
|
183
|
+
function isUrlInput(input) {
|
|
184
|
+
return typeof input === "object" && input !== null && "url" in input && typeof input.url === "string";
|
|
185
|
+
}
|
|
186
|
+
function sleep(ms) {
|
|
187
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
155
188
|
}
|
|
156
189
|
|
|
157
190
|
// src/client.ts
|
|
158
191
|
var DEFAULT_BASE_URL = "https://api.poof.bg/v1";
|
|
159
192
|
var DEFAULT_TIMEOUT = 6e4;
|
|
193
|
+
var DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
194
|
+
var DEFAULT_JOB_TIMEOUT_MS = 6e5;
|
|
160
195
|
var Poof = class {
|
|
161
196
|
apiKey;
|
|
162
197
|
baseUrl;
|
|
@@ -170,41 +205,48 @@ var Poof = class {
|
|
|
170
205
|
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
171
206
|
}
|
|
172
207
|
/**
|
|
173
|
-
* Remove background from an image
|
|
208
|
+
* Remove the background from an image or a video.
|
|
174
209
|
*
|
|
175
|
-
*
|
|
210
|
+
* Images are processed synchronously and come back as bytes. Videos are
|
|
211
|
+
* processed as a job: the call returns a queued {@link Job}; poll it with
|
|
212
|
+
* {@link getJob} / {@link waitForJob}, or pass `wait: true` to get the
|
|
213
|
+
* completed job (with `resultUrl`) directly. Use {@link isJob} to tell the
|
|
214
|
+
* two apart.
|
|
215
|
+
*
|
|
216
|
+
* @param input - File, Blob, Buffer, ArrayBuffer, Uint8Array, file path (Node.js), or `{ url }`
|
|
176
217
|
* @param options - Processing options
|
|
177
|
-
* @returns Processed image data and metadata
|
|
218
|
+
* @returns Processed image data and metadata, or a video Job
|
|
178
219
|
*
|
|
179
220
|
* @example
|
|
180
221
|
* ```typescript
|
|
181
|
-
* //
|
|
222
|
+
* // Image
|
|
182
223
|
* const result = await poof.removeBackground(file);
|
|
183
224
|
* const imageBlob = new Blob([result.data], { type: result.metadata.contentType });
|
|
184
225
|
*
|
|
185
226
|
* // With options
|
|
186
|
-
* const result = await poof.removeBackground(file, {
|
|
187
|
-
* format: 'webp',
|
|
188
|
-
* crop: true,
|
|
189
|
-
* size: 'medium'
|
|
190
|
-
* });
|
|
227
|
+
* const result = await poof.removeBackground(file, { format: 'webp', crop: true, size: 'medium' });
|
|
191
228
|
*
|
|
192
229
|
* // Exact 500x500 output, subject cropped and centred, never stretched
|
|
193
230
|
* const result = await poof.removeBackground(file, { crop: true, width: 500, height: 500 });
|
|
194
231
|
*
|
|
195
|
-
* //
|
|
196
|
-
* const
|
|
232
|
+
* // Video: submit, then wait for the result
|
|
233
|
+
* const job = await poof.removeBackground('./clip.mp4', { format: 'webm' });
|
|
234
|
+
* const done = await poof.waitForJob(job);
|
|
235
|
+
* console.log(done.resultUrl);
|
|
197
236
|
*
|
|
198
|
-
* //
|
|
199
|
-
* const
|
|
200
|
-
* fs.writeFileSync('output.png', Buffer.from(result.data));
|
|
237
|
+
* // Video from a public URL, waiting inline
|
|
238
|
+
* const done = await poof.removeBackground({ url: 'https://example.com/clip.mp4' }, { wait: true });
|
|
201
239
|
* ```
|
|
202
240
|
*/
|
|
203
241
|
async removeBackground(input, options = {}) {
|
|
204
|
-
const blob = await toBlob(input);
|
|
205
|
-
const filename = getFilename(input, blob.type);
|
|
206
242
|
const formData = new FormData();
|
|
207
|
-
|
|
243
|
+
if (isUrlInput(input)) {
|
|
244
|
+
formData.append("url", input.url);
|
|
245
|
+
} else {
|
|
246
|
+
const blob = await toBlob(input);
|
|
247
|
+
const filename = getFilename(input, blob.type);
|
|
248
|
+
formData.append("file", blob, filename);
|
|
249
|
+
}
|
|
208
250
|
if (options.format) formData.append("format", options.format);
|
|
209
251
|
if (options.channels) formData.append("channels", options.channels);
|
|
210
252
|
if (options.bgColor) formData.append("bg_color", options.bgColor);
|
|
@@ -221,6 +263,13 @@ var Poof = class {
|
|
|
221
263
|
if (!response.ok) {
|
|
222
264
|
await this.handleError(response);
|
|
223
265
|
}
|
|
266
|
+
if (response.status === 202) {
|
|
267
|
+
const job = toJob(await response.json());
|
|
268
|
+
if (options.wait) {
|
|
269
|
+
return this.waitForJob(job, options.wait === true ? {} : options.wait);
|
|
270
|
+
}
|
|
271
|
+
return job;
|
|
272
|
+
}
|
|
224
273
|
const data = await response.arrayBuffer();
|
|
225
274
|
const matteConfidence = response.headers.get("X-Matte-Confidence");
|
|
226
275
|
const matteAmbiguousRatio = response.headers.get("X-Matte-Ambiguous-Ratio");
|
|
@@ -237,6 +286,74 @@ var Poof = class {
|
|
|
237
286
|
}
|
|
238
287
|
};
|
|
239
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Get the current state of a video job. Polling is free and does not count
|
|
291
|
+
* against your rate limit.
|
|
292
|
+
*
|
|
293
|
+
* @param jobId - The job ID returned by removeBackground()
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* const job = await poof.getJob('job_8f3a1c2e9b7d4a10');
|
|
298
|
+
* console.log(job.status, job.progress);
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
async getJob(jobId) {
|
|
302
|
+
const response = await this.fetch(`${this.baseUrl}/jobs/${encodeURIComponent(jobId)}`, {
|
|
303
|
+
method: "GET",
|
|
304
|
+
headers: {
|
|
305
|
+
Accept: "application/json"
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
if (!response.ok) {
|
|
309
|
+
await this.handleError(response);
|
|
310
|
+
}
|
|
311
|
+
return toJob(await response.json());
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Poll a video job until it is completed.
|
|
315
|
+
*
|
|
316
|
+
* @param job - A Job or a job ID
|
|
317
|
+
* @param options - Poll interval, timeout and progress callback
|
|
318
|
+
* @returns The completed job; `resultUrl` is a direct download link valid for 24 hours
|
|
319
|
+
* @throws PoofError with code `job_failed` if the job fails (credits are refunded),
|
|
320
|
+
* or code `timeout` if `timeoutMs` elapses first
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```typescript
|
|
324
|
+
* const done = await poof.waitForJob(job, {
|
|
325
|
+
* onProgress: (j) => console.log(`${Math.round(j.progress * 100)}%`),
|
|
326
|
+
* });
|
|
327
|
+
* const video = await fetch(done.resultUrl!);
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
async waitForJob(job, options = {}) {
|
|
331
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
332
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_JOB_TIMEOUT_MS;
|
|
333
|
+
const deadline = Date.now() + timeoutMs;
|
|
334
|
+
let current = typeof job === "string" ? await this.getJob(job) : job;
|
|
335
|
+
options.onProgress?.(current);
|
|
336
|
+
while (!isTerminal(current.status)) {
|
|
337
|
+
if (Date.now() >= deadline) {
|
|
338
|
+
throw new PoofError(
|
|
339
|
+
`Timed out after ${timeoutMs}ms waiting for job ${current.id}`,
|
|
340
|
+
"timeout",
|
|
341
|
+
{ status: 408 }
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
await sleep(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())));
|
|
345
|
+
current = await this.getJob(current.id);
|
|
346
|
+
options.onProgress?.(current);
|
|
347
|
+
}
|
|
348
|
+
if (current.status === "failed") {
|
|
349
|
+
throw new PoofError(
|
|
350
|
+
current.error?.message || `Job ${current.id} failed`,
|
|
351
|
+
"job_failed",
|
|
352
|
+
{ details: current.error?.code }
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
return current;
|
|
356
|
+
}
|
|
240
357
|
/**
|
|
241
358
|
* Get account information
|
|
242
359
|
*
|
|
@@ -298,6 +415,35 @@ var Poof = class {
|
|
|
298
415
|
throw PoofError.fromResponse(errorData, response.status);
|
|
299
416
|
}
|
|
300
417
|
};
|
|
418
|
+
function isJob(value) {
|
|
419
|
+
return typeof value === "object" && value !== null && "status" in value && typeof value.id === "string";
|
|
420
|
+
}
|
|
421
|
+
function isTerminal(status) {
|
|
422
|
+
return status === "completed" || status === "failed";
|
|
423
|
+
}
|
|
424
|
+
function toJob(raw) {
|
|
425
|
+
return {
|
|
426
|
+
id: raw.id,
|
|
427
|
+
status: raw.status,
|
|
428
|
+
progress: raw.progress ?? 0,
|
|
429
|
+
createdAt: raw.created_at,
|
|
430
|
+
startedAt: raw.started_at ?? null,
|
|
431
|
+
completedAt: raw.completed_at ?? null,
|
|
432
|
+
durationSeconds: raw.duration_seconds,
|
|
433
|
+
fps: raw.fps,
|
|
434
|
+
width: raw.width,
|
|
435
|
+
height: raw.height,
|
|
436
|
+
frames: raw.frames,
|
|
437
|
+
credits: raw.credits,
|
|
438
|
+
format: raw.format,
|
|
439
|
+
channels: raw.channels,
|
|
440
|
+
statusUrl: raw.status_url,
|
|
441
|
+
resultUrl: raw.result_url ?? null,
|
|
442
|
+
expiresAt: raw.expires_at ?? null,
|
|
443
|
+
error: raw.error ?? null,
|
|
444
|
+
stats: raw.stats ?? null
|
|
445
|
+
};
|
|
446
|
+
}
|
|
301
447
|
|
|
302
448
|
exports.AuthenticationError = AuthenticationError;
|
|
303
449
|
exports.PaymentRequiredError = PaymentRequiredError;
|
|
@@ -307,5 +453,6 @@ exports.PoofError = PoofError;
|
|
|
307
453
|
exports.RateLimitError = RateLimitError;
|
|
308
454
|
exports.ServerError = ServerError;
|
|
309
455
|
exports.ValidationError = ValidationError;
|
|
456
|
+
exports.isJob = isJob;
|
|
310
457
|
//# sourceMappingURL=index.cjs.map
|
|
311
458
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/utils.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ErrorCode, ApiErrorResponse } from './types.js';\n\n/**\n * Base error class for all Poof SDK errors\n */\nexport class PoofError extends Error {\n /** Machine-readable error code */\n readonly code: string;\n /** HTTP status code */\n readonly status?: number;\n /** Unique request ID for support */\n readonly requestId?: string;\n /** Additional details */\n readonly details?: string;\n\n constructor(\n message: string,\n code: string,\n options?: { status?: number; requestId?: string; details?: string }\n ) {\n super(message);\n this.name = 'PoofError';\n this.code = code;\n this.status = options?.status;\n this.requestId = options?.requestId;\n this.details = options?.details;\n\n // Maintains proper stack trace for where error was thrown (V8 engines)\n if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n\n /** Create error from API response */\n static fromResponse(response: ApiErrorResponse, status: number): PoofError {\n const ErrorClass = ERROR_MAP[response.code] || PoofError;\n return new ErrorClass(response.message, response.code, {\n status,\n requestId: response.request_id,\n details: response.details,\n });\n }\n}\n\n/**\n * Thrown when the API key is invalid or missing\n */\nexport class AuthenticationError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * Thrown when the account doesn't have permission\n */\nexport class PermissionError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'PermissionError';\n }\n}\n\n/**\n * Thrown when the account has insufficient credits\n */\nexport class PaymentRequiredError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'PaymentRequiredError';\n }\n}\n\n/**\n * Thrown when rate limit is exceeded\n */\nexport class RateLimitError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'RateLimitError';\n }\n}\n\n/**\n * Thrown when request parameters are invalid\n */\nexport class ValidationError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Thrown when a server error occurs\n */\nexport class ServerError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'ServerError';\n }\n}\n\n/** Map error codes to error classes */\nconst ERROR_MAP: Record<ErrorCode, typeof PoofError> = {\n authentication_error: AuthenticationError,\n permission_denied: PermissionError,\n payment_required: PaymentRequiredError,\n rate_limit_exceeded: RateLimitError,\n validation_error: ValidationError,\n missing_image: ValidationError,\n image_too_large: ValidationError,\n upstream_error: ServerError,\n internal_server_error: ServerError,\n};\n","import type { ImageInput } from './types.js';\n\n/** Check if running in Node.js environment */\nexport const isNode = typeof process !== 'undefined' && process.versions?.node;\n\n/** Detect MIME type from buffer */\nfunction detectMimeType(buffer: Uint8Array): string {\n // Check magic bytes\n if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {\n return 'image/png';\n }\n if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {\n return 'image/jpeg';\n }\n if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46) {\n // RIFF header, check for WEBP\n if (buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {\n return 'image/webp';\n }\n }\n if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) {\n return 'image/gif';\n }\n // Default to octet-stream\n return 'application/octet-stream';\n}\n\n/** Get file extension from MIME type */\nfunction getExtension(mimeType: string): string {\n const map: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n };\n return map[mimeType] || 'bin';\n}\n\n/** Convert various input types to a Blob suitable for FormData */\nexport async function toBlob(input: ImageInput): Promise<Blob> {\n // Already a Blob or File\n if (input instanceof Blob) {\n return input;\n }\n\n // ArrayBuffer\n if (input instanceof ArrayBuffer) {\n const uint8 = new Uint8Array(input);\n const mimeType = detectMimeType(uint8);\n return new Blob([input], { type: mimeType });\n }\n\n // Uint8Array or Node.js Buffer. Copy into a fresh ArrayBuffer-backed view:\n // a view over a SharedArrayBuffer (or a Buffer pool slice) is not a valid BlobPart.\n if (input instanceof Uint8Array || (isNode && Buffer.isBuffer(input))) {\n const bytes = Uint8Array.from(input);\n const mimeType = detectMimeType(bytes);\n return new Blob([bytes], { type: mimeType });\n }\n\n // String - treat as file path (Node.js only)\n if (typeof input === 'string') {\n if (!isNode) {\n throw new Error('File paths are only supported in Node.js. Use a Blob or File in the browser.');\n }\n \n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n \n const buffer = await fs.readFile(input);\n const uint8 = new Uint8Array(buffer);\n const mimeType = detectMimeType(uint8);\n const ext = path.extname(input).toLowerCase().slice(1);\n \n // Use file extension for mime type if available\n const mimeFromExt: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n gif: 'image/gif',\n };\n \n const finalMimeType = mimeFromExt[ext] || mimeType;\n return new Blob([buffer], { type: finalMimeType });\n }\n\n throw new Error(`Unsupported input type: ${typeof input}`);\n}\n\n/** Get filename for FormData */\nexport function getFilename(input: ImageInput, mimeType: string): string {\n // File has a name\n if (typeof File !== 'undefined' && input instanceof File) {\n return input.name;\n }\n\n // String path\n if (typeof input === 'string' && isNode) {\n // Extract filename from path\n const parts = input.split(/[/\\\\]/);\n return parts[parts.length - 1] || `image.${getExtension(mimeType)}`;\n }\n\n // Generate name from mime type\n return `image.${getExtension(mimeType)}`;\n}\n","import type {\n PoofOptions,\n ImageInput,\n RemoveBackgroundOptions,\n RemoveBackgroundResult,\n AccountInfo,\n ApiErrorResponse,\n} from './types.js';\nimport { PoofError } from './errors.js';\nimport { toBlob, getFilename } from './utils.js';\n\nconst DEFAULT_BASE_URL = 'https://api.poof.bg/v1';\nconst DEFAULT_TIMEOUT = 60000;\n\n/**\n * Poof API client for background removal\n *\n * @example\n * ```typescript\n * import { Poof } from '@poof-bg/js';\n *\n * const poof = new Poof({ apiKey: 'your-api-key' });\n * const result = await poof.removeBackground(imageFile);\n * ```\n */\nexport class Poof {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(options: PoofOptions) {\n if (!options.apiKey) {\n throw new Error('API key is required');\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl?.replace(/\\/$/, '') || DEFAULT_BASE_URL;\n this.timeout = options.timeout || DEFAULT_TIMEOUT;\n }\n\n /**\n * Remove background from an image\n *\n * @param input - Image input: File, Blob, Buffer, ArrayBuffer, Uint8Array, or file path (Node.js)\n * @param options - Processing options\n * @returns Processed image data and metadata\n *\n * @example\n * ```typescript\n * // Basic usage\n * const result = await poof.removeBackground(file);\n * const imageBlob = new Blob([result.data], { type: result.metadata.contentType });\n *\n * // With options\n * const result = await poof.removeBackground(file, {\n * format: 'webp',\n * crop: true,\n * size: 'medium'\n * });\n *\n * // Exact 500x500 output, subject cropped and centred, never stretched\n * const result = await poof.removeBackground(file, { crop: true, width: 500, height: 500 });\n *\n * // Node.js: from file path\n * const result = await poof.removeBackground('/path/to/image.png');\n *\n * // Node.js: save to file\n * const fs = require('fs');\n * fs.writeFileSync('output.png', Buffer.from(result.data));\n * ```\n */\n async removeBackground(\n input: ImageInput,\n options: RemoveBackgroundOptions = {}\n ): Promise<RemoveBackgroundResult> {\n const blob = await toBlob(input);\n const filename = getFilename(input, blob.type);\n\n const formData = new FormData();\n formData.append('image_file', blob, filename);\n\n // Add optional parameters\n if (options.format) formData.append('format', options.format);\n if (options.channels) formData.append('channels', options.channels);\n if (options.bgColor) formData.append('bg_color', options.bgColor);\n if (options.size) formData.append('size', options.size);\n if (options.crop !== undefined) formData.append('crop', String(options.crop));\n if (options.padding) formData.append('padding', options.padding);\n if (options.width !== undefined) formData.append('width', String(options.width));\n if (options.height !== undefined) formData.append('height', String(options.height));\n if (options.fit) formData.append('fit', options.fit);\n\n const response = await this.fetch(`${this.baseUrl}/remove`, {\n method: 'POST',\n body: formData,\n });\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.arrayBuffer();\n\n const matteConfidence = response.headers.get('X-Matte-Confidence');\n const matteAmbiguousRatio = response.headers.get('X-Matte-Ambiguous-Ratio');\n\n return {\n data,\n metadata: {\n requestId: response.headers.get('X-Request-ID') || '',\n processingTimeMs: parseInt(response.headers.get('X-Processing-Time-Ms') || '0', 10),\n width: parseInt(response.headers.get('X-Image-Width') || '0', 10),\n height: parseInt(response.headers.get('X-Image-Height') || '0', 10),\n contentType: response.headers.get('Content-Type') || 'image/png',\n matteConfidence: matteConfidence !== null ? parseFloat(matteConfidence) : undefined,\n matteAmbiguousRatio: matteAmbiguousRatio !== null ? parseFloat(matteAmbiguousRatio) : undefined,\n },\n };\n }\n\n /**\n * Get account information\n *\n * @returns Account info including plan and credit usage\n *\n * @example\n * ```typescript\n * const account = await poof.me();\n * console.log(`Plan: ${account.plan}`);\n * console.log(`Credits: ${account.usedCredits}/${account.maxCredits}`);\n * ```\n */\n async me(): Promise<AccountInfo> {\n const response = await this.fetch(`${this.baseUrl}/me`, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n });\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n return response.json();\n }\n\n /** Make a fetch request with auth and timeout */\n private async fetch(url: string, init: RequestInit): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set('x-api-key', this.apiKey);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...init,\n headers,\n signal: controller.signal,\n });\n return response;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new PoofError('Request timeout', 'timeout', { status: 408 });\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** Handle error response */\n private async handleError(response: Response): Promise<never> {\n let errorData: ApiErrorResponse;\n\n try {\n errorData = await response.json();\n } catch {\n throw new PoofError(\n `HTTP ${response.status}: ${response.statusText}`,\n 'unknown_error',\n { status: response.status }\n );\n }\n\n throw PoofError.fromResponse(errorData, response.status);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/utils.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ErrorCode, ApiErrorResponse } from './types.js';\n\n/**\n * Base error class for all Poof SDK errors\n */\nexport class PoofError extends Error {\n /** Machine-readable error code */\n readonly code: string;\n /** HTTP status code */\n readonly status?: number;\n /** Unique request ID for support */\n readonly requestId?: string;\n /** Additional details */\n readonly details?: string;\n\n constructor(\n message: string,\n code: string,\n options?: { status?: number; requestId?: string; details?: string }\n ) {\n super(message);\n this.name = 'PoofError';\n this.code = code;\n this.status = options?.status;\n this.requestId = options?.requestId;\n this.details = options?.details;\n\n // Maintains proper stack trace for where error was thrown (V8 engines)\n if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n\n /** Create error from API response */\n static fromResponse(response: ApiErrorResponse, status: number): PoofError {\n const ErrorClass = ERROR_MAP[response.code] || PoofError;\n return new ErrorClass(response.message, response.code, {\n status,\n requestId: response.request_id,\n details: response.details,\n });\n }\n}\n\n/**\n * Thrown when the API key is invalid or missing\n */\nexport class AuthenticationError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * Thrown when the account doesn't have permission\n */\nexport class PermissionError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'PermissionError';\n }\n}\n\n/**\n * Thrown when the account has insufficient credits\n */\nexport class PaymentRequiredError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'PaymentRequiredError';\n }\n}\n\n/**\n * Thrown when rate limit is exceeded\n */\nexport class RateLimitError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'RateLimitError';\n }\n}\n\n/**\n * Thrown when request parameters are invalid\n */\nexport class ValidationError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Thrown when a server error occurs\n */\nexport class ServerError extends PoofError {\n constructor(message: string, code: string, options?: { status?: number; requestId?: string; details?: string }) {\n super(message, code, options);\n this.name = 'ServerError';\n }\n}\n\n/** Map error codes to error classes */\nconst ERROR_MAP: Record<ErrorCode, typeof PoofError> = {\n authentication_error: AuthenticationError,\n permission_denied: PermissionError,\n payment_required: PaymentRequiredError,\n rate_limit_exceeded: RateLimitError,\n validation_error: ValidationError,\n missing_image: ValidationError,\n image_too_large: ValidationError,\n invalid_image: ValidationError,\n video_too_large: ValidationError,\n video_too_long: ValidationError,\n invalid_video: ValidationError,\n job_not_found: ValidationError,\n job_failed: PoofError,\n upstream_error: ServerError,\n internal_server_error: ServerError,\n};\n","import type { ImageInput, MediaInput, UrlInput } from './types.js';\n\n/** Check if running in Node.js environment */\nexport const isNode = typeof process !== 'undefined' && process.versions?.node;\n\n/** Detect MIME type from buffer */\nfunction detectMimeType(buffer: Uint8Array): string {\n // Check magic bytes\n if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {\n return 'image/png';\n }\n if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {\n return 'image/jpeg';\n }\n if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46) {\n // RIFF header, check for WEBP\n if (buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {\n return 'image/webp';\n }\n }\n if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) {\n return 'image/gif';\n }\n // ISO base media file (MP4, MOV): 'ftyp' at offset 4\n if (buffer[4] === 0x66 && buffer[5] === 0x74 && buffer[6] === 0x79 && buffer[7] === 0x70) {\n // 'qt ' brand is QuickTime\n if (buffer[8] === 0x71 && buffer[9] === 0x74) {\n return 'video/quicktime';\n }\n return 'video/mp4';\n }\n // EBML header (WebM, Matroska)\n if (buffer[0] === 0x1a && buffer[1] === 0x45 && buffer[2] === 0xdf && buffer[3] === 0xa3) {\n return 'video/webm';\n }\n // Default to octet-stream\n return 'application/octet-stream';\n}\n\n/** Get file extension from MIME type */\nfunction getExtension(mimeType: string): string {\n const map: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n 'video/mp4': 'mp4',\n 'video/quicktime': 'mov',\n 'video/webm': 'webm',\n };\n return map[mimeType] || 'bin';\n}\n\n/** Convert various input types to a Blob suitable for FormData */\nexport async function toBlob(input: ImageInput): Promise<Blob> {\n // Already a Blob or File\n if (input instanceof Blob) {\n return input;\n }\n\n // ArrayBuffer\n if (input instanceof ArrayBuffer) {\n const uint8 = new Uint8Array(input);\n const mimeType = detectMimeType(uint8);\n return new Blob([input], { type: mimeType });\n }\n\n // Uint8Array or Node.js Buffer. Copy into a fresh ArrayBuffer-backed view:\n // a view over a SharedArrayBuffer (or a Buffer pool slice) is not a valid BlobPart.\n if (input instanceof Uint8Array || (isNode && Buffer.isBuffer(input))) {\n const bytes = Uint8Array.from(input);\n const mimeType = detectMimeType(bytes);\n return new Blob([bytes], { type: mimeType });\n }\n\n // String - treat as file path (Node.js only)\n if (typeof input === 'string') {\n if (!isNode) {\n throw new Error('File paths are only supported in Node.js. Use a Blob or File in the browser.');\n }\n \n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n \n const buffer = await fs.readFile(input);\n const uint8 = new Uint8Array(buffer);\n const mimeType = detectMimeType(uint8);\n const ext = path.extname(input).toLowerCase().slice(1);\n \n // Use file extension for mime type if available\n const mimeFromExt: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n gif: 'image/gif',\n mp4: 'video/mp4',\n m4v: 'video/mp4',\n mov: 'video/quicktime',\n webm: 'video/webm',\n mkv: 'video/x-matroska',\n };\n \n const finalMimeType = mimeFromExt[ext] || mimeType;\n return new Blob([buffer], { type: finalMimeType });\n }\n\n throw new Error(`Unsupported input type: ${typeof input}`);\n}\n\n/** Get filename for FormData */\nexport function getFilename(input: ImageInput, mimeType: string): string {\n // File has a name\n if (typeof File !== 'undefined' && input instanceof File) {\n return input.name;\n }\n\n // String path\n if (typeof input === 'string' && isNode) {\n // Extract filename from path\n const parts = input.split(/[/\\\\]/);\n return parts[parts.length - 1] || defaultFilename(mimeType);\n }\n\n // Generate name from mime type\n return defaultFilename(mimeType);\n}\n\nfunction defaultFilename(mimeType: string): string {\n const stem = mimeType.startsWith('video/') ? 'video' : 'image';\n return `${stem}.${getExtension(mimeType)}`;\n}\n\n/** Is the input a `{ url }` object rather than file contents? */\nexport function isUrlInput(input: MediaInput): input is UrlInput {\n return typeof input === 'object' && input !== null && 'url' in input && typeof (input as UrlInput).url === 'string';\n}\n\n/** Resolve after `ms` milliseconds */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type {\n PoofOptions,\n MediaInput,\n RemoveBackgroundOptions,\n RemoveBackgroundResult,\n Job,\n JobStatus,\n WaitForJobOptions,\n AccountInfo,\n ApiErrorResponse,\n} from './types.js';\nimport { PoofError } from './errors.js';\nimport { toBlob, getFilename, isUrlInput, sleep } from './utils.js';\n\nconst DEFAULT_BASE_URL = 'https://api.poof.bg/v1';\nconst DEFAULT_TIMEOUT = 60000;\nconst DEFAULT_POLL_INTERVAL_MS = 3000;\nconst DEFAULT_JOB_TIMEOUT_MS = 600000;\n\n/**\n * Poof API client for background removal\n *\n * @example\n * ```typescript\n * import { Poof } from '@poof-bg/js';\n *\n * const poof = new Poof({ apiKey: 'your-api-key' });\n * const result = await poof.removeBackground(imageFile);\n * ```\n */\nexport class Poof {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(options: PoofOptions) {\n if (!options.apiKey) {\n throw new Error('API key is required');\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl?.replace(/\\/$/, '') || DEFAULT_BASE_URL;\n this.timeout = options.timeout || DEFAULT_TIMEOUT;\n }\n\n /**\n * Remove the background from an image or a video.\n *\n * Images are processed synchronously and come back as bytes. Videos are\n * processed as a job: the call returns a queued {@link Job}; poll it with\n * {@link getJob} / {@link waitForJob}, or pass `wait: true` to get the\n * completed job (with `resultUrl`) directly. Use {@link isJob} to tell the\n * two apart.\n *\n * @param input - File, Blob, Buffer, ArrayBuffer, Uint8Array, file path (Node.js), or `{ url }`\n * @param options - Processing options\n * @returns Processed image data and metadata, or a video Job\n *\n * @example\n * ```typescript\n * // Image\n * const result = await poof.removeBackground(file);\n * const imageBlob = new Blob([result.data], { type: result.metadata.contentType });\n *\n * // With options\n * const result = await poof.removeBackground(file, { format: 'webp', crop: true, size: 'medium' });\n *\n * // Exact 500x500 output, subject cropped and centred, never stretched\n * const result = await poof.removeBackground(file, { crop: true, width: 500, height: 500 });\n *\n * // Video: submit, then wait for the result\n * const job = await poof.removeBackground('./clip.mp4', { format: 'webm' });\n * const done = await poof.waitForJob(job);\n * console.log(done.resultUrl);\n *\n * // Video from a public URL, waiting inline\n * const done = await poof.removeBackground({ url: 'https://example.com/clip.mp4' }, { wait: true });\n * ```\n */\n async removeBackground(\n input: MediaInput,\n options: RemoveBackgroundOptions = {}\n ): Promise<RemoveBackgroundResult | Job> {\n const formData = new FormData();\n\n if (isUrlInput(input)) {\n formData.append('url', input.url);\n } else {\n const blob = await toBlob(input);\n const filename = getFilename(input, blob.type);\n formData.append('file', blob, filename);\n }\n\n // Add optional parameters\n if (options.format) formData.append('format', options.format);\n if (options.channels) formData.append('channels', options.channels);\n if (options.bgColor) formData.append('bg_color', options.bgColor);\n if (options.size) formData.append('size', options.size);\n if (options.crop !== undefined) formData.append('crop', String(options.crop));\n if (options.padding) formData.append('padding', options.padding);\n if (options.width !== undefined) formData.append('width', String(options.width));\n if (options.height !== undefined) formData.append('height', String(options.height));\n if (options.fit) formData.append('fit', options.fit);\n\n const response = await this.fetch(`${this.baseUrl}/remove`, {\n method: 'POST',\n body: formData,\n });\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n // Videos are accepted as a job\n if (response.status === 202) {\n const job = toJob(await response.json());\n if (options.wait) {\n return this.waitForJob(job, options.wait === true ? {} : options.wait);\n }\n return job;\n }\n\n const data = await response.arrayBuffer();\n\n const matteConfidence = response.headers.get('X-Matte-Confidence');\n const matteAmbiguousRatio = response.headers.get('X-Matte-Ambiguous-Ratio');\n\n return {\n data,\n metadata: {\n requestId: response.headers.get('X-Request-ID') || '',\n processingTimeMs: parseInt(response.headers.get('X-Processing-Time-Ms') || '0', 10),\n width: parseInt(response.headers.get('X-Image-Width') || '0', 10),\n height: parseInt(response.headers.get('X-Image-Height') || '0', 10),\n contentType: response.headers.get('Content-Type') || 'image/png',\n matteConfidence: matteConfidence !== null ? parseFloat(matteConfidence) : undefined,\n matteAmbiguousRatio: matteAmbiguousRatio !== null ? parseFloat(matteAmbiguousRatio) : undefined,\n },\n };\n }\n\n /**\n * Get the current state of a video job. Polling is free and does not count\n * against your rate limit.\n *\n * @param jobId - The job ID returned by removeBackground()\n *\n * @example\n * ```typescript\n * const job = await poof.getJob('job_8f3a1c2e9b7d4a10');\n * console.log(job.status, job.progress);\n * ```\n */\n async getJob(jobId: string): Promise<Job> {\n const response = await this.fetch(`${this.baseUrl}/jobs/${encodeURIComponent(jobId)}`, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n });\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n return toJob(await response.json());\n }\n\n /**\n * Poll a video job until it is completed.\n *\n * @param job - A Job or a job ID\n * @param options - Poll interval, timeout and progress callback\n * @returns The completed job; `resultUrl` is a direct download link valid for 24 hours\n * @throws PoofError with code `job_failed` if the job fails (credits are refunded),\n * or code `timeout` if `timeoutMs` elapses first\n *\n * @example\n * ```typescript\n * const done = await poof.waitForJob(job, {\n * onProgress: (j) => console.log(`${Math.round(j.progress * 100)}%`),\n * });\n * const video = await fetch(done.resultUrl!);\n * ```\n */\n async waitForJob(job: Job | string, options: WaitForJobOptions = {}): Promise<Job> {\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const timeoutMs = options.timeoutMs ?? DEFAULT_JOB_TIMEOUT_MS;\n const deadline = Date.now() + timeoutMs;\n\n let current: Job = typeof job === 'string' ? await this.getJob(job) : job;\n options.onProgress?.(current);\n\n while (!isTerminal(current.status)) {\n if (Date.now() >= deadline) {\n throw new PoofError(\n `Timed out after ${timeoutMs}ms waiting for job ${current.id}`,\n 'timeout',\n { status: 408 }\n );\n }\n await sleep(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())));\n current = await this.getJob(current.id);\n options.onProgress?.(current);\n }\n\n if (current.status === 'failed') {\n throw new PoofError(\n current.error?.message || `Job ${current.id} failed`,\n 'job_failed',\n { details: current.error?.code }\n );\n }\n\n return current;\n }\n\n /**\n * Get account information\n *\n * @returns Account info including plan and credit usage\n *\n * @example\n * ```typescript\n * const account = await poof.me();\n * console.log(`Plan: ${account.plan}`);\n * console.log(`Credits: ${account.usedCredits}/${account.maxCredits}`);\n * ```\n */\n async me(): Promise<AccountInfo> {\n const response = await this.fetch(`${this.baseUrl}/me`, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n });\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n return response.json();\n }\n\n /** Make a fetch request with auth and timeout */\n private async fetch(url: string, init: RequestInit): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set('x-api-key', this.apiKey);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...init,\n headers,\n signal: controller.signal,\n });\n return response;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new PoofError('Request timeout', 'timeout', { status: 408 });\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** Handle error response */\n private async handleError(response: Response): Promise<never> {\n let errorData: ApiErrorResponse;\n\n try {\n errorData = await response.json();\n } catch {\n throw new PoofError(\n `HTTP ${response.status}: ${response.statusText}`,\n 'unknown_error',\n { status: response.status }\n );\n }\n\n throw PoofError.fromResponse(errorData, response.status);\n }\n}\n\n/**\n * Type guard: did removeBackground() return a video Job rather than image bytes?\n *\n * @example\n * ```typescript\n * const result = await poof.removeBackground(input);\n * if (isJob(result)) {\n * const done = await poof.waitForJob(result);\n * } else {\n * fs.writeFileSync('output.png', Buffer.from(result.data));\n * }\n * ```\n */\nexport function isJob(value: RemoveBackgroundResult | Job): value is Job {\n return typeof value === 'object' && value !== null && 'status' in value && typeof (value as Job).id === 'string';\n}\n\nfunction isTerminal(status: JobStatus): boolean {\n return status === 'completed' || status === 'failed';\n}\n\n/** The job object as the API returns it (snake_case) */\ninterface RawJob {\n id: string;\n status: JobStatus;\n progress: number;\n created_at: string;\n started_at: string | null;\n completed_at: string | null;\n duration_seconds: number;\n fps: number;\n width: number;\n height: number;\n frames: number;\n credits: number;\n format: Job['format'];\n channels: Job['channels'];\n status_url: string;\n result_url: string | null;\n expires_at: string | null;\n error: Job['error'];\n stats: Job['stats'];\n}\n\nfunction toJob(raw: RawJob): Job {\n return {\n id: raw.id,\n status: raw.status,\n progress: raw.progress ?? 0,\n createdAt: raw.created_at,\n startedAt: raw.started_at ?? null,\n completedAt: raw.completed_at ?? null,\n durationSeconds: raw.duration_seconds,\n fps: raw.fps,\n width: raw.width,\n height: raw.height,\n frames: raw.frames,\n credits: raw.credits,\n format: raw.format,\n channels: raw.channels,\n statusUrl: raw.status_url,\n resultUrl: raw.result_url ?? null,\n expiresAt: raw.expires_at ?? null,\n error: raw.error ?? null,\n stats: raw.stats ?? null,\n };\n}\n"]}
|