@fotovid/sdk 0.1.1 → 0.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 +53 -2
- package/dist/index.cjs +99 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -1
- package/dist/index.d.ts +109 -1
- package/dist/index.js +97 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @fotovid/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@fotovid/sdk)
|
|
4
|
+
|
|
5
|
+
Thin, typed Node.js / TypeScript SDK for the [Fotovid](https://fotovid.co) media API — a serverless ffmpeg API for watermarking video and images, trimming video and audio, extracting audio from video, generating video thumbnails, and probing video metadata. POST a source URL, await the finished file over one HTTPS call. No ffmpeg binary, no native dependencies, nothing to install beyond this package.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -42,13 +44,53 @@ Parameter names match the API 1:1 (`source_url`, `watermark_image_url`, …). Ev
|
|
|
42
44
|
media operation returns `{ id, type, url, expires_at, duration? }`; `probe`
|
|
43
45
|
returns video metadata.
|
|
44
46
|
|
|
47
|
+
## Async (large or long video)
|
|
48
|
+
|
|
49
|
+
The sync methods above reject video over ~720p or 15s with a 400 asking you to
|
|
50
|
+
use the async endpoint (a hard limit — the sync API has a short time budget).
|
|
51
|
+
For a large video watermark, a long trim, or anything you don't need back in a
|
|
52
|
+
couple of seconds, submit a task instead and poll for the result:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
let task = await fotovid.tasks.video.watermark({
|
|
56
|
+
source_url: "https://cdn.example.com/1080x1920.mp4", // vertical / large video, rejected by sync
|
|
57
|
+
watermark_type: "image",
|
|
58
|
+
watermark_image_url: "https://cdn.example.com/logo.png",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
while (task.status === "starting" || task.status === "processing") {
|
|
62
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
63
|
+
task = await fotovid.tasks.get(task.id);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (task.status === "succeeded") {
|
|
67
|
+
console.log(task.outputs); // [{ kind: "video", url: "..." }, ...] — read by kind, order not guaranteed
|
|
68
|
+
} else {
|
|
69
|
+
console.error(task.error); // { code, message, request_id, detail? } — a failed task, not an exception
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`tasks.*` mirrors the sync methods 1:1 (`tasks.video.watermark`, `tasks.video.trim`,
|
|
74
|
+
`tasks.video.extractAudio`, `tasks.video.thumbnail`, `tasks.image.watermark`,
|
|
75
|
+
`tasks.audio.trim`) plus the low-level `tasks.create`/`tasks.get`. There's no
|
|
76
|
+
built-in polling helper — the loop above is the whole pattern. `probe` has no
|
|
77
|
+
async form; it's sync-only.
|
|
78
|
+
|
|
79
|
+
## Sync vs async
|
|
80
|
+
|
|
81
|
+
| | Sync (`fotovid.video.*`, …) | Async (`fotovid.tasks.*`) |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| Returns | Finished result, same call | A `Task` — poll `tasks.get` until terminal |
|
|
84
|
+
| Limits | ~720p / 15s | 4K / 600s |
|
|
85
|
+
| Use for | Small/short media, need the result now | Large video, long clips, batch/background jobs |
|
|
86
|
+
|
|
45
87
|
## Config
|
|
46
88
|
|
|
47
89
|
```ts
|
|
48
90
|
new Fotovid({
|
|
49
91
|
apiKey: "p6_<key_id>:<secret>", // or set FOTOVID_API_KEY
|
|
50
92
|
baseUrl: "https://api.fotovid.co", // optional
|
|
51
|
-
fetch: customFetch, // optional, defaults to global fetch (Node
|
|
93
|
+
fetch: customFetch, // optional, defaults to global fetch (Node 20+)
|
|
52
94
|
});
|
|
53
95
|
```
|
|
54
96
|
|
|
@@ -67,6 +109,15 @@ await fotovid.video.watermark(input, opts);
|
|
|
67
109
|
await fotovid.video.watermark(input, opts);
|
|
68
110
|
```
|
|
69
111
|
|
|
112
|
+
The same applies to `fotovid.tasks.*` — pass `idempotencyKey` there too (it travels
|
|
113
|
+
in the request body, not a header, but the SDK handles that difference for you).
|
|
114
|
+
|
|
115
|
+
## Errors
|
|
116
|
+
|
|
117
|
+
A non-2xx response throws `FotovidError` (`status`, `detail`, `retryAfter`).
|
|
118
|
+
For `fotovid.tasks.*`, that's the only thing that throws — a task that finishes
|
|
119
|
+
as `"failed"` is a normal return value, not an exception; check `task.error`.
|
|
120
|
+
|
|
70
121
|
## License
|
|
71
122
|
|
|
72
123
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Fotovid: () => Fotovid,
|
|
24
24
|
FotovidError: () => FotovidError,
|
|
25
|
+
Tasks: () => Tasks,
|
|
25
26
|
default: () => Fotovid
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -54,6 +55,82 @@ var FotovidError = class extends Error {
|
|
|
54
55
|
}
|
|
55
56
|
};
|
|
56
57
|
|
|
58
|
+
// src/http.ts
|
|
59
|
+
async function requestJson(ctx, path, init) {
|
|
60
|
+
const response = await ctx.fetch(new URL(path, ctx.baseUrl), {
|
|
61
|
+
method: init.method,
|
|
62
|
+
headers: {
|
|
63
|
+
authorization: `Bearer ${ctx.apiKey}`,
|
|
64
|
+
// An explicit UA identifies the SDK to the edge and to server-side
|
|
65
|
+
// observability. (The Python SDK's default urllib UA was blocked by
|
|
66
|
+
// Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)
|
|
67
|
+
"user-agent": `fotovid-sdk/${"0.2.0"}`,
|
|
68
|
+
...init.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
69
|
+
...init.headers
|
|
70
|
+
},
|
|
71
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
|
|
72
|
+
signal: init.signal
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
let detail;
|
|
76
|
+
try {
|
|
77
|
+
detail = await response.json();
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
const ra = response.headers.get("retry-after");
|
|
81
|
+
const retryAfter = ra ? Number(ra) : void 0;
|
|
82
|
+
throw new FotovidError(response.status, detail, retryAfter);
|
|
83
|
+
}
|
|
84
|
+
return await response.json();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/tasks.ts
|
|
88
|
+
function taskBody(input, options) {
|
|
89
|
+
return {
|
|
90
|
+
type: input.type,
|
|
91
|
+
source_url: input.source_url,
|
|
92
|
+
params: input.params,
|
|
93
|
+
idempotency_key: options?.idempotencyKey ?? globalThis.crypto.randomUUID(),
|
|
94
|
+
webhook: options?.webhook,
|
|
95
|
+
webhook_events_filter: options?.webhookEventsFilter
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
var Tasks = class {
|
|
99
|
+
#ctx;
|
|
100
|
+
constructor(ctx) {
|
|
101
|
+
this.#ctx = ctx;
|
|
102
|
+
}
|
|
103
|
+
create(input, options) {
|
|
104
|
+
return requestJson(this.#ctx, "/v1/tasks", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
body: taskBody(input, options)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */
|
|
110
|
+
get(id, options) {
|
|
111
|
+
return requestJson(this.#ctx, `/v1/tasks/${encodeURIComponent(id)}`, {
|
|
112
|
+
method: "GET",
|
|
113
|
+
signal: options?.signal
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
video = {
|
|
117
|
+
watermark: (input, options) => this.create({ type: "video.watermark", ...splitInput(input) }, options),
|
|
118
|
+
trim: (input, options) => this.create({ type: "video.trim", ...splitInput(input) }, options),
|
|
119
|
+
extractAudio: (input, options) => this.create({ type: "video.audio", ...splitInput(input) }, options),
|
|
120
|
+
thumbnail: (input, options) => this.create({ type: "video.cover", ...splitInput(input) }, options)
|
|
121
|
+
};
|
|
122
|
+
image = {
|
|
123
|
+
watermark: (input, options) => this.create({ type: "image.watermark", ...splitInput(input) }, options)
|
|
124
|
+
};
|
|
125
|
+
audio = {
|
|
126
|
+
trim: (input, options) => this.create({ type: "audio.trim", ...splitInput(input) }, options)
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
function splitInput(input) {
|
|
130
|
+
const { source_url, ...params } = input;
|
|
131
|
+
return { source_url, params };
|
|
132
|
+
}
|
|
133
|
+
|
|
57
134
|
// src/client.ts
|
|
58
135
|
var DEFAULT_BASE_URL = "https://api.fotovid.co";
|
|
59
136
|
function resolveApiKey(explicit) {
|
|
@@ -69,39 +146,33 @@ var Fotovid = class {
|
|
|
69
146
|
#apiKey;
|
|
70
147
|
#baseUrl;
|
|
71
148
|
#fetch;
|
|
149
|
+
/** Async task surface — submit larger jobs than sync accepts, then poll for the result. */
|
|
150
|
+
tasks;
|
|
72
151
|
constructor(options = {}) {
|
|
73
152
|
this.#apiKey = resolveApiKey(options.apiKey);
|
|
74
153
|
this.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
75
154
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
155
|
+
this.tasks = new Tasks({
|
|
156
|
+
apiKey: this.#apiKey,
|
|
157
|
+
baseUrl: this.#baseUrl,
|
|
158
|
+
fetch: this.#fetch
|
|
159
|
+
});
|
|
76
160
|
}
|
|
77
|
-
|
|
161
|
+
#post(path, input, options) {
|
|
78
162
|
const { source_url, ...params } = input;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"idempotency-key": options?.idempotencyKey ?? globalThis.crypto.randomUUID()
|
|
91
|
-
},
|
|
92
|
-
body: JSON.stringify({ source_url, params })
|
|
93
|
-
});
|
|
94
|
-
if (!response.ok) {
|
|
95
|
-
let detail;
|
|
96
|
-
try {
|
|
97
|
-
detail = await response.json();
|
|
98
|
-
} catch {
|
|
163
|
+
return requestJson(
|
|
164
|
+
{ apiKey: this.#apiKey, baseUrl: this.#baseUrl, fetch: this.#fetch },
|
|
165
|
+
path,
|
|
166
|
+
{
|
|
167
|
+
method: "POST",
|
|
168
|
+
body: { source_url, params },
|
|
169
|
+
headers: {
|
|
170
|
+
// Billed endpoints require an idempotency key; default to a fresh
|
|
171
|
+
// UUID per call, overridable to make a retry replay (not re-charge).
|
|
172
|
+
"idempotency-key": options?.idempotencyKey ?? globalThis.crypto.randomUUID()
|
|
173
|
+
}
|
|
99
174
|
}
|
|
100
|
-
|
|
101
|
-
const retryAfter = ra ? Number(ra) : void 0;
|
|
102
|
-
throw new FotovidError(response.status, detail, retryAfter);
|
|
103
|
-
}
|
|
104
|
-
return await response.json();
|
|
175
|
+
);
|
|
105
176
|
}
|
|
106
177
|
video = {
|
|
107
178
|
watermark: (input, options) => this.#post("/v1/video/watermark", input, options),
|
|
@@ -120,6 +191,7 @@ var Fotovid = class {
|
|
|
120
191
|
// Annotate the CommonJS export names for ESM import in node:
|
|
121
192
|
0 && (module.exports = {
|
|
122
193
|
Fotovid,
|
|
123
|
-
FotovidError
|
|
194
|
+
FotovidError,
|
|
195
|
+
Tasks
|
|
124
196
|
});
|
|
125
197
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/error.ts","../src/client.ts"],"sourcesContent":["export { Fotovid, Fotovid as default } from \"./client.js\";\nexport { FotovidError } from \"./error.js\";\nexport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageFormat,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n\tWatermarkPosition,\n\tWatermarkType,\n\tX264Preset,\n} from \"./types.js\";\n","function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/** Thrown when the Fotovid API returns a non-2xx response. */\nexport class FotovidError extends Error {\n\t/** HTTP status code of the failed response. */\n\treadonly status: number;\n\t/** Parsed error body, when the response carried one. */\n\treadonly detail: unknown;\n\t/** Seconds to wait before retrying, from the `Retry-After` header (e.g. 429/503). */\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, detail: unknown, retryAfter?: number) {\n\t\tlet message: string | undefined;\n\t\tif (isRecord(detail)) {\n\t\t\tif (typeof detail.detail === \"string\") {\n\t\t\t\tmessage = detail.detail;\n\t\t\t} else if (typeof detail.message === \"string\") {\n\t\t\t\tmessage = detail.message;\n\t\t\t}\n\t\t}\n\t\tsuper(message ?? `Fotovid API request failed with status ${status}`);\n\t\tthis.name = \"FotovidError\";\n\t\tthis.status = status;\n\t\tthis.detail = detail;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n","import { FotovidError } from \"./error.js\";\nimport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.fotovid.co\";\n\n// Injected at build time from package.json by tsup (see tsup.config.ts).\ndeclare const __SDK_VERSION__: string;\n\nfunction resolveApiKey(explicit: string | undefined): string {\n\tconst key =\n\t\texplicit ??\n\t\t(typeof process !== \"undefined\" ? process.env?.FOTOVID_API_KEY : undefined);\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Fotovid: missing API key. Pass { apiKey } or set FOTOVID_API_KEY.\",\n\t\t);\n\t}\n\treturn key;\n}\n\n/**\n * Typed client for the Fotovid media API. Methods mirror the REST resources\n * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names\n * match the API 1:1 (`source_url`, `watermark_image_url`, …).\n */\nexport class Fotovid {\n\treadonly #apiKey: string;\n\treadonly #baseUrl: string;\n\treadonly #fetch: typeof globalThis.fetch;\n\n\tconstructor(options: FotovidOptions = {}) {\n\t\tthis.#apiKey = resolveApiKey(options.apiKey);\n\t\tthis.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch;\n\t}\n\n\tasync #post<T>(\n\t\tpath: string,\n\t\tinput: { source_url: string },\n\t\toptions?: RequestOptions,\n\t): Promise<T> {\n\t\tconst { source_url, ...params } = input;\n\t\tconst response = await this.#fetch(new URL(path, this.#baseUrl), {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\tauthorization: `Bearer ${this.#apiKey}`,\n\t\t\t\t// An explicit UA identifies the SDK to the edge and to server-side\n\t\t\t\t// observability. (The Python SDK's default urllib UA was blocked by\n\t\t\t\t// Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)\n\t\t\t\t\"user-agent\": `fotovid-sdk/${__SDK_VERSION__}`,\n\t\t\t\t// Billed endpoints require an idempotency key; default to a fresh\n\t\t\t\t// UUID per call, overridable to make a retry replay (not re-charge).\n\t\t\t\t\"idempotency-key\":\n\t\t\t\t\toptions?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\t\t},\n\t\t\tbody: JSON.stringify({ source_url, params }),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tlet detail: unknown;\n\t\t\ttry {\n\t\t\t\tdetail = await response.json();\n\t\t\t} catch {\n\t\t\t\t// non-JSON error body — leave detail undefined\n\t\t\t}\n\t\t\tconst ra = response.headers.get(\"retry-after\");\n\t\t\tconst retryAfter = ra ? Number(ra) : undefined;\n\t\t\tthrow new FotovidError(response.status, detail, retryAfter);\n\t\t}\n\t\treturn (await response.json()) as T;\n\t}\n\n\treadonly video = {\n\t\twatermark: (\n\t\t\tinput: VideoWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/watermark\", input, options),\n\t\ttrim: (input: TrimInput, options?: RequestOptions): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/trim\", input, options),\n\t\textractAudio: (\n\t\t\tinput: ExtractAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-audio\", input, options),\n\t\tthumbnail: (\n\t\t\tinput: ThumbnailInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-cover\", input, options),\n\t\tprobe: (\n\t\t\tinput: ProbeInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<ProbeResult> =>\n\t\t\tthis.#post<ProbeResult>(\"/v1/video/probe\", input, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (\n\t\t\tinput: ImageWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/image/watermark\", input, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (\n\t\t\tinput: TrimAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/audio/trim\", input, options),\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,SAAS,SAAS,OAAkD;AACnE,SAAO,OAAO,UAAU,YAAY,UAAU;AAC/C;AAGO,IAAM,eAAN,cAA2B,MAAM;AAAA;AAAA,EAE9B;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,QAAgB,QAAiB,YAAqB;AACjE,QAAI;AACJ,QAAI,SAAS,MAAM,GAAG;AACrB,UAAI,OAAO,OAAO,WAAW,UAAU;AACtC,kBAAU,OAAO;AAAA,MAClB,WAAW,OAAO,OAAO,YAAY,UAAU;AAC9C,kBAAU,OAAO;AAAA,MAClB;AAAA,IACD;AACA,UAAM,WAAW,0CAA0C,MAAM,EAAE;AACnE,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,aAAa;AAAA,EACnB;AACD;;;ACbA,IAAM,mBAAmB;AAKzB,SAAS,cAAc,UAAsC;AAC5D,QAAM,MACL,aACC,OAAO,YAAY,cAAc,QAAQ,KAAK,kBAAkB;AAClE,MAAI,CAAC,KAAK;AACT,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAOO,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,UAA0B,CAAC,GAAG;AACzC,SAAK,UAAU,cAAc,QAAQ,MAAM;AAC3C,SAAK,WAAW,QAAQ,WAAW;AACnC,SAAK,SAAS,QAAQ,SAAS,WAAW;AAAA,EAC3C;AAAA,EAEA,MAAM,MACL,MACA,OACA,SACa;AACb,UAAM,EAAE,YAAY,GAAG,OAAO,IAAI;AAClC,UAAM,WAAW,MAAM,KAAK,OAAO,IAAI,IAAI,MAAM,KAAK,QAAQ,GAAG;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACR,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK,OAAO;AAAA;AAAA;AAAA;AAAA,QAIrC,cAAc,eAAe,OAAe;AAAA;AAAA;AAAA,QAG5C,mBACC,SAAS,kBAAkB,WAAW,OAAO,WAAW;AAAA,MAC1D;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,YAAY,OAAO,CAAC;AAAA,IAC5C,CAAC;AACD,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI;AACJ,UAAI;AACH,iBAAS,MAAM,SAAS,KAAK;AAAA,MAC9B,QAAQ;AAAA,MAER;AACA,YAAM,KAAK,SAAS,QAAQ,IAAI,aAAa;AAC7C,YAAM,aAAa,KAAK,OAAO,EAAE,IAAI;AACrC,YAAM,IAAI,aAAa,SAAS,QAAQ,QAAQ,UAAU;AAAA,IAC3D;AACA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC7B;AAAA,EAES,QAAQ;AAAA,IAChB,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,uBAAuB,OAAO,OAAO;AAAA,IAC9D,MAAM,CAAC,OAAkB,YACxB,KAAK,MAAmB,kBAAkB,OAAO,OAAO;AAAA,IACzD,cAAc,CACb,OACA,YAEA,KAAK,MAAmB,2BAA2B,OAAO,OAAO;AAAA,IAClE,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,2BAA2B,OAAO,OAAO;AAAA,IAClE,OAAO,CACN,OACA,YAEA,KAAK,MAAmB,mBAAmB,OAAO,OAAO;AAAA,EAC3D;AAAA,EAES,QAAQ;AAAA,IAChB,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,uBAAuB,OAAO,OAAO;AAAA,EAC/D;AAAA,EAES,QAAQ;AAAA,IAChB,MAAM,CACL,OACA,YAEA,KAAK,MAAmB,kBAAkB,OAAO,OAAO;AAAA,EAC1D;AACD;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/error.ts","../src/http.ts","../src/tasks.ts","../src/client.ts"],"sourcesContent":["export { Fotovid, Fotovid as default } from \"./client.js\";\nexport { FotovidError } from \"./error.js\";\nexport type {\n\tCreateTaskInput,\n\tTask,\n\tTaskError,\n\tTaskErrorCode,\n\tTaskOutput,\n\tTaskRequestOptions,\n\tTaskStatus,\n\tTaskType,\n\tWebhookEvent,\n} from \"./tasks.js\";\nexport { Tasks } from \"./tasks.js\";\nexport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageFormat,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n\tWatermarkPosition,\n\tWatermarkType,\n\tX264Preset,\n} from \"./types.js\";\n","function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/** Thrown when the Fotovid API returns a non-2xx response. */\nexport class FotovidError extends Error {\n\t/** HTTP status code of the failed response. */\n\treadonly status: number;\n\t/** Parsed error body, when the response carried one. */\n\treadonly detail: unknown;\n\t/** Seconds to wait before retrying, from the `Retry-After` header (e.g. 429/503). */\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, detail: unknown, retryAfter?: number) {\n\t\tlet message: string | undefined;\n\t\tif (isRecord(detail)) {\n\t\t\tif (typeof detail.detail === \"string\") {\n\t\t\t\tmessage = detail.detail;\n\t\t\t} else if (typeof detail.message === \"string\") {\n\t\t\t\tmessage = detail.message;\n\t\t\t}\n\t\t}\n\t\tsuper(message ?? `Fotovid API request failed with status ${status}`);\n\t\tthis.name = \"FotovidError\";\n\t\tthis.status = status;\n\t\tthis.detail = detail;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n","import { FotovidError } from \"./error.js\";\n\n// Injected at build time from package.json by tsup (see tsup.config.ts).\ndeclare const __SDK_VERSION__: string;\n\nexport interface RequestContext {\n\tapiKey: string;\n\tbaseUrl: string;\n\tfetch: typeof globalThis.fetch;\n}\n\nexport interface JsonRequestInit {\n\tmethod: \"GET\" | \"POST\";\n\tbody?: unknown;\n\theaders?: Record<string, string>;\n\tsignal?: AbortSignal;\n}\n\n/**\n * Shared low-level request used by both the sync (client.ts) and the async\n * (tasks.ts) surfaces — same auth, UA, and error handling either way.\n */\nexport async function requestJson<T>(\n\tctx: RequestContext,\n\tpath: string,\n\tinit: JsonRequestInit,\n): Promise<T> {\n\tconst response = await ctx.fetch(new URL(path, ctx.baseUrl), {\n\t\tmethod: init.method,\n\t\theaders: {\n\t\t\tauthorization: `Bearer ${ctx.apiKey}`,\n\t\t\t// An explicit UA identifies the SDK to the edge and to server-side\n\t\t\t// observability. (The Python SDK's default urllib UA was blocked by\n\t\t\t// Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)\n\t\t\t\"user-agent\": `fotovid-sdk/${__SDK_VERSION__}`,\n\t\t\t...(init.body !== undefined\n\t\t\t\t? { \"content-type\": \"application/json\" }\n\t\t\t\t: {}),\n\t\t\t...init.headers,\n\t\t},\n\t\tbody: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n\t\tsignal: init.signal,\n\t});\n\tif (!response.ok) {\n\t\tlet detail: unknown;\n\t\ttry {\n\t\t\tdetail = await response.json();\n\t\t} catch {\n\t\t\t// non-JSON error body — leave detail undefined\n\t\t}\n\t\tconst ra = response.headers.get(\"retry-after\");\n\t\tconst retryAfter = ra ? Number(ra) : undefined;\n\t\tthrow new FotovidError(response.status, detail, retryAfter);\n\t}\n\treturn (await response.json()) as T;\n}\n","import { type RequestContext, requestJson } from \"./http.js\";\nimport type {\n\tExtractAudioInput,\n\tImageWatermarkInput,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\n/**\n * Async task type. Deliberately excludes `\"video.meta\"` — the async worker\n * always fails it (probe has no artifact to produce); use the sync\n * `client.video.probe` instead.\n */\nexport type TaskType =\n\t| \"image.watermark\"\n\t| \"video.watermark\"\n\t| \"video.cover\"\n\t| \"video.trim\"\n\t| \"video.audio\"\n\t| \"audio.trim\";\n\nexport type TaskStatus =\n\t| \"starting\"\n\t| \"processing\"\n\t| \"succeeded\"\n\t| \"failed\"\n\t| \"canceled\";\n\nexport type WebhookEvent = \"completed\" | \"failed\";\n\n/** Stable, closed set of task-level error codes (`taskerr.ExternalCode`). */\nexport type TaskErrorCode =\n\t| \"invalid_input\"\n\t| \"unsupported_media\"\n\t| \"source_unreachable\"\n\t| \"source_too_large\"\n\t| \"unknown_task_type\"\n\t| \"insufficient_credits\"\n\t| \"processing_failed\"\n\t| \"internal_error\";\n\n/** One produced artifact. `kind` is `\"video\" | \"cover\" | \"image\" | \"audio\"`. */\nexport interface TaskOutput {\n\tkind: string;\n\turl: string;\n}\n\n/**\n * Task-level failure, carried in `Task.error` on a `\"failed\"` task. Distinct\n * from `FotovidError`, which is thrown for transport-layer failures (a\n * non-2xx response to create/get itself).\n */\nexport interface TaskError {\n\tcode: TaskErrorCode;\n\tmessage: string;\n\trequest_id: string;\n\tdetail?: string;\n}\n\nexport interface Task {\n\tid: string;\n\tstatus: TaskStatus;\n\ttask_type?: string;\n\t/** Produced artifacts, once `status` is `\"succeeded\"`. Order is not guaranteed — read by `kind`. */\n\toutputs?: TaskOutput[];\n\t/** Present when `status` is `\"failed\"`. A failed task is not an exception — check this field. */\n\terror: TaskError | null;\n\tsource: string;\n\tdata_removed: boolean;\n\tcreated_at: string;\n\tstarted_at?: string;\n\tcompleted_at?: string;\n\t/** RFC 3339, empty until the task succeeds. Output artifacts expire 7 days after completion. */\n\texpires_at?: string;\n\tmetrics?: { total_time?: number };\n\t/** `get`: this task's own polling URL — not a download link. */\n\turls: { get: string };\n}\n\nexport interface CreateTaskInput {\n\ttype: TaskType;\n\tsource_url: string;\n\tparams?: Record<string, unknown>;\n}\n\nexport interface TaskRequestOptions {\n\t/**\n\t * Idempotency key (≤64 chars). Defaults to a fresh UUID per call, like the\n\t * sync surface — unlike sync, this is a JSON body field, not a header.\n\t */\n\tidempotencyKey?: string;\n\t/** URL notified (HMAC-signed) when the task reaches a terminal state. */\n\twebhook?: string;\n\t/** Restrict which terminal states trigger the webhook. Default: all. */\n\twebhookEventsFilter?: WebhookEvent[];\n\tsignal?: AbortSignal;\n}\n\nfunction taskBody(\n\tinput: CreateTaskInput,\n\toptions: TaskRequestOptions | undefined,\n) {\n\treturn {\n\t\ttype: input.type,\n\t\tsource_url: input.source_url,\n\t\tparams: input.params,\n\t\tidempotency_key: options?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\twebhook: options?.webhook,\n\t\twebhook_events_filter: options?.webhookEventsFilter,\n\t};\n}\n\n/**\n * Async task surface: submit larger jobs than the sync endpoints accept, then\n * poll for the result. There is no built-in `wait` — poll `get` yourself:\n *\n * ```ts\n * let task = await client.tasks.video.watermark({ source_url, ...params });\n * while (task.status === \"starting\" || task.status === \"processing\") {\n * await new Promise((r) => setTimeout(r, 2000));\n * task = await client.tasks.get(task.id);\n * }\n * ```\n */\nexport class Tasks {\n\treadonly #ctx: RequestContext;\n\n\tconstructor(ctx: RequestContext) {\n\t\tthis.#ctx = ctx;\n\t}\n\n\tcreate(input: CreateTaskInput, options?: TaskRequestOptions): Promise<Task> {\n\t\treturn requestJson<Task>(this.#ctx, \"/v1/tasks\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: taskBody(input, options),\n\t\t});\n\t}\n\n\t/** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */\n\tget(id: string, options?: { signal?: AbortSignal }): Promise<Task> {\n\t\treturn requestJson<Task>(this.#ctx, `/v1/tasks/${encodeURIComponent(id)}`, {\n\t\t\tmethod: \"GET\",\n\t\t\tsignal: options?.signal,\n\t\t});\n\t}\n\n\treadonly video = {\n\t\twatermark: (input: VideoWatermarkInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.watermark\", ...splitInput(input) }, options),\n\t\ttrim: (input: TrimInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.trim\", ...splitInput(input) }, options),\n\t\textractAudio: (input: ExtractAudioInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.audio\", ...splitInput(input) }, options),\n\t\tthumbnail: (input: ThumbnailInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.cover\", ...splitInput(input) }, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (input: ImageWatermarkInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"image.watermark\", ...splitInput(input) }, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (input: TrimAudioInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"audio.trim\", ...splitInput(input) }, options),\n\t};\n}\n\n/** Splits a sync-style `{ source_url, ...params }` input into the task envelope shape. */\nfunction splitInput(input: { source_url: string }): {\n\tsource_url: string;\n\tparams: Record<string, unknown>;\n} {\n\tconst { source_url, ...params } = input;\n\treturn { source_url, params };\n}\n","import { requestJson } from \"./http.js\";\nimport { Tasks } from \"./tasks.js\";\nimport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.fotovid.co\";\n\nfunction resolveApiKey(explicit: string | undefined): string {\n\tconst key =\n\t\texplicit ??\n\t\t(typeof process !== \"undefined\" ? process.env?.FOTOVID_API_KEY : undefined);\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Fotovid: missing API key. Pass { apiKey } or set FOTOVID_API_KEY.\",\n\t\t);\n\t}\n\treturn key;\n}\n\n/**\n * Typed client for the Fotovid media API. Methods mirror the REST resources\n * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names\n * match the API 1:1 (`source_url`, `watermark_image_url`, …).\n */\nexport class Fotovid {\n\treadonly #apiKey: string;\n\treadonly #baseUrl: string;\n\treadonly #fetch: typeof globalThis.fetch;\n\n\t/** Async task surface — submit larger jobs than sync accepts, then poll for the result. */\n\treadonly tasks: Tasks;\n\n\tconstructor(options: FotovidOptions = {}) {\n\t\tthis.#apiKey = resolveApiKey(options.apiKey);\n\t\tthis.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch;\n\t\tthis.tasks = new Tasks({\n\t\t\tapiKey: this.#apiKey,\n\t\t\tbaseUrl: this.#baseUrl,\n\t\t\tfetch: this.#fetch,\n\t\t});\n\t}\n\n\t#post<T>(\n\t\tpath: string,\n\t\tinput: { source_url: string },\n\t\toptions?: RequestOptions,\n\t): Promise<T> {\n\t\tconst { source_url, ...params } = input;\n\t\treturn requestJson<T>(\n\t\t\t{ apiKey: this.#apiKey, baseUrl: this.#baseUrl, fetch: this.#fetch },\n\t\t\tpath,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: { source_url, params },\n\t\t\t\theaders: {\n\t\t\t\t\t// Billed endpoints require an idempotency key; default to a fresh\n\t\t\t\t\t// UUID per call, overridable to make a retry replay (not re-charge).\n\t\t\t\t\t\"idempotency-key\":\n\t\t\t\t\t\toptions?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\treadonly video = {\n\t\twatermark: (\n\t\t\tinput: VideoWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/watermark\", input, options),\n\t\ttrim: (input: TrimInput, options?: RequestOptions): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/trim\", input, options),\n\t\textractAudio: (\n\t\t\tinput: ExtractAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-audio\", input, options),\n\t\tthumbnail: (\n\t\t\tinput: ThumbnailInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-cover\", input, options),\n\t\tprobe: (\n\t\t\tinput: ProbeInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<ProbeResult> =>\n\t\t\tthis.#post<ProbeResult>(\"/v1/video/probe\", input, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (\n\t\t\tinput: ImageWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/image/watermark\", input, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (\n\t\t\tinput: TrimAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/audio/trim\", input, options),\n\t};\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
interface RequestContext {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
fetch: typeof globalThis.fetch;
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
/** Anchor position for a watermark overlay. */
|
|
2
8
|
type WatermarkPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center";
|
|
3
9
|
/** Output image format for image-producing operations. */
|
|
@@ -100,6 +106,106 @@ interface RequestOptions {
|
|
|
100
106
|
idempotencyKey?: string;
|
|
101
107
|
}
|
|
102
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Async task type. Deliberately excludes `"video.meta"` — the async worker
|
|
111
|
+
* always fails it (probe has no artifact to produce); use the sync
|
|
112
|
+
* `client.video.probe` instead.
|
|
113
|
+
*/
|
|
114
|
+
type TaskType = "image.watermark" | "video.watermark" | "video.cover" | "video.trim" | "video.audio" | "audio.trim";
|
|
115
|
+
type TaskStatus = "starting" | "processing" | "succeeded" | "failed" | "canceled";
|
|
116
|
+
type WebhookEvent = "completed" | "failed";
|
|
117
|
+
/** Stable, closed set of task-level error codes (`taskerr.ExternalCode`). */
|
|
118
|
+
type TaskErrorCode = "invalid_input" | "unsupported_media" | "source_unreachable" | "source_too_large" | "unknown_task_type" | "insufficient_credits" | "processing_failed" | "internal_error";
|
|
119
|
+
/** One produced artifact. `kind` is `"video" | "cover" | "image" | "audio"`. */
|
|
120
|
+
interface TaskOutput {
|
|
121
|
+
kind: string;
|
|
122
|
+
url: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Task-level failure, carried in `Task.error` on a `"failed"` task. Distinct
|
|
126
|
+
* from `FotovidError`, which is thrown for transport-layer failures (a
|
|
127
|
+
* non-2xx response to create/get itself).
|
|
128
|
+
*/
|
|
129
|
+
interface TaskError {
|
|
130
|
+
code: TaskErrorCode;
|
|
131
|
+
message: string;
|
|
132
|
+
request_id: string;
|
|
133
|
+
detail?: string;
|
|
134
|
+
}
|
|
135
|
+
interface Task {
|
|
136
|
+
id: string;
|
|
137
|
+
status: TaskStatus;
|
|
138
|
+
task_type?: string;
|
|
139
|
+
/** Produced artifacts, once `status` is `"succeeded"`. Order is not guaranteed — read by `kind`. */
|
|
140
|
+
outputs?: TaskOutput[];
|
|
141
|
+
/** Present when `status` is `"failed"`. A failed task is not an exception — check this field. */
|
|
142
|
+
error: TaskError | null;
|
|
143
|
+
source: string;
|
|
144
|
+
data_removed: boolean;
|
|
145
|
+
created_at: string;
|
|
146
|
+
started_at?: string;
|
|
147
|
+
completed_at?: string;
|
|
148
|
+
/** RFC 3339, empty until the task succeeds. Output artifacts expire 7 days after completion. */
|
|
149
|
+
expires_at?: string;
|
|
150
|
+
metrics?: {
|
|
151
|
+
total_time?: number;
|
|
152
|
+
};
|
|
153
|
+
/** `get`: this task's own polling URL — not a download link. */
|
|
154
|
+
urls: {
|
|
155
|
+
get: string;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
interface CreateTaskInput {
|
|
159
|
+
type: TaskType;
|
|
160
|
+
source_url: string;
|
|
161
|
+
params?: Record<string, unknown>;
|
|
162
|
+
}
|
|
163
|
+
interface TaskRequestOptions {
|
|
164
|
+
/**
|
|
165
|
+
* Idempotency key (≤64 chars). Defaults to a fresh UUID per call, like the
|
|
166
|
+
* sync surface — unlike sync, this is a JSON body field, not a header.
|
|
167
|
+
*/
|
|
168
|
+
idempotencyKey?: string;
|
|
169
|
+
/** URL notified (HMAC-signed) when the task reaches a terminal state. */
|
|
170
|
+
webhook?: string;
|
|
171
|
+
/** Restrict which terminal states trigger the webhook. Default: all. */
|
|
172
|
+
webhookEventsFilter?: WebhookEvent[];
|
|
173
|
+
signal?: AbortSignal;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Async task surface: submit larger jobs than the sync endpoints accept, then
|
|
177
|
+
* poll for the result. There is no built-in `wait` — poll `get` yourself:
|
|
178
|
+
*
|
|
179
|
+
* ```ts
|
|
180
|
+
* let task = await client.tasks.video.watermark({ source_url, ...params });
|
|
181
|
+
* while (task.status === "starting" || task.status === "processing") {
|
|
182
|
+
* await new Promise((r) => setTimeout(r, 2000));
|
|
183
|
+
* task = await client.tasks.get(task.id);
|
|
184
|
+
* }
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
declare class Tasks {
|
|
188
|
+
#private;
|
|
189
|
+
constructor(ctx: RequestContext);
|
|
190
|
+
create(input: CreateTaskInput, options?: TaskRequestOptions): Promise<Task>;
|
|
191
|
+
/** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */
|
|
192
|
+
get(id: string, options?: {
|
|
193
|
+
signal?: AbortSignal;
|
|
194
|
+
}): Promise<Task>;
|
|
195
|
+
readonly video: {
|
|
196
|
+
watermark: (input: VideoWatermarkInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
197
|
+
trim: (input: TrimInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
198
|
+
extractAudio: (input: ExtractAudioInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
199
|
+
thumbnail: (input: ThumbnailInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
200
|
+
};
|
|
201
|
+
readonly image: {
|
|
202
|
+
watermark: (input: ImageWatermarkInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
203
|
+
};
|
|
204
|
+
readonly audio: {
|
|
205
|
+
trim: (input: TrimAudioInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
103
209
|
/**
|
|
104
210
|
* Typed client for the Fotovid media API. Methods mirror the REST resources
|
|
105
211
|
* (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names
|
|
@@ -107,6 +213,8 @@ interface RequestOptions {
|
|
|
107
213
|
*/
|
|
108
214
|
declare class Fotovid {
|
|
109
215
|
#private;
|
|
216
|
+
/** Async task surface — submit larger jobs than sync accepts, then poll for the result. */
|
|
217
|
+
readonly tasks: Tasks;
|
|
110
218
|
constructor(options?: FotovidOptions);
|
|
111
219
|
readonly video: {
|
|
112
220
|
watermark: (input: VideoWatermarkInput, options?: RequestOptions) => Promise<MediaResult>;
|
|
@@ -134,4 +242,4 @@ declare class FotovidError extends Error {
|
|
|
134
242
|
constructor(status: number, detail: unknown, retryAfter?: number);
|
|
135
243
|
}
|
|
136
244
|
|
|
137
|
-
export { type ExtractAudioInput, Fotovid, FotovidError, type FotovidOptions, type ImageFormat, type ImageWatermarkInput, type MediaResult, type ProbeInput, type ProbeResult, type RequestOptions, type ThumbnailInput, type TrimAudioInput, type TrimInput, type VideoWatermarkInput, type WatermarkPosition, type WatermarkType, type X264Preset, Fotovid as default };
|
|
245
|
+
export { type CreateTaskInput, type ExtractAudioInput, Fotovid, FotovidError, type FotovidOptions, type ImageFormat, type ImageWatermarkInput, type MediaResult, type ProbeInput, type ProbeResult, type RequestOptions, type Task, type TaskError, type TaskErrorCode, type TaskOutput, type TaskRequestOptions, type TaskStatus, type TaskType, Tasks, type ThumbnailInput, type TrimAudioInput, type TrimInput, type VideoWatermarkInput, type WatermarkPosition, type WatermarkType, type WebhookEvent, type X264Preset, Fotovid as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
interface RequestContext {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
fetch: typeof globalThis.fetch;
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
/** Anchor position for a watermark overlay. */
|
|
2
8
|
type WatermarkPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center";
|
|
3
9
|
/** Output image format for image-producing operations. */
|
|
@@ -100,6 +106,106 @@ interface RequestOptions {
|
|
|
100
106
|
idempotencyKey?: string;
|
|
101
107
|
}
|
|
102
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Async task type. Deliberately excludes `"video.meta"` — the async worker
|
|
111
|
+
* always fails it (probe has no artifact to produce); use the sync
|
|
112
|
+
* `client.video.probe` instead.
|
|
113
|
+
*/
|
|
114
|
+
type TaskType = "image.watermark" | "video.watermark" | "video.cover" | "video.trim" | "video.audio" | "audio.trim";
|
|
115
|
+
type TaskStatus = "starting" | "processing" | "succeeded" | "failed" | "canceled";
|
|
116
|
+
type WebhookEvent = "completed" | "failed";
|
|
117
|
+
/** Stable, closed set of task-level error codes (`taskerr.ExternalCode`). */
|
|
118
|
+
type TaskErrorCode = "invalid_input" | "unsupported_media" | "source_unreachable" | "source_too_large" | "unknown_task_type" | "insufficient_credits" | "processing_failed" | "internal_error";
|
|
119
|
+
/** One produced artifact. `kind` is `"video" | "cover" | "image" | "audio"`. */
|
|
120
|
+
interface TaskOutput {
|
|
121
|
+
kind: string;
|
|
122
|
+
url: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Task-level failure, carried in `Task.error` on a `"failed"` task. Distinct
|
|
126
|
+
* from `FotovidError`, which is thrown for transport-layer failures (a
|
|
127
|
+
* non-2xx response to create/get itself).
|
|
128
|
+
*/
|
|
129
|
+
interface TaskError {
|
|
130
|
+
code: TaskErrorCode;
|
|
131
|
+
message: string;
|
|
132
|
+
request_id: string;
|
|
133
|
+
detail?: string;
|
|
134
|
+
}
|
|
135
|
+
interface Task {
|
|
136
|
+
id: string;
|
|
137
|
+
status: TaskStatus;
|
|
138
|
+
task_type?: string;
|
|
139
|
+
/** Produced artifacts, once `status` is `"succeeded"`. Order is not guaranteed — read by `kind`. */
|
|
140
|
+
outputs?: TaskOutput[];
|
|
141
|
+
/** Present when `status` is `"failed"`. A failed task is not an exception — check this field. */
|
|
142
|
+
error: TaskError | null;
|
|
143
|
+
source: string;
|
|
144
|
+
data_removed: boolean;
|
|
145
|
+
created_at: string;
|
|
146
|
+
started_at?: string;
|
|
147
|
+
completed_at?: string;
|
|
148
|
+
/** RFC 3339, empty until the task succeeds. Output artifacts expire 7 days after completion. */
|
|
149
|
+
expires_at?: string;
|
|
150
|
+
metrics?: {
|
|
151
|
+
total_time?: number;
|
|
152
|
+
};
|
|
153
|
+
/** `get`: this task's own polling URL — not a download link. */
|
|
154
|
+
urls: {
|
|
155
|
+
get: string;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
interface CreateTaskInput {
|
|
159
|
+
type: TaskType;
|
|
160
|
+
source_url: string;
|
|
161
|
+
params?: Record<string, unknown>;
|
|
162
|
+
}
|
|
163
|
+
interface TaskRequestOptions {
|
|
164
|
+
/**
|
|
165
|
+
* Idempotency key (≤64 chars). Defaults to a fresh UUID per call, like the
|
|
166
|
+
* sync surface — unlike sync, this is a JSON body field, not a header.
|
|
167
|
+
*/
|
|
168
|
+
idempotencyKey?: string;
|
|
169
|
+
/** URL notified (HMAC-signed) when the task reaches a terminal state. */
|
|
170
|
+
webhook?: string;
|
|
171
|
+
/** Restrict which terminal states trigger the webhook. Default: all. */
|
|
172
|
+
webhookEventsFilter?: WebhookEvent[];
|
|
173
|
+
signal?: AbortSignal;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Async task surface: submit larger jobs than the sync endpoints accept, then
|
|
177
|
+
* poll for the result. There is no built-in `wait` — poll `get` yourself:
|
|
178
|
+
*
|
|
179
|
+
* ```ts
|
|
180
|
+
* let task = await client.tasks.video.watermark({ source_url, ...params });
|
|
181
|
+
* while (task.status === "starting" || task.status === "processing") {
|
|
182
|
+
* await new Promise((r) => setTimeout(r, 2000));
|
|
183
|
+
* task = await client.tasks.get(task.id);
|
|
184
|
+
* }
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
declare class Tasks {
|
|
188
|
+
#private;
|
|
189
|
+
constructor(ctx: RequestContext);
|
|
190
|
+
create(input: CreateTaskInput, options?: TaskRequestOptions): Promise<Task>;
|
|
191
|
+
/** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */
|
|
192
|
+
get(id: string, options?: {
|
|
193
|
+
signal?: AbortSignal;
|
|
194
|
+
}): Promise<Task>;
|
|
195
|
+
readonly video: {
|
|
196
|
+
watermark: (input: VideoWatermarkInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
197
|
+
trim: (input: TrimInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
198
|
+
extractAudio: (input: ExtractAudioInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
199
|
+
thumbnail: (input: ThumbnailInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
200
|
+
};
|
|
201
|
+
readonly image: {
|
|
202
|
+
watermark: (input: ImageWatermarkInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
203
|
+
};
|
|
204
|
+
readonly audio: {
|
|
205
|
+
trim: (input: TrimAudioInput, options?: TaskRequestOptions) => Promise<Task>;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
103
209
|
/**
|
|
104
210
|
* Typed client for the Fotovid media API. Methods mirror the REST resources
|
|
105
211
|
* (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names
|
|
@@ -107,6 +213,8 @@ interface RequestOptions {
|
|
|
107
213
|
*/
|
|
108
214
|
declare class Fotovid {
|
|
109
215
|
#private;
|
|
216
|
+
/** Async task surface — submit larger jobs than sync accepts, then poll for the result. */
|
|
217
|
+
readonly tasks: Tasks;
|
|
110
218
|
constructor(options?: FotovidOptions);
|
|
111
219
|
readonly video: {
|
|
112
220
|
watermark: (input: VideoWatermarkInput, options?: RequestOptions) => Promise<MediaResult>;
|
|
@@ -134,4 +242,4 @@ declare class FotovidError extends Error {
|
|
|
134
242
|
constructor(status: number, detail: unknown, retryAfter?: number);
|
|
135
243
|
}
|
|
136
244
|
|
|
137
|
-
export { type ExtractAudioInput, Fotovid, FotovidError, type FotovidOptions, type ImageFormat, type ImageWatermarkInput, type MediaResult, type ProbeInput, type ProbeResult, type RequestOptions, type ThumbnailInput, type TrimAudioInput, type TrimInput, type VideoWatermarkInput, type WatermarkPosition, type WatermarkType, type X264Preset, Fotovid as default };
|
|
245
|
+
export { type CreateTaskInput, type ExtractAudioInput, Fotovid, FotovidError, type FotovidOptions, type ImageFormat, type ImageWatermarkInput, type MediaResult, type ProbeInput, type ProbeResult, type RequestOptions, type Task, type TaskError, type TaskErrorCode, type TaskOutput, type TaskRequestOptions, type TaskStatus, type TaskType, Tasks, type ThumbnailInput, type TrimAudioInput, type TrimInput, type VideoWatermarkInput, type WatermarkPosition, type WatermarkType, type WebhookEvent, type X264Preset, Fotovid as default };
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,82 @@ var FotovidError = class extends Error {
|
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
// src/http.ts
|
|
30
|
+
async function requestJson(ctx, path, init) {
|
|
31
|
+
const response = await ctx.fetch(new URL(path, ctx.baseUrl), {
|
|
32
|
+
method: init.method,
|
|
33
|
+
headers: {
|
|
34
|
+
authorization: `Bearer ${ctx.apiKey}`,
|
|
35
|
+
// An explicit UA identifies the SDK to the edge and to server-side
|
|
36
|
+
// observability. (The Python SDK's default urllib UA was blocked by
|
|
37
|
+
// Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)
|
|
38
|
+
"user-agent": `fotovid-sdk/${"0.2.0"}`,
|
|
39
|
+
...init.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
40
|
+
...init.headers
|
|
41
|
+
},
|
|
42
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
|
|
43
|
+
signal: init.signal
|
|
44
|
+
});
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
let detail;
|
|
47
|
+
try {
|
|
48
|
+
detail = await response.json();
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
const ra = response.headers.get("retry-after");
|
|
52
|
+
const retryAfter = ra ? Number(ra) : void 0;
|
|
53
|
+
throw new FotovidError(response.status, detail, retryAfter);
|
|
54
|
+
}
|
|
55
|
+
return await response.json();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/tasks.ts
|
|
59
|
+
function taskBody(input, options) {
|
|
60
|
+
return {
|
|
61
|
+
type: input.type,
|
|
62
|
+
source_url: input.source_url,
|
|
63
|
+
params: input.params,
|
|
64
|
+
idempotency_key: options?.idempotencyKey ?? globalThis.crypto.randomUUID(),
|
|
65
|
+
webhook: options?.webhook,
|
|
66
|
+
webhook_events_filter: options?.webhookEventsFilter
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
var Tasks = class {
|
|
70
|
+
#ctx;
|
|
71
|
+
constructor(ctx) {
|
|
72
|
+
this.#ctx = ctx;
|
|
73
|
+
}
|
|
74
|
+
create(input, options) {
|
|
75
|
+
return requestJson(this.#ctx, "/v1/tasks", {
|
|
76
|
+
method: "POST",
|
|
77
|
+
body: taskBody(input, options)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */
|
|
81
|
+
get(id, options) {
|
|
82
|
+
return requestJson(this.#ctx, `/v1/tasks/${encodeURIComponent(id)}`, {
|
|
83
|
+
method: "GET",
|
|
84
|
+
signal: options?.signal
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
video = {
|
|
88
|
+
watermark: (input, options) => this.create({ type: "video.watermark", ...splitInput(input) }, options),
|
|
89
|
+
trim: (input, options) => this.create({ type: "video.trim", ...splitInput(input) }, options),
|
|
90
|
+
extractAudio: (input, options) => this.create({ type: "video.audio", ...splitInput(input) }, options),
|
|
91
|
+
thumbnail: (input, options) => this.create({ type: "video.cover", ...splitInput(input) }, options)
|
|
92
|
+
};
|
|
93
|
+
image = {
|
|
94
|
+
watermark: (input, options) => this.create({ type: "image.watermark", ...splitInput(input) }, options)
|
|
95
|
+
};
|
|
96
|
+
audio = {
|
|
97
|
+
trim: (input, options) => this.create({ type: "audio.trim", ...splitInput(input) }, options)
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
function splitInput(input) {
|
|
101
|
+
const { source_url, ...params } = input;
|
|
102
|
+
return { source_url, params };
|
|
103
|
+
}
|
|
104
|
+
|
|
29
105
|
// src/client.ts
|
|
30
106
|
var DEFAULT_BASE_URL = "https://api.fotovid.co";
|
|
31
107
|
function resolveApiKey(explicit) {
|
|
@@ -41,39 +117,33 @@ var Fotovid = class {
|
|
|
41
117
|
#apiKey;
|
|
42
118
|
#baseUrl;
|
|
43
119
|
#fetch;
|
|
120
|
+
/** Async task surface — submit larger jobs than sync accepts, then poll for the result. */
|
|
121
|
+
tasks;
|
|
44
122
|
constructor(options = {}) {
|
|
45
123
|
this.#apiKey = resolveApiKey(options.apiKey);
|
|
46
124
|
this.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
47
125
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
126
|
+
this.tasks = new Tasks({
|
|
127
|
+
apiKey: this.#apiKey,
|
|
128
|
+
baseUrl: this.#baseUrl,
|
|
129
|
+
fetch: this.#fetch
|
|
130
|
+
});
|
|
48
131
|
}
|
|
49
|
-
|
|
132
|
+
#post(path, input, options) {
|
|
50
133
|
const { source_url, ...params } = input;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"idempotency-key": options?.idempotencyKey ?? globalThis.crypto.randomUUID()
|
|
63
|
-
},
|
|
64
|
-
body: JSON.stringify({ source_url, params })
|
|
65
|
-
});
|
|
66
|
-
if (!response.ok) {
|
|
67
|
-
let detail;
|
|
68
|
-
try {
|
|
69
|
-
detail = await response.json();
|
|
70
|
-
} catch {
|
|
134
|
+
return requestJson(
|
|
135
|
+
{ apiKey: this.#apiKey, baseUrl: this.#baseUrl, fetch: this.#fetch },
|
|
136
|
+
path,
|
|
137
|
+
{
|
|
138
|
+
method: "POST",
|
|
139
|
+
body: { source_url, params },
|
|
140
|
+
headers: {
|
|
141
|
+
// Billed endpoints require an idempotency key; default to a fresh
|
|
142
|
+
// UUID per call, overridable to make a retry replay (not re-charge).
|
|
143
|
+
"idempotency-key": options?.idempotencyKey ?? globalThis.crypto.randomUUID()
|
|
144
|
+
}
|
|
71
145
|
}
|
|
72
|
-
|
|
73
|
-
const retryAfter = ra ? Number(ra) : void 0;
|
|
74
|
-
throw new FotovidError(response.status, detail, retryAfter);
|
|
75
|
-
}
|
|
76
|
-
return await response.json();
|
|
146
|
+
);
|
|
77
147
|
}
|
|
78
148
|
video = {
|
|
79
149
|
watermark: (input, options) => this.#post("/v1/video/watermark", input, options),
|
|
@@ -92,6 +162,7 @@ var Fotovid = class {
|
|
|
92
162
|
export {
|
|
93
163
|
Fotovid,
|
|
94
164
|
FotovidError,
|
|
165
|
+
Tasks,
|
|
95
166
|
Fotovid as default
|
|
96
167
|
};
|
|
97
168
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/error.ts","../src/client.ts"],"sourcesContent":["function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/** Thrown when the Fotovid API returns a non-2xx response. */\nexport class FotovidError extends Error {\n\t/** HTTP status code of the failed response. */\n\treadonly status: number;\n\t/** Parsed error body, when the response carried one. */\n\treadonly detail: unknown;\n\t/** Seconds to wait before retrying, from the `Retry-After` header (e.g. 429/503). */\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, detail: unknown, retryAfter?: number) {\n\t\tlet message: string | undefined;\n\t\tif (isRecord(detail)) {\n\t\t\tif (typeof detail.detail === \"string\") {\n\t\t\t\tmessage = detail.detail;\n\t\t\t} else if (typeof detail.message === \"string\") {\n\t\t\t\tmessage = detail.message;\n\t\t\t}\n\t\t}\n\t\tsuper(message ?? `Fotovid API request failed with status ${status}`);\n\t\tthis.name = \"FotovidError\";\n\t\tthis.status = status;\n\t\tthis.detail = detail;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n","import { FotovidError } from \"./error.js\";\nimport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.fotovid.co\";\n\n// Injected at build time from package.json by tsup (see tsup.config.ts).\ndeclare const __SDK_VERSION__: string;\n\nfunction resolveApiKey(explicit: string | undefined): string {\n\tconst key =\n\t\texplicit ??\n\t\t(typeof process !== \"undefined\" ? process.env?.FOTOVID_API_KEY : undefined);\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Fotovid: missing API key. Pass { apiKey } or set FOTOVID_API_KEY.\",\n\t\t);\n\t}\n\treturn key;\n}\n\n/**\n * Typed client for the Fotovid media API. Methods mirror the REST resources\n * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names\n * match the API 1:1 (`source_url`, `watermark_image_url`, …).\n */\nexport class Fotovid {\n\treadonly #apiKey: string;\n\treadonly #baseUrl: string;\n\treadonly #fetch: typeof globalThis.fetch;\n\n\tconstructor(options: FotovidOptions = {}) {\n\t\tthis.#apiKey = resolveApiKey(options.apiKey);\n\t\tthis.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch;\n\t}\n\n\tasync #post<T>(\n\t\tpath: string,\n\t\tinput: { source_url: string },\n\t\toptions?: RequestOptions,\n\t): Promise<T> {\n\t\tconst { source_url, ...params } = input;\n\t\tconst response = await this.#fetch(new URL(path, this.#baseUrl), {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\tauthorization: `Bearer ${this.#apiKey}`,\n\t\t\t\t// An explicit UA identifies the SDK to the edge and to server-side\n\t\t\t\t// observability. (The Python SDK's default urllib UA was blocked by\n\t\t\t\t// Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)\n\t\t\t\t\"user-agent\": `fotovid-sdk/${__SDK_VERSION__}`,\n\t\t\t\t// Billed endpoints require an idempotency key; default to a fresh\n\t\t\t\t// UUID per call, overridable to make a retry replay (not re-charge).\n\t\t\t\t\"idempotency-key\":\n\t\t\t\t\toptions?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\t\t},\n\t\t\tbody: JSON.stringify({ source_url, params }),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tlet detail: unknown;\n\t\t\ttry {\n\t\t\t\tdetail = await response.json();\n\t\t\t} catch {\n\t\t\t\t// non-JSON error body — leave detail undefined\n\t\t\t}\n\t\t\tconst ra = response.headers.get(\"retry-after\");\n\t\t\tconst retryAfter = ra ? Number(ra) : undefined;\n\t\t\tthrow new FotovidError(response.status, detail, retryAfter);\n\t\t}\n\t\treturn (await response.json()) as T;\n\t}\n\n\treadonly video = {\n\t\twatermark: (\n\t\t\tinput: VideoWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/watermark\", input, options),\n\t\ttrim: (input: TrimInput, options?: RequestOptions): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/trim\", input, options),\n\t\textractAudio: (\n\t\t\tinput: ExtractAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-audio\", input, options),\n\t\tthumbnail: (\n\t\t\tinput: ThumbnailInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-cover\", input, options),\n\t\tprobe: (\n\t\t\tinput: ProbeInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<ProbeResult> =>\n\t\t\tthis.#post<ProbeResult>(\"/v1/video/probe\", input, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (\n\t\t\tinput: ImageWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/image/watermark\", input, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (\n\t\t\tinput: TrimAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/audio/trim\", input, options),\n\t};\n}\n"],"mappings":";AAAA,SAAS,SAAS,OAAkD;AACnE,SAAO,OAAO,UAAU,YAAY,UAAU;AAC/C;AAGO,IAAM,eAAN,cAA2B,MAAM;AAAA;AAAA,EAE9B;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,QAAgB,QAAiB,YAAqB;AACjE,QAAI;AACJ,QAAI,SAAS,MAAM,GAAG;AACrB,UAAI,OAAO,OAAO,WAAW,UAAU;AACtC,kBAAU,OAAO;AAAA,MAClB,WAAW,OAAO,OAAO,YAAY,UAAU;AAC9C,kBAAU,OAAO;AAAA,MAClB;AAAA,IACD;AACA,UAAM,WAAW,0CAA0C,MAAM,EAAE;AACnE,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,aAAa;AAAA,EACnB;AACD;;;ACbA,IAAM,mBAAmB;AAKzB,SAAS,cAAc,UAAsC;AAC5D,QAAM,MACL,aACC,OAAO,YAAY,cAAc,QAAQ,KAAK,kBAAkB;AAClE,MAAI,CAAC,KAAK;AACT,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAOO,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,UAA0B,CAAC,GAAG;AACzC,SAAK,UAAU,cAAc,QAAQ,MAAM;AAC3C,SAAK,WAAW,QAAQ,WAAW;AACnC,SAAK,SAAS,QAAQ,SAAS,WAAW;AAAA,EAC3C;AAAA,EAEA,MAAM,MACL,MACA,OACA,SACa;AACb,UAAM,EAAE,YAAY,GAAG,OAAO,IAAI;AAClC,UAAM,WAAW,MAAM,KAAK,OAAO,IAAI,IAAI,MAAM,KAAK,QAAQ,GAAG;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACR,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK,OAAO;AAAA;AAAA;AAAA;AAAA,QAIrC,cAAc,eAAe,OAAe;AAAA;AAAA;AAAA,QAG5C,mBACC,SAAS,kBAAkB,WAAW,OAAO,WAAW;AAAA,MAC1D;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,YAAY,OAAO,CAAC;AAAA,IAC5C,CAAC;AACD,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI;AACJ,UAAI;AACH,iBAAS,MAAM,SAAS,KAAK;AAAA,MAC9B,QAAQ;AAAA,MAER;AACA,YAAM,KAAK,SAAS,QAAQ,IAAI,aAAa;AAC7C,YAAM,aAAa,KAAK,OAAO,EAAE,IAAI;AACrC,YAAM,IAAI,aAAa,SAAS,QAAQ,QAAQ,UAAU;AAAA,IAC3D;AACA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC7B;AAAA,EAES,QAAQ;AAAA,IAChB,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,uBAAuB,OAAO,OAAO;AAAA,IAC9D,MAAM,CAAC,OAAkB,YACxB,KAAK,MAAmB,kBAAkB,OAAO,OAAO;AAAA,IACzD,cAAc,CACb,OACA,YAEA,KAAK,MAAmB,2BAA2B,OAAO,OAAO;AAAA,IAClE,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,2BAA2B,OAAO,OAAO;AAAA,IAClE,OAAO,CACN,OACA,YAEA,KAAK,MAAmB,mBAAmB,OAAO,OAAO;AAAA,EAC3D;AAAA,EAES,QAAQ;AAAA,IAChB,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,uBAAuB,OAAO,OAAO;AAAA,EAC/D;AAAA,EAES,QAAQ;AAAA,IAChB,MAAM,CACL,OACA,YAEA,KAAK,MAAmB,kBAAkB,OAAO,OAAO;AAAA,EAC1D;AACD;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/error.ts","../src/http.ts","../src/tasks.ts","../src/client.ts"],"sourcesContent":["function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/** Thrown when the Fotovid API returns a non-2xx response. */\nexport class FotovidError extends Error {\n\t/** HTTP status code of the failed response. */\n\treadonly status: number;\n\t/** Parsed error body, when the response carried one. */\n\treadonly detail: unknown;\n\t/** Seconds to wait before retrying, from the `Retry-After` header (e.g. 429/503). */\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, detail: unknown, retryAfter?: number) {\n\t\tlet message: string | undefined;\n\t\tif (isRecord(detail)) {\n\t\t\tif (typeof detail.detail === \"string\") {\n\t\t\t\tmessage = detail.detail;\n\t\t\t} else if (typeof detail.message === \"string\") {\n\t\t\t\tmessage = detail.message;\n\t\t\t}\n\t\t}\n\t\tsuper(message ?? `Fotovid API request failed with status ${status}`);\n\t\tthis.name = \"FotovidError\";\n\t\tthis.status = status;\n\t\tthis.detail = detail;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n","import { FotovidError } from \"./error.js\";\n\n// Injected at build time from package.json by tsup (see tsup.config.ts).\ndeclare const __SDK_VERSION__: string;\n\nexport interface RequestContext {\n\tapiKey: string;\n\tbaseUrl: string;\n\tfetch: typeof globalThis.fetch;\n}\n\nexport interface JsonRequestInit {\n\tmethod: \"GET\" | \"POST\";\n\tbody?: unknown;\n\theaders?: Record<string, string>;\n\tsignal?: AbortSignal;\n}\n\n/**\n * Shared low-level request used by both the sync (client.ts) and the async\n * (tasks.ts) surfaces — same auth, UA, and error handling either way.\n */\nexport async function requestJson<T>(\n\tctx: RequestContext,\n\tpath: string,\n\tinit: JsonRequestInit,\n): Promise<T> {\n\tconst response = await ctx.fetch(new URL(path, ctx.baseUrl), {\n\t\tmethod: init.method,\n\t\theaders: {\n\t\t\tauthorization: `Bearer ${ctx.apiKey}`,\n\t\t\t// An explicit UA identifies the SDK to the edge and to server-side\n\t\t\t// observability. (The Python SDK's default urllib UA was blocked by\n\t\t\t// Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)\n\t\t\t\"user-agent\": `fotovid-sdk/${__SDK_VERSION__}`,\n\t\t\t...(init.body !== undefined\n\t\t\t\t? { \"content-type\": \"application/json\" }\n\t\t\t\t: {}),\n\t\t\t...init.headers,\n\t\t},\n\t\tbody: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n\t\tsignal: init.signal,\n\t});\n\tif (!response.ok) {\n\t\tlet detail: unknown;\n\t\ttry {\n\t\t\tdetail = await response.json();\n\t\t} catch {\n\t\t\t// non-JSON error body — leave detail undefined\n\t\t}\n\t\tconst ra = response.headers.get(\"retry-after\");\n\t\tconst retryAfter = ra ? Number(ra) : undefined;\n\t\tthrow new FotovidError(response.status, detail, retryAfter);\n\t}\n\treturn (await response.json()) as T;\n}\n","import { type RequestContext, requestJson } from \"./http.js\";\nimport type {\n\tExtractAudioInput,\n\tImageWatermarkInput,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\n/**\n * Async task type. Deliberately excludes `\"video.meta\"` — the async worker\n * always fails it (probe has no artifact to produce); use the sync\n * `client.video.probe` instead.\n */\nexport type TaskType =\n\t| \"image.watermark\"\n\t| \"video.watermark\"\n\t| \"video.cover\"\n\t| \"video.trim\"\n\t| \"video.audio\"\n\t| \"audio.trim\";\n\nexport type TaskStatus =\n\t| \"starting\"\n\t| \"processing\"\n\t| \"succeeded\"\n\t| \"failed\"\n\t| \"canceled\";\n\nexport type WebhookEvent = \"completed\" | \"failed\";\n\n/** Stable, closed set of task-level error codes (`taskerr.ExternalCode`). */\nexport type TaskErrorCode =\n\t| \"invalid_input\"\n\t| \"unsupported_media\"\n\t| \"source_unreachable\"\n\t| \"source_too_large\"\n\t| \"unknown_task_type\"\n\t| \"insufficient_credits\"\n\t| \"processing_failed\"\n\t| \"internal_error\";\n\n/** One produced artifact. `kind` is `\"video\" | \"cover\" | \"image\" | \"audio\"`. */\nexport interface TaskOutput {\n\tkind: string;\n\turl: string;\n}\n\n/**\n * Task-level failure, carried in `Task.error` on a `\"failed\"` task. Distinct\n * from `FotovidError`, which is thrown for transport-layer failures (a\n * non-2xx response to create/get itself).\n */\nexport interface TaskError {\n\tcode: TaskErrorCode;\n\tmessage: string;\n\trequest_id: string;\n\tdetail?: string;\n}\n\nexport interface Task {\n\tid: string;\n\tstatus: TaskStatus;\n\ttask_type?: string;\n\t/** Produced artifacts, once `status` is `\"succeeded\"`. Order is not guaranteed — read by `kind`. */\n\toutputs?: TaskOutput[];\n\t/** Present when `status` is `\"failed\"`. A failed task is not an exception — check this field. */\n\terror: TaskError | null;\n\tsource: string;\n\tdata_removed: boolean;\n\tcreated_at: string;\n\tstarted_at?: string;\n\tcompleted_at?: string;\n\t/** RFC 3339, empty until the task succeeds. Output artifacts expire 7 days after completion. */\n\texpires_at?: string;\n\tmetrics?: { total_time?: number };\n\t/** `get`: this task's own polling URL — not a download link. */\n\turls: { get: string };\n}\n\nexport interface CreateTaskInput {\n\ttype: TaskType;\n\tsource_url: string;\n\tparams?: Record<string, unknown>;\n}\n\nexport interface TaskRequestOptions {\n\t/**\n\t * Idempotency key (≤64 chars). Defaults to a fresh UUID per call, like the\n\t * sync surface — unlike sync, this is a JSON body field, not a header.\n\t */\n\tidempotencyKey?: string;\n\t/** URL notified (HMAC-signed) when the task reaches a terminal state. */\n\twebhook?: string;\n\t/** Restrict which terminal states trigger the webhook. Default: all. */\n\twebhookEventsFilter?: WebhookEvent[];\n\tsignal?: AbortSignal;\n}\n\nfunction taskBody(\n\tinput: CreateTaskInput,\n\toptions: TaskRequestOptions | undefined,\n) {\n\treturn {\n\t\ttype: input.type,\n\t\tsource_url: input.source_url,\n\t\tparams: input.params,\n\t\tidempotency_key: options?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\twebhook: options?.webhook,\n\t\twebhook_events_filter: options?.webhookEventsFilter,\n\t};\n}\n\n/**\n * Async task surface: submit larger jobs than the sync endpoints accept, then\n * poll for the result. There is no built-in `wait` — poll `get` yourself:\n *\n * ```ts\n * let task = await client.tasks.video.watermark({ source_url, ...params });\n * while (task.status === \"starting\" || task.status === \"processing\") {\n * await new Promise((r) => setTimeout(r, 2000));\n * task = await client.tasks.get(task.id);\n * }\n * ```\n */\nexport class Tasks {\n\treadonly #ctx: RequestContext;\n\n\tconstructor(ctx: RequestContext) {\n\t\tthis.#ctx = ctx;\n\t}\n\n\tcreate(input: CreateTaskInput, options?: TaskRequestOptions): Promise<Task> {\n\t\treturn requestJson<Task>(this.#ctx, \"/v1/tasks\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: taskBody(input, options),\n\t\t});\n\t}\n\n\t/** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */\n\tget(id: string, options?: { signal?: AbortSignal }): Promise<Task> {\n\t\treturn requestJson<Task>(this.#ctx, `/v1/tasks/${encodeURIComponent(id)}`, {\n\t\t\tmethod: \"GET\",\n\t\t\tsignal: options?.signal,\n\t\t});\n\t}\n\n\treadonly video = {\n\t\twatermark: (input: VideoWatermarkInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.watermark\", ...splitInput(input) }, options),\n\t\ttrim: (input: TrimInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.trim\", ...splitInput(input) }, options),\n\t\textractAudio: (input: ExtractAudioInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.audio\", ...splitInput(input) }, options),\n\t\tthumbnail: (input: ThumbnailInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.cover\", ...splitInput(input) }, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (input: ImageWatermarkInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"image.watermark\", ...splitInput(input) }, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (input: TrimAudioInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"audio.trim\", ...splitInput(input) }, options),\n\t};\n}\n\n/** Splits a sync-style `{ source_url, ...params }` input into the task envelope shape. */\nfunction splitInput(input: { source_url: string }): {\n\tsource_url: string;\n\tparams: Record<string, unknown>;\n} {\n\tconst { source_url, ...params } = input;\n\treturn { source_url, params };\n}\n","import { requestJson } from \"./http.js\";\nimport { Tasks } from \"./tasks.js\";\nimport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.fotovid.co\";\n\nfunction resolveApiKey(explicit: string | undefined): string {\n\tconst key =\n\t\texplicit ??\n\t\t(typeof process !== \"undefined\" ? process.env?.FOTOVID_API_KEY : undefined);\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Fotovid: missing API key. Pass { apiKey } or set FOTOVID_API_KEY.\",\n\t\t);\n\t}\n\treturn key;\n}\n\n/**\n * Typed client for the Fotovid media API. Methods mirror the REST resources\n * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names\n * match the API 1:1 (`source_url`, `watermark_image_url`, …).\n */\nexport class Fotovid {\n\treadonly #apiKey: string;\n\treadonly #baseUrl: string;\n\treadonly #fetch: typeof globalThis.fetch;\n\n\t/** Async task surface — submit larger jobs than sync accepts, then poll for the result. */\n\treadonly tasks: Tasks;\n\n\tconstructor(options: FotovidOptions = {}) {\n\t\tthis.#apiKey = resolveApiKey(options.apiKey);\n\t\tthis.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch;\n\t\tthis.tasks = new Tasks({\n\t\t\tapiKey: this.#apiKey,\n\t\t\tbaseUrl: this.#baseUrl,\n\t\t\tfetch: this.#fetch,\n\t\t});\n\t}\n\n\t#post<T>(\n\t\tpath: string,\n\t\tinput: { source_url: string },\n\t\toptions?: RequestOptions,\n\t): Promise<T> {\n\t\tconst { source_url, ...params } = input;\n\t\treturn requestJson<T>(\n\t\t\t{ apiKey: this.#apiKey, baseUrl: this.#baseUrl, fetch: this.#fetch },\n\t\t\tpath,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: { source_url, params },\n\t\t\t\theaders: {\n\t\t\t\t\t// Billed endpoints require an idempotency key; default to a fresh\n\t\t\t\t\t// UUID per call, overridable to make a retry replay (not re-charge).\n\t\t\t\t\t\"idempotency-key\":\n\t\t\t\t\t\toptions?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\treadonly video = {\n\t\twatermark: (\n\t\t\tinput: VideoWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/watermark\", input, options),\n\t\ttrim: (input: TrimInput, options?: RequestOptions): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/trim\", input, options),\n\t\textractAudio: (\n\t\t\tinput: ExtractAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-audio\", input, options),\n\t\tthumbnail: (\n\t\t\tinput: ThumbnailInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-cover\", input, options),\n\t\tprobe: (\n\t\t\tinput: ProbeInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<ProbeResult> =>\n\t\t\tthis.#post<ProbeResult>(\"/v1/video/probe\", input, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (\n\t\t\tinput: ImageWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/image/watermark\", input, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (\n\t\t\tinput: TrimAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/audio/trim\", input, options),\n\t};\n}\n"],"mappings":"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","names":[]}
|