@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.
- package/dist/ProgressIterator.d.ts +25 -0
- package/dist/ProgressIterator.test.d.ts +1 -0
- package/dist/StreamEventSource.d.ts +50 -0
- package/dist/StreamEventSource.test.d.ts +1 -0
- package/dist/{CHUNK_SIZE_BYTES.js → api/src/CHUNK_SIZE_BYTES.js} +1 -1
- package/dist/api/src/ProgressIterator.js +99 -0
- package/dist/api/src/StreamEventSource.js +130 -0
- package/dist/api/src/client.js +49 -0
- package/dist/api/src/index.js +49 -0
- package/dist/{resources → api/src/resources}/caption-file.js +22 -2
- package/dist/{resources → api/src/resources}/image-file.js +24 -4
- package/dist/api/src/resources/isobmff-file.js +102 -0
- package/dist/{resources → api/src/resources}/isobmff-track.js +44 -1
- package/dist/api/src/resources/process-isobmff.js +22 -0
- package/dist/{resources → api/src/resources}/renders.js +21 -2
- package/dist/api/src/resources/transcriptions.js +45 -0
- package/dist/api/src/resources/unprocessed-file.js +114 -0
- package/dist/api/src/streamChunker.js +28 -0
- package/dist/{uploadChunks.js → api/src/uploadChunks.js} +1 -4
- package/dist/cli/src/utils/createReadableStreamFromReadable.js +82 -0
- package/dist/client.d.ts +6 -3
- package/dist/index.d.ts +7 -5
- package/dist/readableFromBuffers.d.ts +1 -2
- package/dist/resources/caption-file.d.ts +7 -3
- package/dist/resources/image-file.d.ts +15 -5
- package/dist/resources/isobmff-file.d.ts +29 -3
- package/dist/resources/isobmff-track.d.ts +13 -15
- package/dist/resources/process-isobmff.d.ts +12 -0
- package/dist/resources/process-isobmff.test.d.ts +1 -0
- package/dist/resources/renders.d.ts +10 -5
- package/dist/resources/transcriptions.d.ts +24 -0
- package/dist/resources/transcriptions.test.d.ts +1 -0
- package/dist/resources/unprocessed-file.d.ts +15 -53
- package/dist/streamChunker.d.ts +1 -2
- package/dist/uploadChunks.d.ts +1 -2
- package/package.json +3 -2
- package/src/resources/caption-file.test.ts +57 -6
- package/src/resources/caption-file.ts +34 -5
- package/src/resources/image-file.test.ts +59 -11
- package/src/resources/image-file.ts +75 -9
- package/src/resources/isobmff-file.test.ts +57 -6
- package/src/resources/isobmff-file.ts +96 -5
- package/src/resources/isobmff-track.test.ts +3 -3
- package/src/resources/isobmff-track.ts +50 -5
- package/src/resources/process-isobmff.test.ts +62 -0
- package/src/resources/process-isobmff.ts +33 -0
- package/src/resources/renders.test.ts +51 -6
- package/src/resources/renders.ts +34 -5
- package/src/resources/transcriptions.test.ts +49 -0
- package/src/resources/transcriptions.ts +64 -0
- package/src/resources/unprocessed-file.test.ts +24 -437
- package/src/resources/unprocessed-file.ts +90 -160
- package/dist/client.js +0 -35
- package/dist/index.js +0 -36
- package/dist/resources/isobmff-file.js +0 -47
- package/dist/resources/unprocessed-file.js +0 -180
- package/dist/streamChunker.js +0 -17
- /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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
49
|
-
export declare const
|
|
50
|
-
export declare const
|
|
51
|
-
|
|
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 {};
|
package/dist/streamChunker.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
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>;
|
package/dist/uploadChunks.d.ts
CHANGED
|
@@ -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:
|
|
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.
|
|
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.
|
|
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 {
|
|
7
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 {
|
|
8
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
92
|
+
uploadDetails: {
|
|
93
|
+
id: string;
|
|
94
|
+
byte_size: number;
|
|
95
|
+
},
|
|
96
|
+
fileStream: ReadableStream,
|
|
56
97
|
) => {
|
|
57
|
-
log("Uploading image file",
|
|
98
|
+
log("Uploading image file", uploadDetails.id);
|
|
58
99
|
|
|
59
100
|
return uploadChunks(client, {
|
|
60
|
-
url: `/api/v1/image_files/${
|
|
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 {
|
|
7
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|