@editframe/api 0.16.3-beta.0 → 0.16.6-beta.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/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/node.js +3 -1
- package/dist/resources/renders.d.ts +428 -0
- package/dist/resources/renders.js +105 -1
- package/dist/uploadChunks.js +0 -1
- package/package.json +3 -3
- package/src/resources/image-file.test.ts +14 -13
- package/src/resources/isobmff-track.test.ts +14 -13
- package/src/resources/renders.test.ts +61 -1
- package/src/resources/renders.ts +129 -0
- package/src/resources/unprocessed-file.test.ts +14 -13
- package/types.json +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { http, HttpResponse } from "msw";
|
|
2
2
|
import { setupServer } from "msw/node";
|
|
3
3
|
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
4
|
-
import { ZodError } from "zod";
|
|
5
4
|
|
|
6
5
|
import { Client } from "../client.js";
|
|
7
6
|
import { webReadableFromBuffers } from "../readableFromBuffers.js";
|
|
@@ -58,19 +57,21 @@ describe("ImageFile", () => {
|
|
|
58
57
|
width: 100,
|
|
59
58
|
mime_type: "image/jpeg",
|
|
60
59
|
}),
|
|
61
|
-
).rejects.
|
|
62
|
-
|
|
60
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
61
|
+
[ZodError: [
|
|
63
62
|
{
|
|
64
|
-
code: "too_big",
|
|
65
|
-
maximum: 16777216,
|
|
66
|
-
type: "number",
|
|
67
|
-
inclusive: true,
|
|
68
|
-
exact: false,
|
|
69
|
-
message: "Number must be less than or equal to 16777216",
|
|
70
|
-
path: [
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
"code": "too_big",
|
|
64
|
+
"maximum": 16777216,
|
|
65
|
+
"type": "number",
|
|
66
|
+
"inclusive": true,
|
|
67
|
+
"exact": false,
|
|
68
|
+
"message": "Number must be less than or equal to 16777216",
|
|
69
|
+
"path": [
|
|
70
|
+
"byte_size"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
]]
|
|
74
|
+
`);
|
|
74
75
|
});
|
|
75
76
|
|
|
76
77
|
test("Throws when server returns an error", async () => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { http, HttpResponse } from "msw";
|
|
2
2
|
import { setupServer } from "msw/node";
|
|
3
3
|
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
4
|
-
import { ZodError } from "zod";
|
|
5
4
|
|
|
6
5
|
import { createTestTrack } from "TEST/createTestTrack.js";
|
|
7
6
|
import { Client } from "../client.js";
|
|
@@ -29,19 +28,21 @@ describe("ISOBMFF Track", () => {
|
|
|
29
28
|
client,
|
|
30
29
|
createTestTrack({ byte_size: 1024 * 1024 * 1025 }),
|
|
31
30
|
),
|
|
32
|
-
).rejects.
|
|
33
|
-
|
|
31
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
32
|
+
[ZodError: [
|
|
34
33
|
{
|
|
35
|
-
code: "too_big",
|
|
36
|
-
maximum: 1073741824,
|
|
37
|
-
type: "number",
|
|
38
|
-
inclusive: true,
|
|
39
|
-
exact: false,
|
|
40
|
-
message: "Number must be less than or equal to 1073741824",
|
|
41
|
-
path: [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
"code": "too_big",
|
|
35
|
+
"maximum": 1073741824,
|
|
36
|
+
"type": "number",
|
|
37
|
+
"inclusive": true,
|
|
38
|
+
"exact": false,
|
|
39
|
+
"message": "Number must be less than or equal to 1073741824",
|
|
40
|
+
"path": [
|
|
41
|
+
"byte_size"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
]]
|
|
45
|
+
`);
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
test("Throws when server returns an error", async () => {
|
|
@@ -4,7 +4,12 @@ import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
|
4
4
|
|
|
5
5
|
import { Client } from "../client.js";
|
|
6
6
|
import { webReadableFromBuffers } from "../readableFromBuffers.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
OutputConfiguration,
|
|
9
|
+
createRender,
|
|
10
|
+
lookupRenderByMd5,
|
|
11
|
+
uploadRender,
|
|
12
|
+
} from "./renders.js";
|
|
8
13
|
|
|
9
14
|
const server = setupServer();
|
|
10
15
|
const client = new Client("ef_TEST_TOKEN", "http://localhost");
|
|
@@ -130,6 +135,61 @@ describe("Renders", () => {
|
|
|
130
135
|
});
|
|
131
136
|
});
|
|
132
137
|
|
|
138
|
+
describe("OutputConfiguration", () => {
|
|
139
|
+
test("should create a valid output configuration from nullish values", () => {
|
|
140
|
+
const outputConfiguration = OutputConfiguration.parse();
|
|
141
|
+
expect(outputConfiguration.output).toEqual({
|
|
142
|
+
container: "mp4",
|
|
143
|
+
audio: {
|
|
144
|
+
codec: "aac",
|
|
145
|
+
},
|
|
146
|
+
video: {
|
|
147
|
+
codec: "h264",
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("should permit mp4 configuration", () => {
|
|
153
|
+
const outputConfiguration = OutputConfiguration.parse({
|
|
154
|
+
container: "mp4",
|
|
155
|
+
video: {
|
|
156
|
+
codec: "h264",
|
|
157
|
+
},
|
|
158
|
+
audio: {
|
|
159
|
+
codec: "aac",
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
expect(outputConfiguration.isVideo).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("should permit png configuration", () => {
|
|
166
|
+
const outputConfiguration = OutputConfiguration.parse({
|
|
167
|
+
container: "png",
|
|
168
|
+
compression: 100,
|
|
169
|
+
transparency: true,
|
|
170
|
+
});
|
|
171
|
+
expect(outputConfiguration.isStill).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("should permit webp configuration", () => {
|
|
175
|
+
const outputConfiguration = OutputConfiguration.parse({
|
|
176
|
+
container: "webp",
|
|
177
|
+
quality: 100,
|
|
178
|
+
compression: 6,
|
|
179
|
+
transparency: true,
|
|
180
|
+
});
|
|
181
|
+
expect(outputConfiguration.isStill).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("should permit jpeg configuration", () => {
|
|
185
|
+
const outputConfiguration = OutputConfiguration.parse({
|
|
186
|
+
container: "jpeg",
|
|
187
|
+
quality: 100,
|
|
188
|
+
});
|
|
189
|
+
expect(outputConfiguration.isStill).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
133
193
|
const createTestRender = () =>
|
|
134
194
|
({
|
|
135
195
|
md5: "test-md5",
|
package/src/resources/renders.ts
CHANGED
|
@@ -7,6 +7,49 @@ import { assertTypesMatch } from "../utils/assertTypesMatch.ts";
|
|
|
7
7
|
|
|
8
8
|
const log = debug("ef:api:renders");
|
|
9
9
|
|
|
10
|
+
const H264Configuration = z.object({
|
|
11
|
+
codec: z.literal("h264"),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const AACConfiguration = z.object({
|
|
15
|
+
codec: z.literal("aac"),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const MP4Configuration = z.object({
|
|
19
|
+
container: z.literal("mp4"),
|
|
20
|
+
video: H264Configuration,
|
|
21
|
+
audio: AACConfiguration,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const JpegConfiguration = z.object({
|
|
25
|
+
container: z.literal("jpeg"),
|
|
26
|
+
quality: z.number().int().min(1).max(100).default(80).optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const PngConfiguration = z.object({
|
|
30
|
+
container: z.literal("png"),
|
|
31
|
+
compression: z.number().int().min(1).max(100).default(80).optional(),
|
|
32
|
+
transparency: z.boolean().default(false).optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const WebpConfiguration = z.object({
|
|
36
|
+
container: z.literal("webp"),
|
|
37
|
+
quality: z.number().int().min(1).max(100).default(80).optional(),
|
|
38
|
+
compression: z.number().int().min(0).max(6).default(4).optional(),
|
|
39
|
+
transparency: z.boolean().default(false).optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const RenderOutputConfiguration = z.discriminatedUnion("container", [
|
|
43
|
+
MP4Configuration,
|
|
44
|
+
JpegConfiguration,
|
|
45
|
+
PngConfiguration,
|
|
46
|
+
WebpConfiguration,
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
export type RenderOutputConfiguration = z.infer<
|
|
50
|
+
typeof RenderOutputConfiguration
|
|
51
|
+
>;
|
|
52
|
+
|
|
10
53
|
export const CreateRenderPayload = z.object({
|
|
11
54
|
md5: z.string().optional(),
|
|
12
55
|
fps: z.number().int().min(1).max(120).default(30).optional(),
|
|
@@ -23,8 +66,83 @@ export const CreateRenderPayload = z.object({
|
|
|
23
66
|
metadata: z.record(z.string(), z.string()).optional(),
|
|
24
67
|
duration_ms: z.number().int().optional(),
|
|
25
68
|
strategy: z.enum(["v1"]).default("v1").optional(),
|
|
69
|
+
output: RenderOutputConfiguration.default({
|
|
70
|
+
container: "mp4",
|
|
71
|
+
video: {
|
|
72
|
+
codec: "h264",
|
|
73
|
+
},
|
|
74
|
+
audio: {
|
|
75
|
+
codec: "aac",
|
|
76
|
+
},
|
|
77
|
+
}).optional(),
|
|
26
78
|
});
|
|
27
79
|
|
|
80
|
+
export const CreateRenderPayloadWithOutput = CreateRenderPayload.extend({
|
|
81
|
+
output: RenderOutputConfiguration,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export class OutputConfiguration {
|
|
85
|
+
static parse(input?: any) {
|
|
86
|
+
const output = RenderOutputConfiguration.parse(
|
|
87
|
+
input ?? {
|
|
88
|
+
container: "mp4",
|
|
89
|
+
video: {
|
|
90
|
+
codec: "h264",
|
|
91
|
+
},
|
|
92
|
+
audio: {
|
|
93
|
+
codec: "aac",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
return new OutputConfiguration(output);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
constructor(public readonly output: RenderOutputConfiguration) {}
|
|
101
|
+
|
|
102
|
+
get isStill() {
|
|
103
|
+
return (
|
|
104
|
+
this.output.container === "jpeg" ||
|
|
105
|
+
this.output.container === "png" ||
|
|
106
|
+
this.output.container === "webp"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get isVideo() {
|
|
111
|
+
return this.output.container === "mp4";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get fileExtension() {
|
|
115
|
+
return this.output.container;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get contentType() {
|
|
119
|
+
if (this.isStill) {
|
|
120
|
+
return `image/${this.fileExtension}`;
|
|
121
|
+
}
|
|
122
|
+
return `video/${this.fileExtension}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get container() {
|
|
126
|
+
return this.output.container;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get jpegConfig() {
|
|
130
|
+
return this.output.container === "jpeg" ? this.output : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get pngConfig() {
|
|
134
|
+
return this.output.container === "png" ? this.output : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get webpConfig() {
|
|
138
|
+
return this.output.container === "webp" ? this.output : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get mp4Config() {
|
|
142
|
+
return this.output.container === "mp4" ? this.output : null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
28
146
|
export interface CreateRenderPayload {
|
|
29
147
|
md5?: string;
|
|
30
148
|
fps?: number;
|
|
@@ -35,6 +153,7 @@ export interface CreateRenderPayload {
|
|
|
35
153
|
duration_ms?: number;
|
|
36
154
|
metadata?: Record<string, string>;
|
|
37
155
|
strategy?: "v1";
|
|
156
|
+
output?: z.infer<typeof RenderOutputConfiguration>;
|
|
38
157
|
}
|
|
39
158
|
|
|
40
159
|
assertTypesMatch<CreateRenderPayload, z.infer<typeof CreateRenderPayload>>(
|
|
@@ -65,6 +184,16 @@ export const createRender = async (
|
|
|
65
184
|
// Manually applying defaults here is a hack
|
|
66
185
|
payload.strategy ??= "v1";
|
|
67
186
|
payload.work_slice_ms ??= 4_000;
|
|
187
|
+
payload.output ??= {
|
|
188
|
+
container: "mp4",
|
|
189
|
+
video: {
|
|
190
|
+
codec: "h264",
|
|
191
|
+
},
|
|
192
|
+
audio: {
|
|
193
|
+
codec: "aac",
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
68
197
|
const response = await client.authenticatedFetch("/api/v1/renders", {
|
|
69
198
|
method: "POST",
|
|
70
199
|
body: JSON.stringify(payload),
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { http, HttpResponse } from "msw";
|
|
2
2
|
import { setupServer } from "msw/node";
|
|
3
3
|
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
4
|
-
import { ZodError } from "zod";
|
|
5
4
|
|
|
6
5
|
import { Client } from "../client.js";
|
|
7
6
|
import { webReadableFromBuffers } from "../readableFromBuffers.js";
|
|
@@ -32,19 +31,21 @@ describe("Unprocessed File", () => {
|
|
|
32
31
|
filename: "test-file",
|
|
33
32
|
byte_size: 1024 * 1024 * 1025,
|
|
34
33
|
}),
|
|
35
|
-
).rejects.
|
|
36
|
-
|
|
34
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
35
|
+
[ZodError: [
|
|
37
36
|
{
|
|
38
|
-
code: "too_big",
|
|
39
|
-
maximum: 1073741824,
|
|
40
|
-
type: "number",
|
|
41
|
-
inclusive: true,
|
|
42
|
-
exact: false,
|
|
43
|
-
message: "Number must be less than or equal to 1073741824",
|
|
44
|
-
path: [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
"code": "too_big",
|
|
38
|
+
"maximum": 1073741824,
|
|
39
|
+
"type": "number",
|
|
40
|
+
"inclusive": true,
|
|
41
|
+
"exact": false,
|
|
42
|
+
"message": "Number must be less than or equal to 1073741824",
|
|
43
|
+
"path": [
|
|
44
|
+
"byte_size"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]]
|
|
48
|
+
`);
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
test("Throws when server returns an error", async () => {
|