@poof-bg/js 1.0.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 +108 -5
- package/dist/index.cjs +184 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +177 -26
- package/dist/index.d.ts +177 -26
- package/dist/index.js +184 -31
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
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
|
|
@@ -50,13 +56,35 @@ const url = URL.createObjectURL(blob);
|
|
|
50
56
|
```typescript
|
|
51
57
|
const result = await poof.removeBackground(file, {
|
|
52
58
|
format: 'webp', // Output format: 'png' | 'jpg' | 'webp'
|
|
53
|
-
crop: true, // Crop to subject bounds
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
crop: true, // Crop to subject bounds, or an aspect ratio like '1:1'
|
|
60
|
+
padding: '10%', // Padding around the subject when cropping
|
|
61
|
+
size: 'medium', // Size preset: 'full' | 'preview' | 'medium' | 'hd'
|
|
62
|
+
channels: 'rgb', // 'rgba' for transparency, 'rgb' for opaque, 'alpha' for the mask
|
|
63
|
+
bgColor: '#ffffff', // Background color (when channels is 'rgb' or 'rgba')
|
|
57
64
|
});
|
|
58
65
|
```
|
|
59
66
|
|
|
67
|
+
### Fixed Output Size
|
|
68
|
+
|
|
69
|
+
Resize to an exact size without stretching. `fit` decides what happens with the aspect-ratio difference: `contain` (default) pads, `cover` crops the overflow around the subject, `scale-down` pads without ever enlarging a smaller image. Setting only `width` or `height` keeps the aspect ratio. `size` is ignored when a fixed size is set.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// 500x500 product image: subject cropped, padded and centred
|
|
73
|
+
const result = await poof.removeBackground(file, { crop: true, width: 500, height: 500 });
|
|
74
|
+
|
|
75
|
+
// Fill a 1080x1080 canvas on white instead
|
|
76
|
+
const result = await poof.removeBackground(file, {
|
|
77
|
+
width: 1080,
|
|
78
|
+
height: 1080,
|
|
79
|
+
fit: 'cover',
|
|
80
|
+
format: 'jpg',
|
|
81
|
+
bgColor: '#ffffff',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Cap the width at 1000px, keep the aspect ratio, leave smaller images alone
|
|
85
|
+
const result = await poof.removeBackground(file, { width: 1000, fit: 'scale-down' });
|
|
86
|
+
```
|
|
87
|
+
|
|
60
88
|
### Node.js: From File Path
|
|
61
89
|
|
|
62
90
|
```typescript
|
|
@@ -86,6 +114,71 @@ const result = await poof.removeBackground(imageBuffer);
|
|
|
86
114
|
fs.writeFileSync('output.png', Buffer.from(result.data));
|
|
87
115
|
```
|
|
88
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
|
+
|
|
89
182
|
### Account Information
|
|
90
183
|
|
|
91
184
|
```typescript
|
|
@@ -132,6 +225,8 @@ try {
|
|
|
132
225
|
console.error('Rate limit exceeded, retry later');
|
|
133
226
|
} else if (error instanceof ValidationError) {
|
|
134
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);
|
|
135
230
|
} else if (error instanceof PoofError) {
|
|
136
231
|
console.error(`API error: ${error.message} (${error.code})`);
|
|
137
232
|
console.error(`Request ID: ${error.requestId}`);
|
|
@@ -161,6 +256,9 @@ const poof = new Poof({
|
|
|
161
256
|
| `Uint8Array` | ✅ | ✅ |
|
|
162
257
|
| `Buffer` | ❌ | ✅ |
|
|
163
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.
|
|
164
262
|
|
|
165
263
|
## TypeScript
|
|
166
264
|
|
|
@@ -169,11 +267,16 @@ Full TypeScript support with exported types:
|
|
|
169
267
|
```typescript
|
|
170
268
|
import type {
|
|
171
269
|
PoofOptions,
|
|
270
|
+
MediaInput,
|
|
172
271
|
RemoveBackgroundOptions,
|
|
173
272
|
RemoveBackgroundResult,
|
|
174
273
|
ProcessingMetadata,
|
|
274
|
+
Job,
|
|
275
|
+
JobStatus,
|
|
276
|
+
WaitForJobOptions,
|
|
175
277
|
AccountInfo,
|
|
176
278
|
ImageFormat,
|
|
279
|
+
VideoFormat,
|
|
177
280
|
ImageSize,
|
|
178
281
|
Channels,
|
|
179
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
|
}
|
|
@@ -116,14 +134,10 @@ async function toBlob(input) {
|
|
|
116
134
|
const mimeType = detectMimeType(uint8);
|
|
117
135
|
return new Blob([input], { type: mimeType });
|
|
118
136
|
}
|
|
119
|
-
if (input instanceof Uint8Array) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (isNode && Buffer.isBuffer(input)) {
|
|
124
|
-
const uint8 = new Uint8Array(input);
|
|
125
|
-
const mimeType = detectMimeType(uint8);
|
|
126
|
-
return new Blob([input], { type: mimeType });
|
|
137
|
+
if (input instanceof Uint8Array || isNode && Buffer.isBuffer(input)) {
|
|
138
|
+
const bytes = Uint8Array.from(input);
|
|
139
|
+
const mimeType = detectMimeType(bytes);
|
|
140
|
+
return new Blob([bytes], { type: mimeType });
|
|
127
141
|
}
|
|
128
142
|
if (typeof input === "string") {
|
|
129
143
|
if (!isNode) {
|
|
@@ -140,7 +154,12 @@ async function toBlob(input) {
|
|
|
140
154
|
jpg: "image/jpeg",
|
|
141
155
|
jpeg: "image/jpeg",
|
|
142
156
|
webp: "image/webp",
|
|
143
|
-
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"
|
|
144
163
|
};
|
|
145
164
|
const finalMimeType = mimeFromExt[ext] || mimeType;
|
|
146
165
|
return new Blob([buffer], { type: finalMimeType });
|
|
@@ -153,14 +172,26 @@ function getFilename(input, mimeType) {
|
|
|
153
172
|
}
|
|
154
173
|
if (typeof input === "string" && isNode) {
|
|
155
174
|
const parts = input.split(/[/\\]/);
|
|
156
|
-
return parts[parts.length - 1] ||
|
|
175
|
+
return parts[parts.length - 1] || defaultFilename(mimeType);
|
|
157
176
|
}
|
|
158
|
-
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));
|
|
159
188
|
}
|
|
160
189
|
|
|
161
190
|
// src/client.ts
|
|
162
191
|
var DEFAULT_BASE_URL = "https://api.poof.bg/v1";
|
|
163
192
|
var DEFAULT_TIMEOUT = 6e4;
|
|
193
|
+
var DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
194
|
+
var DEFAULT_JOB_TIMEOUT_MS = 6e5;
|
|
164
195
|
var Poof = class {
|
|
165
196
|
apiKey;
|
|
166
197
|
baseUrl;
|
|
@@ -174,43 +205,57 @@ var Poof = class {
|
|
|
174
205
|
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
175
206
|
}
|
|
176
207
|
/**
|
|
177
|
-
* Remove background from an image
|
|
208
|
+
* Remove the background from an image or a video.
|
|
209
|
+
*
|
|
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.
|
|
178
215
|
*
|
|
179
|
-
* @param input -
|
|
216
|
+
* @param input - File, Blob, Buffer, ArrayBuffer, Uint8Array, file path (Node.js), or `{ url }`
|
|
180
217
|
* @param options - Processing options
|
|
181
|
-
* @returns Processed image data and metadata
|
|
218
|
+
* @returns Processed image data and metadata, or a video Job
|
|
182
219
|
*
|
|
183
220
|
* @example
|
|
184
221
|
* ```typescript
|
|
185
|
-
* //
|
|
222
|
+
* // Image
|
|
186
223
|
* const result = await poof.removeBackground(file);
|
|
187
224
|
* const imageBlob = new Blob([result.data], { type: result.metadata.contentType });
|
|
188
225
|
*
|
|
189
226
|
* // With options
|
|
190
|
-
* const result = await poof.removeBackground(file, {
|
|
191
|
-
* format: 'webp',
|
|
192
|
-
* crop: true,
|
|
193
|
-
* size: 'medium'
|
|
194
|
-
* });
|
|
227
|
+
* const result = await poof.removeBackground(file, { format: 'webp', crop: true, size: 'medium' });
|
|
195
228
|
*
|
|
196
|
-
* //
|
|
197
|
-
* const result = await poof.removeBackground(
|
|
229
|
+
* // Exact 500x500 output, subject cropped and centred, never stretched
|
|
230
|
+
* const result = await poof.removeBackground(file, { crop: true, width: 500, height: 500 });
|
|
198
231
|
*
|
|
199
|
-
* //
|
|
200
|
-
* const
|
|
201
|
-
*
|
|
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);
|
|
236
|
+
*
|
|
237
|
+
* // Video from a public URL, waiting inline
|
|
238
|
+
* const done = await poof.removeBackground({ url: 'https://example.com/clip.mp4' }, { wait: true });
|
|
202
239
|
* ```
|
|
203
240
|
*/
|
|
204
241
|
async removeBackground(input, options = {}) {
|
|
205
|
-
const blob = await toBlob(input);
|
|
206
|
-
const filename = getFilename(input, blob.type);
|
|
207
242
|
const formData = new FormData();
|
|
208
|
-
|
|
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
|
+
}
|
|
209
250
|
if (options.format) formData.append("format", options.format);
|
|
210
251
|
if (options.channels) formData.append("channels", options.channels);
|
|
211
252
|
if (options.bgColor) formData.append("bg_color", options.bgColor);
|
|
212
253
|
if (options.size) formData.append("size", options.size);
|
|
213
254
|
if (options.crop !== void 0) formData.append("crop", String(options.crop));
|
|
255
|
+
if (options.padding) formData.append("padding", options.padding);
|
|
256
|
+
if (options.width !== void 0) formData.append("width", String(options.width));
|
|
257
|
+
if (options.height !== void 0) formData.append("height", String(options.height));
|
|
258
|
+
if (options.fit) formData.append("fit", options.fit);
|
|
214
259
|
const response = await this.fetch(`${this.baseUrl}/remove`, {
|
|
215
260
|
method: "POST",
|
|
216
261
|
body: formData
|
|
@@ -218,7 +263,16 @@ var Poof = class {
|
|
|
218
263
|
if (!response.ok) {
|
|
219
264
|
await this.handleError(response);
|
|
220
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
|
+
}
|
|
221
273
|
const data = await response.arrayBuffer();
|
|
274
|
+
const matteConfidence = response.headers.get("X-Matte-Confidence");
|
|
275
|
+
const matteAmbiguousRatio = response.headers.get("X-Matte-Ambiguous-Ratio");
|
|
222
276
|
return {
|
|
223
277
|
data,
|
|
224
278
|
metadata: {
|
|
@@ -226,10 +280,80 @@ var Poof = class {
|
|
|
226
280
|
processingTimeMs: parseInt(response.headers.get("X-Processing-Time-Ms") || "0", 10),
|
|
227
281
|
width: parseInt(response.headers.get("X-Image-Width") || "0", 10),
|
|
228
282
|
height: parseInt(response.headers.get("X-Image-Height") || "0", 10),
|
|
229
|
-
contentType: response.headers.get("Content-Type") || "image/png"
|
|
283
|
+
contentType: response.headers.get("Content-Type") || "image/png",
|
|
284
|
+
matteConfidence: matteConfidence !== null ? parseFloat(matteConfidence) : void 0,
|
|
285
|
+
matteAmbiguousRatio: matteAmbiguousRatio !== null ? parseFloat(matteAmbiguousRatio) : void 0
|
|
230
286
|
}
|
|
231
287
|
};
|
|
232
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
|
+
}
|
|
233
357
|
/**
|
|
234
358
|
* Get account information
|
|
235
359
|
*
|
|
@@ -291,6 +415,35 @@ var Poof = class {
|
|
|
291
415
|
throw PoofError.fromResponse(errorData, response.status);
|
|
292
416
|
}
|
|
293
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
|
+
}
|
|
294
447
|
|
|
295
448
|
exports.AuthenticationError = AuthenticationError;
|
|
296
449
|
exports.PaymentRequiredError = PaymentRequiredError;
|
|
@@ -300,5 +453,6 @@ exports.PoofError = PoofError;
|
|
|
300
453
|
exports.RateLimitError = RateLimitError;
|
|
301
454
|
exports.ServerError = ServerError;
|
|
302
455
|
exports.ValidationError = ValidationError;
|
|
456
|
+
exports.isJob = isJob;
|
|
303
457
|
//# sourceMappingURL=index.cjs.map
|
|
304
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\n if (input instanceof Uint8Array) {\n const mimeType = detectMimeType(input);\n return new Blob([input], { type: mimeType });\n }\n\n // Node.js Buffer\n if (isNode && Buffer.isBuffer(input)) {\n const uint8 = new Uint8Array(input);\n const mimeType = detectMimeType(uint8);\n return new Blob([input], { 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 * // 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\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 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 },\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"]}
|