@editframe/api 0.11.0-beta.9 → 0.12.0-beta.10

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.
Files changed (58) hide show
  1. package/dist/ProgressIterator.d.ts +25 -0
  2. package/dist/ProgressIterator.test.d.ts +1 -0
  3. package/dist/StreamEventSource.d.ts +50 -0
  4. package/dist/StreamEventSource.test.d.ts +1 -0
  5. package/dist/{CHUNK_SIZE_BYTES.js → api/src/CHUNK_SIZE_BYTES.js} +1 -1
  6. package/dist/api/src/ProgressIterator.js +99 -0
  7. package/dist/api/src/StreamEventSource.js +130 -0
  8. package/dist/api/src/client.js +49 -0
  9. package/dist/api/src/index.js +49 -0
  10. package/dist/{resources → api/src/resources}/caption-file.js +22 -2
  11. package/dist/{resources → api/src/resources}/image-file.js +24 -4
  12. package/dist/api/src/resources/isobmff-file.js +102 -0
  13. package/dist/{resources → api/src/resources}/isobmff-track.js +44 -1
  14. package/dist/api/src/resources/process-isobmff.js +22 -0
  15. package/dist/{resources → api/src/resources}/renders.js +21 -2
  16. package/dist/api/src/resources/transcriptions.js +45 -0
  17. package/dist/api/src/resources/unprocessed-file.js +114 -0
  18. package/dist/api/src/streamChunker.js +28 -0
  19. package/dist/{uploadChunks.js → api/src/uploadChunks.js} +1 -4
  20. package/dist/cli/src/utils/createReadableStreamFromReadable.js +82 -0
  21. package/dist/client.d.ts +6 -3
  22. package/dist/index.d.ts +7 -5
  23. package/dist/readableFromBuffers.d.ts +1 -2
  24. package/dist/resources/caption-file.d.ts +7 -3
  25. package/dist/resources/image-file.d.ts +15 -5
  26. package/dist/resources/isobmff-file.d.ts +29 -3
  27. package/dist/resources/isobmff-track.d.ts +13 -15
  28. package/dist/resources/process-isobmff.d.ts +12 -0
  29. package/dist/resources/process-isobmff.test.d.ts +1 -0
  30. package/dist/resources/renders.d.ts +10 -5
  31. package/dist/resources/transcriptions.d.ts +24 -0
  32. package/dist/resources/transcriptions.test.d.ts +1 -0
  33. package/dist/resources/unprocessed-file.d.ts +15 -53
  34. package/dist/streamChunker.d.ts +1 -2
  35. package/dist/uploadChunks.d.ts +1 -2
  36. package/package.json +3 -2
  37. package/src/resources/caption-file.test.ts +57 -6
  38. package/src/resources/caption-file.ts +34 -5
  39. package/src/resources/image-file.test.ts +59 -11
  40. package/src/resources/image-file.ts +75 -9
  41. package/src/resources/isobmff-file.test.ts +57 -6
  42. package/src/resources/isobmff-file.ts +96 -5
  43. package/src/resources/isobmff-track.test.ts +3 -3
  44. package/src/resources/isobmff-track.ts +50 -5
  45. package/src/resources/process-isobmff.test.ts +62 -0
  46. package/src/resources/process-isobmff.ts +33 -0
  47. package/src/resources/renders.test.ts +51 -6
  48. package/src/resources/renders.ts +34 -5
  49. package/src/resources/transcriptions.test.ts +49 -0
  50. package/src/resources/transcriptions.ts +64 -0
  51. package/src/resources/unprocessed-file.test.ts +24 -437
  52. package/src/resources/unprocessed-file.ts +90 -160
  53. package/dist/client.js +0 -35
  54. package/dist/index.js +0 -36
  55. package/dist/resources/isobmff-file.js +0 -47
  56. package/dist/resources/unprocessed-file.js +0 -180
  57. package/dist/streamChunker.js +0 -17
  58. /package/dist/{resources → api/src/resources}/url-token.js +0 -0
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ import { CompletionIterator } from '../ProgressIterator.ts';
3
+ import { Client } from '../client.ts';
4
+ export declare const CreateTranscriptionPayload: z.ZodObject<{
5
+ file_id: z.ZodString;
6
+ track_id: z.ZodNumber;
7
+ }, "strip", z.ZodTypeAny, {
8
+ file_id: string;
9
+ track_id: number;
10
+ }, {
11
+ file_id: string;
12
+ track_id: number;
13
+ }>;
14
+ export interface CreateTranscriptionResult {
15
+ id: string;
16
+ status: "complete" | "created" | "failed" | "pending" | "transcribing";
17
+ }
18
+ export interface TranscriptionInfoResult {
19
+ id: string;
20
+ status: "complete" | "created" | "failed" | "pending" | "transcribing";
21
+ }
22
+ export declare const createTranscription: (client: Client, payload: z.infer<typeof CreateTranscriptionPayload>) => Promise<CreateTranscriptionResult>;
23
+ export declare const getTranscriptionProgress: (client: Client, id: string) => Promise<CompletionIterator>;
24
+ export declare const getTranscriptionInfo: (client: Client, id: string) => Promise<TranscriptionInfoResult>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,78 +1,40 @@
1
- import { Readable } from 'node:stream';
2
1
  import { z } from 'zod';
