@editframe/api 0.9.0-beta.1 → 0.10.0-beta.2

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.
@@ -10,6 +10,8 @@ import {
10
10
  createUnprocessedFile,
11
11
  processAVFile,
12
12
  processAVFileBuffer,
13
+ processImageFile,
14
+ processImageFileBuffer,
13
15
  updateUnprocessedFile,
14
16
  uploadUnprocessedFile,
15
17
  } from "./unprocessed-file.ts";
@@ -29,7 +31,7 @@ describe("Unprocessed File", () => {
29
31
  test("Throws when file is too large", async () => {
30
32
  await expect(
31
33
  createUnprocessedFile(client, {
32
- id: "test-file",
34
+ md5: "test-file",
33
35
  filename: "test-file",
34
36
  processes: [],
35
37
  byte_size: 1024 * 1024 * 1025,
@@ -58,7 +60,7 @@ describe("Unprocessed File", () => {
58
60
 
59
61
  await expect(
60
62
  createUnprocessedFile(client, {
61
- id: "test-file",
63
+ md5: "test-file",
62
64
  filename: "test-file",
63
65
  processes: [],
64
66
  byte_size: 1024 * 1024,
@@ -79,7 +81,7 @@ describe("Unprocessed File", () => {
79
81
  );
80
82
 
81
83
  const result = await createUnprocessedFile(client, {
82
- id: "test-file",
84
+ md5: "test-file",
83
85
  filename: "test-file",
84
86
  processes: [],
85
87
  byte_size: 1024 * 1024,
@@ -182,7 +184,14 @@ describe("Unprocessed File", () => {
182
184
  test("Throws when server responds with an error when uploading file", async () => {
183
185
  server.use(
184
186
  http.post("http://localhost/api/v1/unprocessed_files", () =>
185
- HttpResponse.json({}, { status: 200 }),
187
+ HttpResponse.json(
188
+ {
189
+ complete: false,
190
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
191
+ processes: [],
192
+ },
193
+ { status: 200 },
194
+ ),
186
195
  ),
187
196
  http.post(
188
197
  "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
@@ -200,7 +209,14 @@ describe("Unprocessed File", () => {
200
209
  test("Throws when server responds with an error when updating file", async () => {
201
210
  server.use(
202
211
  http.post("http://localhost/api/v1/unprocessed_files", () =>
203
- HttpResponse.json({}, { status: 200 }),
212
+ HttpResponse.json(
213
+ {
214
+ complete: false,
215
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
216
+ processes: [],
217
+ },
218
+ { status: 200 },
219
+ ),
204
220
  ),
205
221
  http.post(
206
222
  "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
@@ -222,7 +238,14 @@ describe("Unprocessed File", () => {
222
238
  test("Returns json data when upload is successful", async () => {
223
239
  server.use(
224
240
  http.post("http://localhost/api/v1/unprocessed_files", () =>
225
- HttpResponse.json({}, { status: 200 }),
241
+ HttpResponse.json(
242
+ {
243
+ complete: false,
244
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
245
+ processes: [],
246
+ },
247
+ { status: 200 },
248
+ ),
226
249
  ),
227
250
  http.post(
228
251
  "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
@@ -256,7 +279,14 @@ describe("Unprocessed File", () => {
256
279
  test("Throws when server responds with an error when uploading file", async () => {
257
280
  server.use(
258
281
  http.post("http://localhost/api/v1/unprocessed_files", () =>
259
- HttpResponse.json({}, { status: 200 }),
282
+ HttpResponse.json(
283
+ {
284
+ complete: false,
285
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
286
+ processes: [],
287
+ },
288
+ { status: 200 },
289
+ ),
260
290
  ),
261
291
  http.post(
262
292
  "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
@@ -272,7 +302,14 @@ describe("Unprocessed File", () => {
272
302
  test("Throws when server responds with an error when updating file", async () => {
273
303
  server.use(
274
304
  http.post("http://localhost/api/v1/unprocessed_files", () =>
275
- HttpResponse.json({}, { status: 200 }),
305
+ HttpResponse.json(
306
+ {
307
+ complete: false,
308
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
309
+ processes: [],
310
+ },
311
+ { status: 200 },
312
+ ),
276
313
  ),
277
314
  http.post(
278
315
  "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
@@ -292,7 +329,14 @@ describe("Unprocessed File", () => {
292
329
  test("Returns json data when upload is successful", async () => {
293
330
  server.use(
294
331
  http.post("http://localhost/api/v1/unprocessed_files", () =>
295
- HttpResponse.json({}, { status: 200 }),
332
+ HttpResponse.json(
333
+ {
334
+ complete: false,
335
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
336
+ processes: [],
337
+ },
338
+ { status: 200 },
339
+ ),
296
340
  ),
297
341
  http.post(
298
342
  "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
@@ -309,4 +353,188 @@ describe("Unprocessed File", () => {
309
353
  });
310
354
  });
311
355
  });
356
+
357
+ describe("processImageFileBuffer", () => {
358
+ test("Throws when server responds with an error when creating file", async () => {
359
+ server.use(
360
+ http.post("http://localhost/api/v1/unprocessed_files", () =>
361
+ HttpResponse.text("Internal Server Error", { status: 500 }),
362
+ ),
363
+ );
364
+
365
+ await expect(
366
+ processImageFileBuffer(client, Buffer.from("test"), "test-file"),
367
+ ).rejects.toThrowError(
368
+ "Failed to create unprocessed file 500 Internal Server Error",
369
+ );
370
+ });
371
+
372
+ test("Throws when server responds with an error when uploading file", async () => {
373
+ server.use(
374
+ http.post("http://localhost/api/v1/unprocessed_files", () =>
375
+ HttpResponse.json(
376
+ {
377
+ complete: false,
378
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
379
+ processes: [],
380
+ },
381
+ { status: 200 },
382
+ ),
383
+ ),
384
+ http.post(
385
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
386
+ () => HttpResponse.text("Internal Server Error", { status: 500 }),
387
+ ),
388
+ );
389
+
390
+ await expect(
391
+ processImageFileBuffer(client, Buffer.from("test"), "test-file"),
392
+ ).rejects.toThrowError(
393
+ "Failed to upload chunk 0 for /api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload 500 Internal Server Error",
394
+ );
395
+ });
396
+
397
+ test("Throws when server responds with an error when updating file", async () => {
398
+ server.use(
399
+ http.post("http://localhost/api/v1/unprocessed_files", () =>
400
+ HttpResponse.json(
401
+ {
402
+ complete: false,
403
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
404
+ processes: [],
405
+ },
406
+ { status: 200 },
407
+ ),
408
+ ),
409
+ http.post(
410
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
411
+ () => HttpResponse.json({ test }, { status: 201 }),
412
+ ),
413
+ http.post(
414
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6",
415
+ () => HttpResponse.text("Internal Server Error", { status: 500 }),
416
+ ),
417
+ );
418
+
419
+ await expect(
420
+ processImageFileBuffer(client, Buffer.from("test"), "test-file"),
421
+ ).rejects.toThrowError(
422
+ "Failed to update unprocessed file 500 Internal Server Error",
423
+ );
424
+ });
425
+
426
+ test("Returns json data when upload is successful", async () => {
427
+ server.use(
428
+ http.post("http://localhost/api/v1/unprocessed_files", () =>
429
+ HttpResponse.json(
430
+ {
431
+ complete: false,
432
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
433
+ processes: [],
434
+ },
435
+ { status: 200 },
436
+ ),
437
+ ),
438
+ http.post(
439
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
440
+ () => HttpResponse.json({}, { status: 201 }),
441
+ ),
442
+ http.post(
443
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6",
444
+ () => HttpResponse.json({ test: "response" }, { status: 200 }),
445
+ ),
446
+ );
447
+
448
+ await expect(
449
+ processImageFileBuffer(client, Buffer.from("test"), "test-file"),
450
+ ).resolves.toEqual({ test: "response" });
451
+ });
452
+ });
453
+
454
+ describe("ProcessImageFile", () => {
455
+ test("Throws when server responds with an error when creating file", async () => {
456
+ server.use(
457
+ http.post("http://localhost/api/v1/unprocessed_files", () =>
458
+ HttpResponse.text("Internal Server Error", { status: 500 }),
459
+ ),
460
+ );
461
+
462
+ await expect(processImageFile(client, TEST_AV_FILE)).rejects.toThrowError(
463
+ "Failed to create unprocessed file 500 Internal Server Error",
464
+ );
465
+ });
466
+
467
+ test("Throws when server responds with an error when uploading file", async () => {
468
+ server.use(
469
+ http.post("http://localhost/api/v1/unprocessed_files", () =>
470
+ HttpResponse.json(
471
+ {
472
+ complete: false,
473
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
474
+ processes: [],
475
+ },
476
+ { status: 200 },
477
+ ),
478
+ ),
479
+ http.post(
480
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
481
+ () => HttpResponse.text("Internal Server Error", { status: 500 }),
482
+ ),
483
+ );
484
+
485
+ await expect(processImageFile(client, TEST_AV_FILE)).rejects.toThrowError(
486
+ "Failed to upload chunk 0 for /api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload 500 Internal Server Error",
487
+ );
488
+ });
489
+
490
+ test("Throws when server responds with an error when updating file", async () => {
491
+ server.use(
492
+ http.post("http://localhost/api/v1/unprocessed_files", () =>
493
+ HttpResponse.json(
494
+ {
495
+ complete: false,
496
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
497
+ processes: [],
498
+ },
499
+ { status: 200 },
500
+ ),
501
+ ),
502
+ http.post(
503
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
504
+ () => HttpResponse.json({ test }, { status: 201 }),
505
+ ),
506
+ http.post(
507
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6",
508
+ () => HttpResponse.text("Internal Server Error", { status: 500 }),
509
+ ),
510
+ );
511
+
512
+ await expect(processImageFile(client, TEST_AV_FILE)).rejects.toThrowError(
513
+ "Failed to update unprocessed file 500 Internal Server Error",
514
+ );
515
+ });
516
+
517
+ test("Returns json data when upload is successful", async () => {
518
+ server.use(
519
+ http.post("http://localhost/api/v1/unprocessed_files", () =>
520
+ HttpResponse.json(
521
+ {
522
+ complete: false,
523
+ id: "098f6bcd-4621-d373-cade-4e832627b4f6",
524
+ processes: [],
525
+ },
526
+ { status: 200 },
527
+ ),
528
+ ),
529
+ http.post(
530
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload",
531
+ () => HttpResponse.json({}, { status: 201 }),
532
+ ),
533
+ http.post(
534
+ "http://localhost/api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6",
535
+ () => HttpResponse.json({ test: "response" }, { status: 200 }),
536
+ ),
537
+ );
538
+ });
539
+ });
312
540
  });
@@ -13,21 +13,25 @@ import { uploadChunks } from "../uploadChunks.ts";
13
13
 
14
14
  const log = debug("ef:api:unprocessed-file");
15
15
 
16
- const FileProcessors = z
17
- .array(z.union([z.literal("isobmff"), z.literal("captions")]))
18
- .refine(
19
- (value) => {
20
- return new Set(value).size === value.length;
21
- },
22
- {
23
- message: "Processors list must not include duplicates",
24
- },
25
- );
16
+ const FileProcessor = z.union([
17
+ z.literal("isobmff"),
18
+ z.literal("image"),
19
+ z.literal("captions"),
20
+ ]);
21
+
22
+ const FileProcessors = z.array(FileProcessor).refine(
23
+ (value) => {
24
+ return new Set(value).size === value.length;
25
+ },
26
+ {
27
+ message: "Processors list must not include duplicates",
28
+ },
29
+ );
26
30
 
27
31
  const MAX_FILE_SIZE = 1024 * 1024 * 1024; // 1GiB
28
32
 
29
33
  export const CreateUnprocessedFilePayload = z.object({
30
- id: z.string(),
34
+ md5: z.string(),
31
35
  filename: z.string(),
32
36
  processes: FileProcessors.optional(),
33
37
  byte_size: z.number().int().max(MAX_FILE_SIZE),
@@ -40,15 +44,21 @@ export const UpdateUnprocessedFilePayload = z.object({
40
44
  export interface CreateUnprocessedFileResult {
41
45
  byte_size: number;
42
46
  next_byte: number;
47
+ complete: boolean;
43
48
  id: string;
49
+ md5: string;
44
50
  processes: z.infer<typeof FileProcessors>;
51
+ asset_id: string;
45
52
  }
46
53
 
47
54
  export interface UpdateUnprocessedFileResult {
48
55
  byte_size?: number;
49
56
  next_byte: number;
57
+ complete: boolean;
50
58
  id: string;
59
+ md5: string;
51
60
  processes: z.infer<typeof FileProcessors>;
61
+ asset_id: string;
52
62
  }
53
63
 
54
64
  export const createUnprocessedFile = async (
@@ -124,66 +134,90 @@ export const uploadUnprocessedFile = async (
124
134
  log("Unprocessed file upload complete");
125
135
  };
126
136
 
127
- export const processAVFileBuffer = async (
137
+ const processResource = async (
128
138
  client: Client,
129
- buffer: Buffer,
130
- filename = "buffer",
139
+ filename: string,
140
+ md5: string,
141
+ byteSize: number,
142
+ processor: z.infer<typeof FileProcessor>,
143
+ doUpload: (id: string) => Promise<void>,
131
144
  ) => {
132
- log("Processing AV file buffer");
133
- const fileId = md5Buffer(buffer);
145
+ log("Processing", { filename, md5, byteSize, processor });
134
146
 
135
- log("File ID", fileId);
136
- log(`File size: ${buffer.byteLength} bytes`);
137
-
138
- await createUnprocessedFile(client, {
139
- id: fileId,
147
+ const unprocessedFile = await createUnprocessedFile(client, {
148
+ md5: md5,
140
149
  processes: [],
141
150
  filename,
142
- byte_size: buffer.byteLength,
143
- });
144
-
145
- const readStream = new Readable({
146
- read() {
147
- readStream.push(buffer);
148
- readStream.push(null);
149
- },
151
+ byte_size: byteSize,
150
152
  });
151
153
 
152
- await uploadUnprocessedFile(client, fileId, readStream, buffer.byteLength);
154
+ if (unprocessedFile.complete === false) {
155
+ await doUpload(unprocessedFile.id);
156
+ }
153
157
 
154
- const fileInformation = await updateUnprocessedFile(client, fileId, {
155
- processes: ["isobmff"],
156
- });
158
+ if (unprocessedFile.processes.includes(processor)) {
159
+ log("File already processed", unprocessedFile);
160
+ return unprocessedFile;
161
+ }
157
162
 
163
+ const fileInformation = await updateUnprocessedFile(
164
+ client,
165
+ unprocessedFile.id,
166
+ {
167
+ processes: [processor],
168
+ },
169
+ );
158
170
  log("File processed", fileInformation);
159
171
  return fileInformation;
160
172
  };
161
173
 
162
- export const processAVFile = async (client: Client, filePath: string) => {
163
- log("Processing AV file", filePath);
164
- const fileId = await md5FilePath(filePath);
165
-
166
- log("File ID", fileId);
167
- await createUnprocessedFile(client, {
168
- id: fileId,
169
- processes: [],
170
- filename: basename(filePath),
171
- byte_size: (await stat(filePath)).size,
172
- });
173
-
174
- const readStream = createReadStream(filePath);
174
+ const buildBufferProcessor = (processor: z.infer<typeof FileProcessor>) => {
175
+ return async (client: Client, buffer: Buffer, filename = "buffer") => {
176
+ log(`Processing file buffer: ${processor}`, filename);
177
+ const md5 = md5Buffer(buffer);
178
+
179
+ return await processResource(
180
+ client,
181
+ filename,
182
+ md5,
183
+ buffer.byteLength,
184
+ processor,
185
+ async (id: string) => {
186
+ const readStream = new Readable({
187
+ read() {
188
+ readStream.push(buffer);
189
+ readStream.push(null);
190
+ },
191
+ });
192
+
193
+ await uploadUnprocessedFile(client, id, readStream, buffer.byteLength);
194
+ },
195
+ );
196
+ };
197
+ };
175
198
 
176
- await uploadUnprocessedFile(
177
- client,
178
- fileId,
179
- readStream,
180
- (await stat(filePath)).size,
181
- );
199
+ const buildFileProcessor = (processor: z.infer<typeof FileProcessor>) => {
200
+ return async (client: Client, filePath: string) => {
201
+ log(`Processing file ${processor}`, filePath);
202
+ const md5 = await md5FilePath(filePath);
203
+ const byteSize = (await stat(filePath)).size;
204
+
205
+ return await processResource(
206
+ client,
207
+ basename(filePath),
208
+ md5,
209
+ byteSize,
210
+ processor,
211
+ async (id: string) => {
212
+ const readStream = createReadStream(filePath);
213
+ return await uploadUnprocessedFile(client, id, readStream, byteSize);
214
+ },
215
+ );
216
+ };
217
+ };
182
218
 
183
- const fileInformation = await updateUnprocessedFile(client, fileId, {
184
- processes: ["isobmff"],
185
- });
219
+ export const processAVFileBuffer = buildBufferProcessor("isobmff");
220
+ export const processAVFile = buildFileProcessor("isobmff");
186
221
 
187
- log("File processed", fileInformation);
188
- return fileInformation;
189
- };
222
+ export const processImageFileBuffer = buildBufferProcessor("image");
223
+ export const processImageFile = buildFileProcessor("image");