@convertrilo/sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/src/index.d.ts +194 -0
- package/dist/src/index.js +215 -0
- package/dist/src/types.d.ts +1857 -0
- package/dist/src/types.js +5 -0
- package/examples/folder-ingest-s3.ts +51 -0
- package/examples/google-drive-byo-token.ts +37 -0
- package/examples/node-url-to-cdn.ts +41 -0
- package/examples/node-url-to-s3.ts +40 -0
- package/openapi.yaml +1132 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Convertrilo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Convertrilo TypeScript SDK
|
|
2
|
+
|
|
3
|
+
Type-safe client for the Convertrilo API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @convertrilo/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package currently targets modern Node.js runtimes with global `fetch`. If your runtime does
|
|
12
|
+
not provide `fetch`, pass `fetchImpl` to the client.
|
|
13
|
+
|
|
14
|
+
## Create A Client
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { ConvertriloClient } from "@convertrilo/sdk";
|
|
18
|
+
|
|
19
|
+
const client = new ConvertriloClient({
|
|
20
|
+
baseUrl: "https://api.convertrilo.com",
|
|
21
|
+
apiKey: process.env.CONVERTRILO_API_KEY,
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Use an API key for server-to-server integrations. Browser apps should call your own backend,
|
|
26
|
+
then your backend calls Convertrilo.
|
|
27
|
+
|
|
28
|
+
## Examples
|
|
29
|
+
|
|
30
|
+
The `examples/` directory contains starter scripts for the main integration paths:
|
|
31
|
+
|
|
32
|
+
- `node-url-to-cdn.ts` - encode a public URL and receive a signed CDN download URL
|
|
33
|
+
- `node-url-to-s3.ts` - encode a public URL and upload the result to S3/S3-compatible storage
|
|
34
|
+
- `google-drive-byo-token.ts` - upload output to Google Drive using customer OAuth tokens from your app
|
|
35
|
+
- `folder-ingest-s3.ts` - queue one encode job per video in an S3 prefix
|
|
36
|
+
|
|
37
|
+
Local SDK smoke tests use `.env`, but published SDK users should provide credentials through their
|
|
38
|
+
own server environment. Do not put Convertrilo API keys or customer storage tokens in frontend code.
|
|
39
|
+
|
|
40
|
+
## URL Source To CDN Output
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const job = await client.onDemandEncode({
|
|
44
|
+
sourceUrl: "https://example.com/input.mp4",
|
|
45
|
+
codec: "h264",
|
|
46
|
+
resolution: "1080p",
|
|
47
|
+
quality: "better",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let finalStatus;
|
|
51
|
+
while (true) {
|
|
52
|
+
finalStatus = await client.onDemandStatus(job.jobId);
|
|
53
|
+
|
|
54
|
+
if (finalStatus.status === "success") break;
|
|
55
|
+
if (finalStatus.status === "failed") {
|
|
56
|
+
throw new Error(finalStatus.failureMessage || "Encoding failed");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(finalStatus.downloadUrl);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## URL Source To S3 Output
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const job = await client.onDemandEncode({
|
|
69
|
+
sourceUrl: "https://example.com/input.mp4",
|
|
70
|
+
codec: "h264",
|
|
71
|
+
resolution: "1080p",
|
|
72
|
+
outputS3: {
|
|
73
|
+
bucket: "customer-output-bucket",
|
|
74
|
+
key: "encoded/input-1080p.mp4",
|
|
75
|
+
region: "us-east-1",
|
|
76
|
+
accessKeyId: process.env.CUSTOMER_S3_ACCESS_KEY_ID,
|
|
77
|
+
secretAccessKey: process.env.CUSTOMER_S3_SECRET_ACCESS_KEY,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.log(job.jobId);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For S3-compatible services, pass `endpoint` and usually `forcePathStyle: true`.
|
|
85
|
+
|
|
86
|
+
## URL Source To Google Drive Output
|
|
87
|
+
|
|
88
|
+
For API integrations, the customer should authorize Google Drive inside your application.
|
|
89
|
+
Then your backend passes the resulting Google token to Convertrilo.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const job = await client.onDemandEncode({
|
|
93
|
+
sourceUrl: "https://example.com/input.mp4",
|
|
94
|
+
codec: "h264",
|
|
95
|
+
resolution: "1080p",
|
|
96
|
+
outputGoogleDrive: {
|
|
97
|
+
folderId: "GOOGLE_DRIVE_FOLDER_ID",
|
|
98
|
+
fileName: "input-1080p.mp4",
|
|
99
|
+
accessToken: customerGoogleAccessToken,
|
|
100
|
+
refreshToken: customerGoogleRefreshToken,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
console.log(job.jobId);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Dashboard Google OAuth is for dashboard workflows. API integrations should use this
|
|
108
|
+
bring-your-own-token pattern.
|
|
109
|
+
|
|
110
|
+
## Folder Ingest
|
|
111
|
+
|
|
112
|
+
Queue one job per video in an S3 prefix:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const batch = await client.onDemandIngestFolder({
|
|
116
|
+
sourceS3: {
|
|
117
|
+
bucket: "customer-source-bucket",
|
|
118
|
+
prefix: "incoming/",
|
|
119
|
+
region: "us-east-1",
|
|
120
|
+
accessKeyId: process.env.CUSTOMER_S3_ACCESS_KEY_ID,
|
|
121
|
+
secretAccessKey: process.env.CUSTOMER_S3_SECRET_ACCESS_KEY,
|
|
122
|
+
},
|
|
123
|
+
outputDestination: "s3",
|
|
124
|
+
outputS3: {
|
|
125
|
+
bucket: "customer-output-bucket",
|
|
126
|
+
prefix: "encoded/",
|
|
127
|
+
region: "us-east-1",
|
|
128
|
+
accessKeyId: process.env.CUSTOMER_S3_ACCESS_KEY_ID,
|
|
129
|
+
secretAccessKey: process.env.CUSTOMER_S3_SECRET_ACCESS_KEY,
|
|
130
|
+
},
|
|
131
|
+
codec: "h264",
|
|
132
|
+
resolution: "1080p",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
for (const job of batch.jobs || []) {
|
|
136
|
+
console.log(job.jobId, job.fileName);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Queue one job per video in a Google Drive folder:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const batch = await client.onDemandIngestFolder({
|
|
144
|
+
sourceGoogleDrive: {
|
|
145
|
+
folderId: "SOURCE_FOLDER_ID",
|
|
146
|
+
accessToken: customerGoogleAccessToken,
|
|
147
|
+
refreshToken: customerGoogleRefreshToken,
|
|
148
|
+
},
|
|
149
|
+
outputDestination: "google-drive",
|
|
150
|
+
outputGoogleDrive: {
|
|
151
|
+
folderId: "OUTPUT_FOLDER_ID",
|
|
152
|
+
accessToken: customerGoogleAccessToken,
|
|
153
|
+
refreshToken: customerGoogleRefreshToken,
|
|
154
|
+
},
|
|
155
|
+
codec: "h264",
|
|
156
|
+
resolution: "1080p",
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
for (const job of batch.jobs || []) {
|
|
160
|
+
console.log(job.jobId, job.fileName);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Poll each returned `jobId` with `client.onDemandStatus(jobId)`.
|
|
165
|
+
|
|
166
|
+
## Regenerate Types
|
|
167
|
+
|
|
168
|
+
The SDK types are generated from `openapi.yaml`.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pnpm run generate
|
|
172
|
+
pnpm run build
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The generate script uses `--default-non-nullable false` so OpenAPI defaults remain optional
|
|
176
|
+
in TypeScript request bodies.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { paths } from "./types";
|
|
2
|
+
type ApproveBody = paths["/jobs/{id}/approve-unified"]["post"]["requestBody"] extends {
|
|
3
|
+
content: {
|
|
4
|
+
"application/json": infer B;
|
|
5
|
+
};
|
|
6
|
+
} ? B : undefined;
|
|
7
|
+
type ConfirmBody = paths["/jobs/{id}/confirm"]["post"]["requestBody"] extends {
|
|
8
|
+
content: {
|
|
9
|
+
"application/json": infer B;
|
|
10
|
+
};
|
|
11
|
+
} ? B : undefined;
|
|
12
|
+
export type ClientConfig = {
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
getToken?: () => Promise<string> | string;
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
fetchImpl?: typeof fetch;
|
|
17
|
+
};
|
|
18
|
+
export declare class ConvertriloClient {
|
|
19
|
+
private baseUrl;
|
|
20
|
+
private getToken?;
|
|
21
|
+
private apiKey?;
|
|
22
|
+
private fetchImpl;
|
|
23
|
+
constructor(config: ClientConfig);
|
|
24
|
+
private request;
|
|
25
|
+
login(body: paths["/auth/login"]["post"]["requestBody"]["content"]["application/json"]): Promise<paths["/auth/login"]["post"]["responses"]["200"]["content"]["application/json"]>;
|
|
26
|
+
getBalance(): Promise<{
|
|
27
|
+
available?: number;
|
|
28
|
+
reserved?: number;
|
|
29
|
+
trialGrantRemaining?: number;
|
|
30
|
+
trialExpiresAt?: string | null;
|
|
31
|
+
}>;
|
|
32
|
+
reserveTokens(body: paths["/tokens/reserve"]["post"]["requestBody"]["content"]["application/json"]): Promise<unknown>;
|
|
33
|
+
releaseTokens(body: paths["/tokens/release"]["post"]["requestBody"]["content"]["application/json"]): Promise<unknown>;
|
|
34
|
+
deductTokens(body: paths["/tokens/deduct"]["post"]["requestBody"]["content"]["application/json"]): Promise<unknown>;
|
|
35
|
+
createJob(body: paths["/jobs"]["post"]["requestBody"]["content"]["application/json"]): Promise<{
|
|
36
|
+
jobId: string;
|
|
37
|
+
upload: {
|
|
38
|
+
url?: string;
|
|
39
|
+
key?: string;
|
|
40
|
+
} | null;
|
|
41
|
+
output: {
|
|
42
|
+
key?: string;
|
|
43
|
+
publicUrl?: string;
|
|
44
|
+
};
|
|
45
|
+
estimate?: {
|
|
46
|
+
neu?: number;
|
|
47
|
+
};
|
|
48
|
+
}>;
|
|
49
|
+
probeDuration(id: string): Promise<{
|
|
50
|
+
durationSec?: number;
|
|
51
|
+
durationMinutes?: number;
|
|
52
|
+
}>;
|
|
53
|
+
approveUnified(id: string, body?: ApproveBody): Promise<{
|
|
54
|
+
ok?: boolean;
|
|
55
|
+
estimate?: {
|
|
56
|
+
perMinuteNeu?: number;
|
|
57
|
+
durationSec?: number;
|
|
58
|
+
totalNeu?: number;
|
|
59
|
+
reserved?: number;
|
|
60
|
+
};
|
|
61
|
+
}>;
|
|
62
|
+
confirmJob(id: string, body?: ConfirmBody): Promise<{
|
|
63
|
+
ok?: boolean;
|
|
64
|
+
}>;
|
|
65
|
+
cancelJob(id: string): Promise<unknown>;
|
|
66
|
+
jobStatus(id: string): Promise<{
|
|
67
|
+
id?: string;
|
|
68
|
+
status?: string;
|
|
69
|
+
downloadUrl?: string | null;
|
|
70
|
+
encoder?: string | null;
|
|
71
|
+
pct?: number | null;
|
|
72
|
+
}>;
|
|
73
|
+
createJobsBulk(body: paths["/jobs/bulk"]["post"]["requestBody"]["content"]["application/json"]): Promise<{
|
|
74
|
+
totalJobs?: number;
|
|
75
|
+
totalEstimatedNeu?: number;
|
|
76
|
+
jobs?: {
|
|
77
|
+
index?: number;
|
|
78
|
+
jobId?: string;
|
|
79
|
+
status?: string;
|
|
80
|
+
error?: string;
|
|
81
|
+
}[];
|
|
82
|
+
}>;
|
|
83
|
+
bulkStatus(ids: string[]): Promise<{
|
|
84
|
+
jobs?: import("./types").components["schemas"]["StatusResponse"][];
|
|
85
|
+
missing?: string[];
|
|
86
|
+
}>;
|
|
87
|
+
bulkConfirm(body: paths["/jobs/bulk/confirm"]["post"]["requestBody"]["content"]["application/json"]): Promise<{
|
|
88
|
+
queued?: number;
|
|
89
|
+
jobs?: Record<string, never>[];
|
|
90
|
+
}>;
|
|
91
|
+
getApiKeys(): Promise<{
|
|
92
|
+
keys?: import("./types").components["schemas"]["ApiKeyResponse"][];
|
|
93
|
+
}>;
|
|
94
|
+
createApiKey(body: paths["/api-keys"]["post"]["requestBody"]["content"]["application/json"]): Promise<{
|
|
95
|
+
id?: string;
|
|
96
|
+
name?: string;
|
|
97
|
+
key?: string;
|
|
98
|
+
prefix?: string;
|
|
99
|
+
scopes?: string[];
|
|
100
|
+
expiresAt?: string | null;
|
|
101
|
+
createdAt?: string;
|
|
102
|
+
}>;
|
|
103
|
+
revokeApiKey(id: string): Promise<unknown>;
|
|
104
|
+
getWebhooks(): Promise<{
|
|
105
|
+
webhooks?: import("./types").components["schemas"]["WebhookResponse"][];
|
|
106
|
+
}>;
|
|
107
|
+
createWebhook(body: paths["/webhooks"]["post"]["requestBody"]["content"]["application/json"]): Promise<{
|
|
108
|
+
id?: string;
|
|
109
|
+
url?: string;
|
|
110
|
+
events?: string[];
|
|
111
|
+
secret?: string;
|
|
112
|
+
isActive?: boolean;
|
|
113
|
+
createdAt?: string;
|
|
114
|
+
}>;
|
|
115
|
+
deleteWebhook(id: string): Promise<unknown>;
|
|
116
|
+
testWebhook(id: string): Promise<unknown>;
|
|
117
|
+
initStream(id: string): Promise<{
|
|
118
|
+
ok?: boolean;
|
|
119
|
+
uploadId?: string;
|
|
120
|
+
inputKey?: string;
|
|
121
|
+
}>;
|
|
122
|
+
uploadChunk(id: string, index: number, data: Buffer | Blob | ArrayBuffer): Promise<unknown>;
|
|
123
|
+
finalizeStream(id: string, body?: paths["/jobs/{id}/stream/finalize"]["post"]["requestBody"] extends {
|
|
124
|
+
content: {
|
|
125
|
+
"application/json": infer B;
|
|
126
|
+
};
|
|
127
|
+
} ? B : any): Promise<unknown>;
|
|
128
|
+
abortStream(id: string): Promise<unknown>;
|
|
129
|
+
onDemandEncode(body: paths["/ondemand/encode"]["post"]["requestBody"]["content"]["application/json"]): Promise<{
|
|
130
|
+
jobId: string;
|
|
131
|
+
status: string;
|
|
132
|
+
estimatedDuration?: number;
|
|
133
|
+
pricing?: {
|
|
134
|
+
basePerMinute?: number;
|
|
135
|
+
onDemandPerMinute?: number;
|
|
136
|
+
multiplier?: number;
|
|
137
|
+
totalNeu?: number;
|
|
138
|
+
reserved?: number;
|
|
139
|
+
};
|
|
140
|
+
statusUrl: string;
|
|
141
|
+
webhook?: string | null;
|
|
142
|
+
}>;
|
|
143
|
+
onDemandIngestFolder(body: paths["/ondemand/ingest/folder"]["post"]["requestBody"]["content"]["application/json"]): Promise<{
|
|
144
|
+
message?: string;
|
|
145
|
+
jobs?: {
|
|
146
|
+
jobId?: string;
|
|
147
|
+
fileName?: string;
|
|
148
|
+
}[];
|
|
149
|
+
}>;
|
|
150
|
+
onDemandStatus(jobId: string): Promise<{
|
|
151
|
+
jobId?: string;
|
|
152
|
+
status?: string;
|
|
153
|
+
progress?: number | null;
|
|
154
|
+
encoder?: string | null;
|
|
155
|
+
downloadUrl?: string | null;
|
|
156
|
+
expiresIn?: number | null;
|
|
157
|
+
destination?: ({
|
|
158
|
+
type?: "cdn";
|
|
159
|
+
} | {
|
|
160
|
+
type?: "s3";
|
|
161
|
+
bucket?: string;
|
|
162
|
+
key?: string;
|
|
163
|
+
} | {
|
|
164
|
+
type?: "google-drive";
|
|
165
|
+
folderId?: string;
|
|
166
|
+
fileName?: string | null;
|
|
167
|
+
}) | null;
|
|
168
|
+
createdAt?: string;
|
|
169
|
+
startedAt?: string | null;
|
|
170
|
+
finishedAt?: string | null;
|
|
171
|
+
failureMessage?: string | null;
|
|
172
|
+
}>;
|
|
173
|
+
onDemandCancel(jobId: string): Promise<{
|
|
174
|
+
ok: boolean;
|
|
175
|
+
released: number;
|
|
176
|
+
}>;
|
|
177
|
+
/**
|
|
178
|
+
* Helper method to encode a video and wait for completion
|
|
179
|
+
* @param sourceUrl - URL to the video file
|
|
180
|
+
* @param options - Encoding options
|
|
181
|
+
* @param pollInterval - Polling interval in milliseconds (default: 5000)
|
|
182
|
+
* @param maxAttempts - Maximum polling attempts (default: 120)
|
|
183
|
+
* @returns Download URL when encoding is complete
|
|
184
|
+
*/
|
|
185
|
+
onDemandEncodeAndWait(sourceUrl: string, options?: {
|
|
186
|
+
codec?: "h264" | "h265" | "av1";
|
|
187
|
+
resolution?: "480p" | "720p" | "1080p" | "1440p" | "2160p";
|
|
188
|
+
fps?: number;
|
|
189
|
+
quality?: "good" | "better" | "best";
|
|
190
|
+
priority?: "normal" | "high";
|
|
191
|
+
outputExpiry?: number;
|
|
192
|
+
}, pollInterval?: number, maxAttempts?: number): Promise<string>;
|
|
193
|
+
}
|
|
194
|
+
export {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
export class ConvertriloClient {
|
|
2
|
+
baseUrl;
|
|
3
|
+
getToken;
|
|
4
|
+
apiKey;
|
|
5
|
+
fetchImpl;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
8
|
+
this.getToken = config.getToken;
|
|
9
|
+
this.apiKey = config.apiKey;
|
|
10
|
+
this.fetchImpl = config.fetchImpl || fetch;
|
|
11
|
+
}
|
|
12
|
+
async request(path, init = {}) {
|
|
13
|
+
const headers = {
|
|
14
|
+
...init.headers,
|
|
15
|
+
};
|
|
16
|
+
if (this.apiKey) {
|
|
17
|
+
headers["X-API-Key"] = this.apiKey;
|
|
18
|
+
}
|
|
19
|
+
else if (this.getToken) {
|
|
20
|
+
const token = await this.getToken();
|
|
21
|
+
if (token)
|
|
22
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
23
|
+
}
|
|
24
|
+
const hasBody = init.body !== undefined;
|
|
25
|
+
if (hasBody && !headers["Content-Type"]) {
|
|
26
|
+
headers["Content-Type"] = "application/json";
|
|
27
|
+
}
|
|
28
|
+
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
29
|
+
...init,
|
|
30
|
+
headers,
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
const text = await res.text().catch(() => "");
|
|
34
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
|
35
|
+
}
|
|
36
|
+
if (res.status === 204)
|
|
37
|
+
return undefined;
|
|
38
|
+
return (await res.json());
|
|
39
|
+
}
|
|
40
|
+
// Auth
|
|
41
|
+
async login(body) {
|
|
42
|
+
return this.request(`/auth/login`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
body: JSON.stringify(body),
|
|
45
|
+
headers: { Authorization: "" }, // no auth
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// Tokens
|
|
49
|
+
async getBalance() {
|
|
50
|
+
return this.request(`/tokens/balance`);
|
|
51
|
+
}
|
|
52
|
+
async reserveTokens(body) {
|
|
53
|
+
return this.request(`/tokens/reserve`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: JSON.stringify(body),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async releaseTokens(body) {
|
|
59
|
+
return this.request(`/tokens/release`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async deductTokens(body) {
|
|
65
|
+
return this.request(`/tokens/deduct`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
body: JSON.stringify(body),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// Jobs
|
|
71
|
+
async createJob(body) {
|
|
72
|
+
return this.request(`/jobs`, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
body: JSON.stringify(body),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async probeDuration(id) {
|
|
78
|
+
return this.request(`/jobs/${id}/probe-duration`);
|
|
79
|
+
}
|
|
80
|
+
async approveUnified(id, body) {
|
|
81
|
+
return this.request(`/jobs/${id}/approve-unified`, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async confirmJob(id, body) {
|
|
87
|
+
return this.request(`/jobs/${id}/confirm`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async cancelJob(id) {
|
|
93
|
+
return this.request(`/jobs/${id}/cancel`, { method: "POST" });
|
|
94
|
+
}
|
|
95
|
+
async jobStatus(id) {
|
|
96
|
+
return this.request(`/jobs/${id}/status`);
|
|
97
|
+
}
|
|
98
|
+
// Bulk Jobs
|
|
99
|
+
async createJobsBulk(body) {
|
|
100
|
+
return this.request(`/jobs/bulk`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
body: JSON.stringify(body),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async bulkStatus(ids) {
|
|
106
|
+
return this.request(`/jobs/bulk/status?ids=${ids.join(",")}`);
|
|
107
|
+
}
|
|
108
|
+
async bulkConfirm(body) {
|
|
109
|
+
return this.request(`/jobs/bulk/confirm`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
body: JSON.stringify(body),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// API Keys
|
|
115
|
+
async getApiKeys() {
|
|
116
|
+
return this.request(`/api-keys`);
|
|
117
|
+
}
|
|
118
|
+
async createApiKey(body) {
|
|
119
|
+
return this.request(`/api-keys`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
body: JSON.stringify(body),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async revokeApiKey(id) {
|
|
125
|
+
return this.request(`/api-keys/${id}`, { method: "DELETE" });
|
|
126
|
+
}
|
|
127
|
+
// Webhooks
|
|
128
|
+
async getWebhooks() {
|
|
129
|
+
return this.request(`/webhooks`);
|
|
130
|
+
}
|
|
131
|
+
async createWebhook(body) {
|
|
132
|
+
return this.request(`/webhooks`, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
body: JSON.stringify(body),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async deleteWebhook(id) {
|
|
138
|
+
return this.request(`/webhooks/${id}`, { method: "DELETE" });
|
|
139
|
+
}
|
|
140
|
+
async testWebhook(id) {
|
|
141
|
+
return this.request(`/webhooks/${id}/test`, { method: "POST" });
|
|
142
|
+
}
|
|
143
|
+
// Streaming
|
|
144
|
+
async initStream(id) {
|
|
145
|
+
return this.request(`/jobs/${id}/stream/init`, { method: "POST" });
|
|
146
|
+
}
|
|
147
|
+
async uploadChunk(id, index, data) {
|
|
148
|
+
return this.request(`/jobs/${id}/stream/chunk/${index}`, {
|
|
149
|
+
method: "PUT",
|
|
150
|
+
body: data,
|
|
151
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async finalizeStream(id, body) {
|
|
155
|
+
return this.request(`/jobs/${id}/stream/finalize`, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async abortStream(id) {
|
|
161
|
+
return this.request(`/jobs/${id}/stream/abort`, { method: "POST" });
|
|
162
|
+
}
|
|
163
|
+
// On-Demand Encoding
|
|
164
|
+
async onDemandEncode(body) {
|
|
165
|
+
return this.request(`/ondemand/encode`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
body: JSON.stringify(body),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async onDemandIngestFolder(body) {
|
|
171
|
+
return this.request(`/ondemand/ingest/folder`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
body: JSON.stringify(body),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async onDemandStatus(jobId) {
|
|
177
|
+
return this.request(`/ondemand/status/${jobId}`);
|
|
178
|
+
}
|
|
179
|
+
async onDemandCancel(jobId) {
|
|
180
|
+
return this.request(`/ondemand/${jobId}`, { method: "DELETE" });
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Helper method to encode a video and wait for completion
|
|
184
|
+
* @param sourceUrl - URL to the video file
|
|
185
|
+
* @param options - Encoding options
|
|
186
|
+
* @param pollInterval - Polling interval in milliseconds (default: 5000)
|
|
187
|
+
* @param maxAttempts - Maximum polling attempts (default: 120)
|
|
188
|
+
* @returns Download URL when encoding is complete
|
|
189
|
+
*/
|
|
190
|
+
async onDemandEncodeAndWait(sourceUrl, options = {}, pollInterval = 5000, maxAttempts = 120) {
|
|
191
|
+
// Submit for encoding
|
|
192
|
+
const job = await this.onDemandEncode({
|
|
193
|
+
sourceUrl,
|
|
194
|
+
...options,
|
|
195
|
+
});
|
|
196
|
+
// Poll for completion
|
|
197
|
+
let attempts = 0;
|
|
198
|
+
while (attempts < maxAttempts) {
|
|
199
|
+
const status = await this.onDemandStatus(job.jobId);
|
|
200
|
+
if (status.status === "success" && status.downloadUrl) {
|
|
201
|
+
return status.downloadUrl;
|
|
202
|
+
}
|
|
203
|
+
if (status.status === "failed") {
|
|
204
|
+
throw new Error(status.failureMessage || "Encoding failed");
|
|
205
|
+
}
|
|
206
|
+
if (status.status === "canceled") {
|
|
207
|
+
throw new Error("Job was canceled");
|
|
208
|
+
}
|
|
209
|
+
// Wait before next poll
|
|
210
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
211
|
+
attempts++;
|
|
212
|
+
}
|
|
213
|
+
throw new Error("Polling timeout: job did not complete in time");
|
|
214
|
+
}
|
|
215
|
+
}
|