3
2
  import { Client } from '../client.ts';
4
- import { IteratorWithPromise, UploadChunkEvent } from '../uploadChunks.ts';
5
- declare const FileProcessors: z.ZodEffects<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"isobmff">, z.ZodLiteral<"image">, z.ZodLiteral<"captions">, z.ZodString]>, "many">, string[], string[]>;
6
3
  export declare const CreateUnprocessedFilePayload: z.ZodObject<{
7
4
  md5: z.ZodString;
8
5
  filename: z.ZodString;
9
- processes: z.ZodOptional<z.ZodEffects<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"isobmff">, z.ZodLiteral<"image">, z.ZodLiteral<"captions">, z.ZodString]>, "many">, string[], string[]>>;
10
6
  byte_size: z.ZodNumber;
11
7
  }, "strip", z.ZodTypeAny, {
12
8
  md5: string;
13
9
  filename: string;
14
10
  byte_size: number;
15
- processes?: string[] | undefined;
16
11
  }, {
17
12
  md5: string;
18
13
  filename: string;
19
14
  byte_size: number;
20
- processes?: string[] | undefined;
21
15
  }>;
22
- export declare const UpdateUnprocessedFilePayload: z.ZodObject<{
23
- processes: z.ZodOptional<z.ZodEffects<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"isobmff">, z.ZodLiteral<"image">, z.ZodLiteral<"captions">, z.ZodString]>, "many">, string[], string[]>>;
24
- }, "strip", z.ZodTypeAny, {
25
- processes?: string[] | undefined;
26
- }, {
27
- processes?: string[] | undefined;
28
- }>;
29
- export interface CreateUnprocessedFileResult {
16
+ export declare const UpdateUnprocessedFilePayload: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
17
+ interface UnprocessedFile {
30
18
  byte_size: number;
31
19
  next_byte: number;
32
20
  complete: boolean;
33
21
  id: string;
34
22
  md5: string;
35
- processes: z.infer<typeof FileProcessors> & string[];
36
- asset_id: string;
37
23
  }
38
- export interface UpdateUnprocessedFileResult {
39
- byte_size?: number;
40
- next_byte: number;
41
- complete: boolean;
24
+ type UnprocessedFileUploadDetails = Pick<UnprocessedFile, "id" | "byte_size">;
25
+ export interface CreateUnprocessedFileResult extends UnprocessedFile {
26
+ }
27
+ export interface LookupUnprocessedFileByMd5Result extends UnprocessedFile {
28
+ }
29
+ export interface UpdateUnprocessedFileResult extends UnprocessedFile {
30
+ }
31
+ export interface ProcessIsobmffFileResult {
42
32
  id: string;
43
- md5: string;
44
- processes: z.infer<typeof FileProcessors>;
45
- asset_id: string;
46
33
  }
34
+ export declare const createUnprocessedFileFromPath: (client: Client, path: string) => Promise<CreateUnprocessedFileResult>;
47
35
  export declare const createUnprocessedFile: (client: Client, payload: z.infer<typeof CreateUnprocessedFilePayload>) => Promise<CreateUnprocessedFileResult>;
48
- export declare const updateUnprocessedFile: (client: Client, fileId: string, payload: Partial<z.infer<typeof UpdateUnprocessedFilePayload>>) => Promise<UpdateUnprocessedFileResult>;
49
- export declare const uploadUnprocessedFile: (client: Client, fileId: string, fileStream: Readable, fileSize: number) => IteratorWithPromise<UploadChunkEvent>;
50
- export declare const processAVFileBuffer: (client: Client, buffer: Buffer, filename?: string) => {
51
- progress(): AsyncGenerator<{
52
- type: string;
53
- progress: number;
54
- }, void, unknown>;
55
- file: () => Promise<UpdateUnprocessedFileResult>;
56
- };
57
- export declare const processAVFile: (client: Client, filePath: string) => {
58
- progress: () => Promise<AsyncGenerator<{
59
- type: string;
60
- progress: number;
61
- }, void, unknown>>;
62
- file: () => Promise<UpdateUnprocessedFileResult>;
63
- };
64
- export declare const processImageFileBuffer: (client: Client, buffer: Buffer, filename?: string) => {
65
- progress(): AsyncGenerator<{
66
- type: string;
67
- progress: number;
68
- }, void, unknown>;
69
- file: () => Promise<UpdateUnprocessedFileResult>;
70
- };
71
- export declare const processImageFile: (client: Client, filePath: string) => {
72
- progress: () => Promise<AsyncGenerator<{
73
- type: string;
74
- progress: number;
75
- }, void, unknown>>;
76
- file: () => Promise<UpdateUnprocessedFileResult>;
77
- };
36
+ export declare const uploadUnprocessedFile: (client: Client, uploadDetails: UnprocessedFileUploadDetails, path: string) => Promise<import('../uploadChunks.ts').IteratorWithPromise<import('../uploadChunks.ts').UploadChunkEvent>>;
37
+ export declare const uploadUnprocessedReadableStream: (client: Client, uploadDetails: UnprocessedFileUploadDetails, fileStream: ReadableStream) => import('../uploadChunks.ts').IteratorWithPromise<import('../uploadChunks.ts').UploadChunkEvent>;
38
+ export declare const lookupUnprocessedFileByMd5: (client: Client, md5: string) => Promise<LookupUnprocessedFileByMd5Result | null>;
39
+ export declare const processIsobmffFile: (client: Client, id: string) => Promise<ProcessIsobmffFileResult>;
78
40
  export {};
@@ -1,2 +1 @@
1
- import { Readable } from 'node:stream';
2
- export declare function streamChunker(readableStream: Readable, chunkSize?: number): AsyncGenerator<Buffer, void, unknown>;
1
+ export declare function streamChunker(readableStream: ReadableStream, chunkSize?: number): AsyncGenerator<Uint8Array, void, unknown>;
@@ -1,4 +1,3 @@
1
- import { Readable } from 'node:stream';
2
1
  import { Client } from './client.ts';
3
2
  export interface IteratorWithPromise<T> extends AsyncGenerator<T, void, unknown> {
4
3
  whenUploaded: () => Promise<T[]>;
@@ -6,7 +5,7 @@ export interface IteratorWithPromise<T> extends AsyncGenerator<T, void, unknown>
6
5
  export declare const fakeCompleteUpload: () => IteratorWithPromise<UploadChunkEvent>;
7
6
  interface UploadChunksOptions {
8
7
  url: string;
9
- fileStream: Readable;
8
+ fileStream: ReadableStream;
10
9
  fileSize: number;
11
10
  maxSize: number;
12
11
  chunkSizeBytes?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/api",
3
- "version": "0.11.0-beta.9",
3
+ "version": "0.12.0-beta.10",
4
4
  "description": "API functions for EditFrame",
5
5
  "exports": {
6
6
  ".": {
@@ -21,6 +21,7 @@
21
21
  "devDependencies": {
22
22
  "@types/jsonwebtoken": "^9.0.6",
23
23
  "@types/node": "^20.14.13",
24
+ "eventsource-parser": "^3.0.0",
24
25
  "typedoc": "^0.26.5",
25
26
  "typescript": "^5.5.4",
26
27
  "vite": "^5.2.11",
@@ -28,7 +29,7 @@
28
29
  "vite-tsconfig-paths": "^4.3.2"
29
30
  },
30
31
  "dependencies": {
31
- "@editframe/assets": "0.11.0-beta.9",
32
+ "@editframe/assets": "0.12.0-beta.10",
32
33
  "debug": "^4.3.5",
33
34
  "jsonwebtoken": "^9.0.2",
34
35
  "node-fetch": "^3.3.2",
@@ -1,10 +1,14 @@
1
- import { test, expect, beforeAll, afterEach, afterAll, describe } from "vitest";
2
1
  import { http, HttpResponse } from "msw";
3
2
  import { setupServer } from "msw/node";
3
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
4
4
 
5
5
  import { Client } from "../client.ts";
6
- import { createCaptionFile, uploadCaptionFile } from "./caption-file.ts";
7
- import { readableFromBuffers } from "../readableFromBuffers.ts";
6
+ import { webReadableFromBuffers } from "../readableFromBuffers.ts";
7
+ import {
8
+ createCaptionFile,
9
+ lookupCaptionFileByMd5,
10
+ uploadCaptionFile,
11
+ } from "./caption-file.ts";
8
12
 
9
13
  const server = setupServer();
10
14
  const client = new Client("ef_TEST_TOKEN", "http://localhost");
@@ -71,7 +75,7 @@ describe("CaptionFile", () => {
71
75
  uploadCaptionFile(
72
76
  client,
73
77
  "test-id",
74
- readableFromBuffers(Buffer.from("test")),
78
+ webReadableFromBuffers(Buffer.from("test")),
75
79
  1024 * 1024 * 3,
76
80
  ),
77
81
  ).rejects.toThrowError(
@@ -90,7 +94,7 @@ describe("CaptionFile", () => {
90
94
  uploadCaptionFile(
91
95
  client,
92
96
  "test-id",
93
- readableFromBuffers(Buffer.from("nice")),
97
+ webReadableFromBuffers(Buffer.from("nice")),
94
98
  4,
95
99
  ),
96
100
  ).rejects.toThrowError(
@@ -111,11 +115,58 @@ describe("CaptionFile", () => {
111
115
  const response = await uploadCaptionFile(
112
116
  client,
113
117
  "test-id",
114
- readableFromBuffers(Buffer.from("nice")),
118
+ webReadableFromBuffers(Buffer.from("nice")),
115
119
  4,
116
120
  );
117
121
 
118
122
  expect(response).toEqual({ id: "test-id" });
119
123
  });
120
124
  });
125
+
126
+ describe("lookupCaptionFileByMd5", () => {
127
+ test("Returns json data from the http response", async () => {
128
+ server.use(
129
+ http.get("http://localhost/api/v1/caption_files/md5/test-md5", () =>
130
+ HttpResponse.json(
131
+ { id: "test-id", md5: "test-md5", complete: true },
132
+ { status: 200, statusText: "OK" },
133
+ ),
134
+ ),
135
+ );
136
+
137
+ const response = await lookupCaptionFileByMd5(client, "test-md5");
138
+
139
+ expect(response).toEqual({
140
+ id: "test-id",
141
+ md5: "test-md5",
142
+ complete: true,
143
+ });
144
+ });
145
+
146
+ test("Returns null when file is not found", async () => {
147
+ server.use(
148
+ http.get("http://localhost/api/v1/caption_files/md5/test-md5", () =>
149
+ HttpResponse.json({}, { status: 404 }),
150
+ ),
151
+ );
152
+
153
+ const response = await lookupCaptionFileByMd5(client, "test-md5");
154
+
155
+ expect(response).toBeNull();
156
+ });
157
+
158
+ test("Throws when server returns an error", async () => {
159
+ server.use(
160
+ http.get("http://localhost/api/v1/caption_files/md5/test-md5", () =>
161
+ HttpResponse.text("Internal Server Error", { status: 500 }),
162
+ ),
163
+ );
164
+
165
+ await expect(
166
+ lookupCaptionFileByMd5(client, "test-md5"),
167
+ ).rejects.toThrowError(
168
+ "Failed to lookup caption by md5 test-md5 500 Internal Server Error",
169
+ );
170
+ });
171
+ });
121
172
  });
@@ -1,7 +1,5 @@
1
- import type { Readable } from "node:stream";
2
-
3
- import { z } from "zod";
4
1
  import debug from "debug";
2
+ import { z } from "zod";
5
3
 
6
4
  import type { Client } from "../client.ts";
7
5
 
@@ -19,7 +17,12 @@ export interface CreateCaptionFileResult {
19
17
  complete: boolean | null;
20
18
  id: string;
21
19
  md5: string;
22
- asset_id: string;
20
+ }
21
+
22
+ export interface LookupCaptionFileByMd5Result {
23
+ complete: boolean | null;
24
+ id: string;
25
+ md5: string;
23
26
  }
24
27
 
25
28
  const restrictSize = (size: number) => {
@@ -71,7 +74,7 @@ export const createCaptionFile = async (
71
74
  export const uploadCaptionFile = async (
72
75
  client: Client,
73
76
  fileId: string,
74
- fileStream: Readable,
77
+ fileStream: ReadableStream,
75
78
  fileSize: number,
76
79
  ) => {
77
80
  log("Uploading caption file", fileId);
@@ -82,6 +85,7 @@ export const uploadCaptionFile = async (
82
85
  {
83
86
  method: "POST",
84
87
  body: fileStream,
88
+ duplex: "half",
85
89
  },
86
90
  );
87
91
  log("Caption file uploaded", response);
@@ -94,3 +98,28 @@ export const uploadCaptionFile = async (
94
98
  `Failed to upload caption ${response.status} ${response.statusText}`,
95
99
  );
96
100
  };
101
+
102
+ export const lookupCaptionFileByMd5 = async (
103
+ client: Client,
104
+ md5: string,
105
+ ): Promise<LookupCaptionFileByMd5Result | null> => {
106
+ const response = await client.authenticatedFetch(
107
+ `/api/v1/caption_files/md5/${md5}`,
108
+ {
109
+ method: "GET",
110
+ },
111
+ );
112
+ log("Caption file lookup", response);
113
+
114
+ if (response.ok) {
115
+ return (await response.json()) as LookupCaptionFileByMd5Result;
116
+ }
117
+
118
+ if (response.status === 404) {
119
+ return null;
120
+ }
121
+
122
+ throw new Error(
123
+ `Failed to lookup caption by md5 ${md5} ${response.status} ${response.statusText}`,
124
+ );
125
+ };
@@ -4,8 +4,12 @@ import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
4
4
  import { ZodError } from "zod";
5
5
 
6
6
  import { Client } from "../client.ts";
7
- import { readableFromBuffers } from "../readableFromBuffers.ts";
8
- import { createImageFile, uploadImageFile } from "./image-file.ts";
7
+ import { webReadableFromBuffers } from "../readableFromBuffers.ts";
8
+ import {
9
+ createImageFile,
10
+ lookupImageFileByMd5,
11
+ uploadImageFile,
12
+ } from "./image-file.ts";
9
13
 
10
14
  const server = setupServer();
11
15
  const client = new Client("ef_TEST_TOKEN", "http://localhost");
@@ -93,9 +97,8 @@ describe("ImageFile", () => {
93
97
  await expect(
94
98
  uploadImageFile(
95
99
  client,
96
- "test-file-id",
97
- readableFromBuffers(Buffer.from("test")),
98
- 1024 * 1024 * 17,
100
+ { id: "test-file-id", byte_size: 1024 * 1024 * 17 },
101
+ webReadableFromBuffers(Buffer.from("test")),
99
102
  ).whenUploaded(),
100
103
  ).rejects.toThrowError(
101
104
  "File size 17825792 bytes exceeds limit 16777216 bytes",
@@ -114,9 +117,8 @@ describe("ImageFile", () => {
114
117
  await expect(
115
118
  uploadImageFile(
116
119
  client,
117
- "test-file-id",
118
- readableFromBuffers(Buffer.from("test")),
119
- 4,
120
+ { id: "test-file-id", byte_size: 4 },
121
+ webReadableFromBuffers(Buffer.from("test")),
120
122
  ).whenUploaded(),
121
123
  ).rejects.toThrowError(
122
124
  "Failed to upload chunk 0 for /api/v1/image_files/test-file-id/upload 500 Internal Server Error",
@@ -135,9 +137,8 @@ describe("ImageFile", () => {
135
137
  await expect(
136
138
  uploadImageFile(
137
139
  client,
138
- "test-file-id",
139
- readableFromBuffers(Buffer.from("test")),
140
- 4,
140
+ { id: "test-file-id", byte_size: 4 },
141
+ webReadableFromBuffers(Buffer.from("test")),
141
142
  ).whenUploaded(),
142
143
  ).resolves.toEqual([
143
144
  { type: "progress", progress: 0 },
@@ -145,4 +146,51 @@ describe("ImageFile", () => {
145
146
  ]);
146
147
  });
147
148
  });
149
+
150
+ describe("lookupImageFileByMd5", () => {
151
+ test("Returns json data from the http response", async () => {
152
+ server.use(
153
+ http.get("http://localhost/api/v1/image_files/md5/test-md5", () =>
154
+ HttpResponse.json(
155
+ { id: "test-id", md5: "test-md5", complete: true },
156
+ { status: 200, statusText: "OK" },
157
+ ),
158
+ ),
159
+ );
160
+
161
+ const response = await lookupImageFileByMd5(client, "test-md5");
162
+
163
+ expect(response).toEqual({
164
+ id: "test-id",
165
+ md5: "test-md5",
166
+ complete: true,
167
+ });
168
+ });
169
+
170
+ test("Returns null when file is not found", async () => {
171
+ server.use(
172
+ http.get("http://localhost/api/v1/image_files/md5/test-md5", () =>
173
+ HttpResponse.json({}, { status: 404 }),
174
+ ),
175
+ );
176
+
177
+ const response = await lookupImageFileByMd5(client, "test-md5");
178
+
179
+ expect(response).toBeNull();
180
+ });
181
+
182
+ test("Throws when server returns an error", async () => {
183
+ server.use(
184
+ http.get("http://localhost/api/v1/image_files/md5/test-md5", () =>
185
+ HttpResponse.text("Internal Server Error", { status: 500 }),
186
+ ),
187
+ );
188
+
189
+ await expect(
190
+ lookupImageFileByMd5(client, "test-md5"),
191
+ ).rejects.toThrowError(
192
+ "Failed to lookup image by md5 test-md5 500 Internal Server Error",
193
+ );
194
+ });
195
+ });
148
196
  });
@@ -1,6 +1,5 @@
1
- import type { Readable } from "node:stream";
2
-
3
1
  import debug from "debug";
2
+ import mime from "mime";
4
3
  import { z } from "zod";
5
4
 
6
5
  import type { Client } from "../client.ts";
@@ -21,11 +20,51 @@ export const CreateImageFilePayload = z.object({
21
20
 
22
21
  export interface CreateImageFileResult {
23
22
  complete: boolean | null;
23
+ byte_size: number;
24
24
  id: string;
25
25
  md5: string;
26
- asset_id: string;
27
26
  }
28
27
 
28
+ export interface LookupImageFileByMd5Result {
29
+ complete: boolean | null;
30
+ byte_size: number;
31
+ id: string;
32
+ md5: string;
33
+ }
34
+
35
+ export const createImageFileFromPath = async (client: Client, path: string) => {
36
+ const [{ stat }, { basename }, { md5FilePath }] = await Promise.all([
37
+ import("node:fs/promises"),
38
+ import("node:path"),
39
+ import("@editframe/assets"),
40
+ ]).catch((error) => {
41
+ console.error("Error importing modules", error);
42
+ console.error(
43
+ "This is likely because you are bundling for the browser. createImageFileFromPath can only be run in environments that support importing `node:path`",
44
+ );
45
+ throw error;
46
+ });
47
+
48
+ const fileInfo = await stat(path);
49
+
50
+ const byte_size = fileInfo.size;
51
+
52
+ const md5 = await md5FilePath(path);
53
+
54
+ const mime_type = mime.getType(path);
55
+
56
+ return createImageFile(client, {
57
+ ...CreateImageFilePayload.parse({
58
+ md5,
59
+ height: 0,
60
+ width: 0,
61
+ mime_type,
62
+ filename: basename(path),
63
+ byte_size,
64
+ }),
65
+ });
66
+ };
67
+
29
68
  export const createImageFile = async (
30
69
  client: Client,
31
70
  payload: z.infer<typeof CreateImageFilePayload>,
@@ -50,16 +89,43 @@ export const createImageFile = async (
50
89
 
51
90
  export const uploadImageFile = (
52
91
  client: Client,
53
- fileId: string,
54
- fileStream: Readable,
55
- fileSize: number,
92
+ uploadDetails: {
93
+ id: string;
94
+ byte_size: number;
95
+ },
96
+ fileStream: ReadableStream,
56
97
  ) => {
57
- log("Uploading image file", fileId);
98
+ log("Uploading image file", uploadDetails.id);
58
99
 
59
100
  return uploadChunks(client, {
60
- url: `/api/v1/image_files/${fileId}/upload`,
61
- fileSize,
101
+ url: `/api/v1/image_files/${uploadDetails.id}/upload`,
102
+ fileSize: uploadDetails.byte_size,
62
103
  fileStream,
63
104
  maxSize: MAX_IMAGE_SIZE,
64
105
  });
65
106
  };
107
+
108
+ export const lookupImageFileByMd5 = async (
109
+ client: Client,
110
+ md5: string,
111
+ ): Promise<LookupImageFileByMd5Result | null> => {
112
+ const response = await client.authenticatedFetch(
113
+ `/api/v1/image_files/md5/${md5}`,
114
+ {
115
+ method: "GET",
116
+ },
117
+ );
118
+ log("Image file lookup", response);
119
+
120
+ if (response.ok) {
121
+ return (await response.json()) as LookupImageFileByMd5Result;
122
+ }
123
+
124
+ if (response.status === 404) {
125
+ return null;
126
+ }
127
+
128
+ throw new Error(
129
+ `Failed to lookup image by md5 ${md5} ${response.status} ${response.statusText}`,
130
+ );
131
+ };
@@ -1,10 +1,14 @@
1
- import { test, expect, beforeAll, afterEach, afterAll, describe } from "vitest";
2
1
  import { http, HttpResponse } from "msw";
3
2
  import { setupServer } from "msw/node";
3
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
4
4
 
5
5
  import { Client } from "../client.ts";
6
- import { createISOBMFFFile, uploadFragmentIndex } from "./isobmff-file.ts";
7
- import { readableFromBuffers } from "../readableFromBuffers.ts";
6
+ import { webReadableFromBuffers } from "../readableFromBuffers.ts";
7
+ import {
8
+ createISOBMFFFile,
9
+ lookupISOBMFFFileByMd5,
10
+ uploadFragmentIndex,
11
+ } from "./isobmff-file.ts";
8
12
 
9
13
  const server = setupServer();
10
14
  const client = new Client("ef_TEST_TOKEN", "http://localhost");
@@ -57,7 +61,7 @@ describe("ISOBMFFFile", () => {
57
61
  uploadFragmentIndex(
58
62
  client,
59
63
  "test-id",
60
- readableFromBuffers(Buffer.from("test")),
64
+ webReadableFromBuffers(Buffer.from("test")),
61
65
  1024 * 1024 * 3,
62
66
  ),
63
67
  ).rejects.toThrowError("File size exceeds limit of 2097152 bytes");
@@ -75,7 +79,7 @@ describe("ISOBMFFFile", () => {
75
79
  uploadFragmentIndex(
76
80
  client,
77
81
  "test-id",
78
- readableFromBuffers(Buffer.from("test")),
82
+ webReadableFromBuffers(Buffer.from("test")),
79
83
  4,
80
84
  ),
81
85
  ).rejects.toThrowError(
@@ -98,11 +102,58 @@ describe("ISOBMFFFile", () => {
98
102
  const response = await uploadFragmentIndex(
99
103
  client,
100
104
  "test-id",
101
- readableFromBuffers(Buffer.from("test")),
105
+ webReadableFromBuffers(Buffer.from("test")),
102
106
  4,
103
107
  );
104
108
 
105
109
  expect(response).toEqual({ fragment_index_complete: true });
106
110
  });
107
111
  });
112
+
113
+ describe("lookupISOBMFFFileByMd5", () => {
114
+ test("Returns json data from the http response", async () => {
115
+ server.use(
116
+ http.get("http://localhost/api/v1/isobmff_files/md5/test-md5", () =>
117
+ HttpResponse.json(
118
+ { id: "test-id", md5: "test-md5", fragment_index_complete: true },
119
+ { status: 200, statusText: "OK" },
120
+ ),
121
+ ),
122
+ );
123
+
124
+ const response = await lookupISOBMFFFileByMd5(client, "test-md5");
125
+
126
+ expect(response).toEqual({
127
+ id: "test-id",
128
+ md5: "test-md5",
129
+ fragment_index_complete: true,
130
+ });
131
+ });
132
+
133
+ test("Returns null when file is not found", async () => {
134
+ server.use(
135
+ http.get("http://localhost/api/v1/isobmff_files/md5/test-md5", () =>
136
+ HttpResponse.json({}, { status: 404 }),
137
+ ),
138
+ );
139
+
140
+ const response = await lookupISOBMFFFileByMd5(client, "test-md5");
141
+
142
+ expect(response).toBeNull();
143
+ });
144
+
145
+ test("Throws when server returns an error", async () => {
146
+ server.use(
147
+ http.get("http://localhost/api/v1/isobmff_files/md5/test-md5", () =>
148
+ HttpResponse.text("Internal Server Error", { status: 500 }),
149
+ ),
150
+ );
151
+
152
+ await expect(
153
+ lookupISOBMFFFileByMd5(client, "test-md5"),
154
+ ).rejects.toThrowError(
155
+ "Failed to lookup isobmff file by md5 test-md5 500 Internal Server Error",
156
+ );
157
+ });
158
+ });
108
159
  });