@editframe/api 0.11.0-beta.9 → 0.12.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.
- package/dist/CHUNK_SIZE_BYTES.js +1 -1
- package/dist/ProgressIterator.d.ts +25 -0
- package/dist/ProgressIterator.js +99 -0
- package/dist/ProgressIterator.test.d.ts +1 -0
- package/dist/StreamEventSource.d.ts +50 -0
- package/dist/StreamEventSource.js +130 -0
- package/dist/StreamEventSource.test.d.ts +1 -0
- package/dist/client.d.ts +6 -3
- package/dist/client.js +20 -6
- package/dist/index.d.ts +7 -5
- package/dist/index.js +20 -11
- package/dist/readableFromBuffers.d.ts +1 -2
- package/dist/resources/caption-file.d.ts +7 -3
- package/dist/resources/caption-file.js +22 -2
- package/dist/resources/image-file.d.ts +7 -3
- package/dist/resources/image-file.js +19 -0
- package/dist/resources/isobmff-file.d.ts +16 -3
- package/dist/resources/isobmff-file.js +37 -2
- package/dist/resources/isobmff-track.d.ts +5 -7
- package/dist/resources/isobmff-track.js +44 -1
- package/dist/resources/process-isobmff.d.ts +12 -0
- package/dist/resources/process-isobmff.js +22 -0
- package/dist/resources/process-isobmff.test.d.ts +1 -0
- package/dist/resources/renders.d.ts +10 -5
- package/dist/resources/renders.js +21 -2
- package/dist/resources/transcriptions.d.ts +24 -0
- package/dist/resources/transcriptions.js +45 -0
- package/dist/resources/transcriptions.test.d.ts +1 -0
- package/dist/resources/unprocessed-file.d.ts +12 -53
- package/dist/resources/unprocessed-file.js +31 -130
- package/dist/streamChunker.d.ts +1 -2
- package/dist/streamChunker.js +20 -9
- package/dist/uploadChunks.d.ts +1 -2
- package/dist/uploadChunks.js +1 -4
- 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 +56 -5
- package/src/resources/image-file.ts +32 -4
- package/src/resources/isobmff-file.test.ts +57 -6
- package/src/resources/isobmff-file.ts +64 -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 +19 -430
- package/src/resources/unprocessed-file.ts +45 -161
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0-beta.2",
|
|
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.2",
|
|
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");
|
|
@@ -94,7 +98,7 @@ describe("ImageFile", () => {
|
|
|
94
98
|
uploadImageFile(
|
|
95
99
|
client,
|
|
96
100
|
"test-file-id",
|
|
97
|
-
|
|
101
|
+
webReadableFromBuffers(Buffer.from("test")),
|
|
98
102
|
1024 * 1024 * 17,
|
|
99
103
|
).whenUploaded(),
|
|
100
104
|
).rejects.toThrowError(
|
|
@@ -115,7 +119,7 @@ describe("ImageFile", () => {
|
|
|
115
119
|
uploadImageFile(
|
|
116
120
|
client,
|
|
117
121
|
"test-file-id",
|
|
118
|
-
|
|
122
|
+
webReadableFromBuffers(Buffer.from("test")),
|
|
119
123
|
4,
|
|
120
124
|
).whenUploaded(),
|
|
121
125
|
).rejects.toThrowError(
|
|
@@ -136,7 +140,7 @@ describe("ImageFile", () => {
|
|
|
136
140
|
uploadImageFile(
|
|
137
141
|
client,
|
|
138
142
|
"test-file-id",
|
|
139
|
-
|
|
143
|
+
webReadableFromBuffers(Buffer.from("test")),
|
|
140
144
|
4,
|
|
141
145
|
).whenUploaded(),
|
|
142
146
|
).resolves.toEqual([
|
|
@@ -145,4 +149,51 @@ describe("ImageFile", () => {
|
|
|
145
149
|
]);
|
|
146
150
|
});
|
|
147
151
|
});
|
|
152
|
+
|
|
153
|
+
describe("lookupImageFileByMd5", () => {
|
|
154
|
+
test("Returns json data from the http response", async () => {
|
|
155
|
+
server.use(
|
|
156
|
+
http.get("http://localhost/api/v1/image_files/md5/test-md5", () =>
|
|
157
|
+
HttpResponse.json(
|
|
158
|
+
{ id: "test-id", md5: "test-md5", complete: true },
|
|
159
|
+
{ status: 200, statusText: "OK" },
|
|
160
|
+
),
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const response = await lookupImageFileByMd5(client, "test-md5");
|
|
165
|
+
|
|
166
|
+
expect(response).toEqual({
|
|
167
|
+
id: "test-id",
|
|
168
|
+
md5: "test-md5",
|
|
169
|
+
complete: true,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("Returns null when file is not found", async () => {
|
|
174
|
+
server.use(
|
|
175
|
+
http.get("http://localhost/api/v1/image_files/md5/test-md5", () =>
|
|
176
|
+
HttpResponse.json({}, { status: 404 }),
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const response = await lookupImageFileByMd5(client, "test-md5");
|
|
181
|
+
|
|
182
|
+
expect(response).toBeNull();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("Throws when server returns an error", async () => {
|
|
186
|
+
server.use(
|
|
187
|
+
http.get("http://localhost/api/v1/image_files/md5/test-md5", () =>
|
|
188
|
+
HttpResponse.text("Internal Server Error", { status: 500 }),
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
await expect(
|
|
193
|
+
lookupImageFileByMd5(client, "test-md5"),
|
|
194
|
+
).rejects.toThrowError(
|
|
195
|
+
"Failed to lookup image by md5 test-md5 500 Internal Server Error",
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
148
199
|
});
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { Readable } from "node:stream";
|
|
2
|
-
|
|
3
1
|
import debug from "debug";
|
|
4
2
|
import { z } from "zod";
|
|
5
3
|
|
|
@@ -23,7 +21,12 @@ export interface CreateImageFileResult {
|
|
|
23
21
|
complete: boolean | null;
|
|
24
22
|
id: string;
|
|
25
23
|
md5: string;
|
|
26
|
-
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LookupImageFileByMd5Result {
|
|
27
|
+
complete: boolean | null;
|
|
28
|
+
id: string;
|
|
29
|
+
md5: string;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export const createImageFile = async (
|
|
@@ -51,7 +54,7 @@ export const createImageFile = async (
|
|
|
51
54
|
export const uploadImageFile = (
|
|
52
55
|
client: Client,
|
|
53
56
|
fileId: string,
|
|
54
|
-
fileStream:
|
|
57
|
+
fileStream: ReadableStream,
|
|
55
58
|
fileSize: number,
|
|
56
59
|
) => {
|
|
57
60
|
log("Uploading image file", fileId);
|
|
@@ -63,3 +66,28 @@ export const uploadImageFile = (
|
|
|
63
66
|
maxSize: MAX_IMAGE_SIZE,
|
|
64
67
|
});
|
|
65
68
|
};
|
|
69
|
+
|
|
70
|
+
export const lookupImageFileByMd5 = async (
|
|
71
|
+
client: Client,
|
|
72
|
+
md5: string,
|
|
73
|
+
): Promise<LookupImageFileByMd5Result | null> => {
|
|
74
|
+
const response = await client.authenticatedFetch(
|
|
75
|
+
`/api/v1/image_files/md5/${md5}`,
|
|
76
|
+
{
|
|
77
|
+
method: "GET",
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
log("Image file lookup", response);
|
|
81
|
+
|
|
82
|
+
if (response.ok) {
|
|
83
|
+
return (await response.json()) as LookupImageFileByMd5Result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (response.status === 404) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Failed to lookup image by md5 ${md5} ${response.status} ${response.statusText}`,
|
|
92
|
+
);
|
|
93
|
+
};
|
|
@@ -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
|
});
|
|
@@ -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
|
|
|
@@ -18,7 +16,21 @@ export interface CreateISOBMFFFileResult {
|
|
|
18
16
|
filename: string;
|
|
19
17
|
id: string;
|
|
20
18
|
md5: string;
|
|
21
|
-
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LookupISOBMFFFileByMd5Result {
|
|
22
|
+
fragment_index_complete: boolean;
|
|
23
|
+
filename: string;
|
|
24
|
+
id: string;
|
|
25
|
+
md5: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface GetISOBMFFFileTranscriptionResult {
|
|
29
|
+
id: string;
|
|
30
|
+
work_slice_ms: number;
|
|
31
|
+
isobmff_track: {
|
|
32
|
+
duration_ms: number;
|
|
33
|
+
};
|
|
22
34
|
}
|
|
23
35
|
|
|
24
36
|
export const createISOBMFFFile = async (
|
|
@@ -45,7 +57,7 @@ export const createISOBMFFFile = async (
|
|
|
45
57
|
export const uploadFragmentIndex = async (
|
|
46
58
|
client: Client,
|
|
47
59
|
fileId: string,
|
|
48
|
-
fileStream:
|
|
60
|
+
fileStream: ReadableStream,
|
|
49
61
|
fileSize: number,
|
|
50
62
|
) => {
|
|
51
63
|
log("Uploading fragment index", fileId);
|
|
@@ -57,6 +69,7 @@ export const uploadFragmentIndex = async (
|
|
|
57
69
|
{
|
|
58
70
|
method: "POST",
|
|
59
71
|
body: fileStream,
|
|
72
|
+
duplex: "half",
|
|
60
73
|
},
|
|
61
74
|
);
|
|
62
75
|
|
|
@@ -69,3 +82,49 @@ export const uploadFragmentIndex = async (
|
|
|
69
82
|
`Failed to create fragment index ${response.status} ${response.statusText}`,
|
|
70
83
|
);
|
|
71
84
|
};
|
|
85
|
+
|
|
86
|
+
export const lookupISOBMFFFileByMd5 = async (
|
|
87
|
+
client: Client,
|
|
88
|
+
md5: string,
|
|
89
|
+
): Promise<LookupISOBMFFFileByMd5Result | null> => {
|
|
90
|
+
const response = await client.authenticatedFetch(
|
|
91
|
+
`/api/v1/isobmff_files/md5/${md5}`,
|
|
92
|
+
{
|
|
93
|
+
method: "GET",
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
log("ISOBMFF file lookup", response);
|
|
97
|
+
|
|
98
|
+
if (response.ok) {
|
|
99
|
+
return (await response.json()) as LookupISOBMFFFileByMd5Result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (response.status === 404) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Failed to lookup isobmff file by md5 ${md5} ${response.status} ${response.statusText}`,
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const getISOBMFFFileTranscription = async (
|
|
112
|
+
client: Client,
|
|
113
|
+
id: string,
|
|
114
|
+
): Promise<GetISOBMFFFileTranscriptionResult | null> => {
|
|
115
|
+
const response = await client.authenticatedFetch(
|
|
116
|
+
`/api/v1/isobmff_files/${id}/transcription`,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (response.ok) {
|
|
120
|
+
return (await response.json()) as GetISOBMFFFileTranscriptionResult;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (response.status === 404) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Failed to get isobmff file transcription ${id} ${response.status} ${response.statusText}`,
|
|
129
|
+
);
|
|
130
|
+
};
|
|
@@ -5,7 +5,7 @@ import { ZodError } from "zod";
|
|
|
5
5
|
|
|
6
6
|
import { createTestTrack } from "TEST/createTestTrack.ts";
|
|
7
7
|
import { Client } from "../client.ts";
|
|
8
|
-
import {
|
|
8
|
+
import { webReadableFromBuffers } from "../readableFromBuffers.ts";
|
|
9
9
|
import { createISOBMFFTrack, uploadISOBMFFTrack } from "./isobmff-track.ts";
|
|
10
10
|
|
|
11
11
|
const server = setupServer();
|
|
@@ -92,7 +92,7 @@ describe("ISOBMFF Track", () => {
|
|
|
92
92
|
client,
|
|
93
93
|
"test-file",
|
|
94
94
|
1,
|
|
95
|
-
|
|
95
|
+
webReadableFromBuffers(Buffer.from("test")),
|
|
96
96
|
4,
|
|
97
97
|
).whenUploaded(),
|
|
98
98
|
).rejects.toThrowError(
|
|
@@ -114,7 +114,7 @@ describe("ISOBMFF Track", () => {
|
|
|
114
114
|
client,
|
|
115
115
|
"test-file",
|
|
116
116
|
1,
|
|
117
|
-
|
|
117
|
+
webReadableFromBuffers(Buffer.from("test")),
|
|
118
118
|
4,
|
|
119
119
|
).whenUploaded(),
|
|
120
120
|
).resolves.toEqual([
|
|
@@ -1,9 +1,55 @@
|
|
|
1
|
-
import type { Readable } from "node:stream";
|
|
2
|
-
|
|
3
1
|
import debug from "debug";
|
|
4
2
|
import { z } from "zod";
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
const AudioStreamSchema = z.object({
|
|
5
|
+
index: z.number(),
|
|
6
|
+
codec_name: z.string(),
|
|
7
|
+
codec_long_name: z.string(),
|
|
8
|
+
codec_type: z.literal("audio"),
|
|
9
|
+
codec_tag_string: z.string(),
|
|
10
|
+
codec_tag: z.string(),
|
|
11
|
+
sample_fmt: z.string(),
|
|
12
|
+
sample_rate: z.string(),
|
|
13
|
+
channels: z.number(),
|
|
14
|
+
channel_layout: z.string(),
|
|
15
|
+
bits_per_sample: z.number(),
|
|
16
|
+
initial_padding: z.number().optional(),
|
|
17
|
+
r_frame_rate: z.string(),
|
|
18
|
+
avg_frame_rate: z.string(),
|
|
19
|
+
time_base: z.string(),
|
|
20
|
+
start_pts: z.number(),
|
|
21
|
+
start_time: z.coerce.number(),
|
|
22
|
+
duration_ts: z.number(),
|
|
23
|
+
duration: z.coerce.number(),
|
|
24
|
+
bit_rate: z.string(),
|
|
25
|
+
disposition: z.record(z.unknown()),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
type AudioStreamSchema = z.infer<typeof AudioStreamSchema>;
|
|
29
|
+
|
|
30
|
+
const VideoStreamSchema = z.object({
|
|
31
|
+
index: z.number(),
|
|
32
|
+
codec_name: z.string(),
|
|
33
|
+
codec_long_name: z.string(),
|
|
34
|
+
codec_type: z.literal("video"),
|
|
35
|
+
codec_tag_string: z.string(),
|
|
36
|
+
codec_tag: z.string(),
|
|
37
|
+
width: z.number(),
|
|
38
|
+
height: z.number(),
|
|
39
|
+
coded_width: z.number(),
|
|
40
|
+
coded_height: z.number(),
|
|
41
|
+
r_frame_rate: z.string(),
|
|
42
|
+
avg_frame_rate: z.string(),
|
|
43
|
+
time_base: z.string(),
|
|
44
|
+
start_pts: z.number().optional(),
|
|
45
|
+
start_time: z.coerce.number().optional(),
|
|
46
|
+
duration_ts: z.number().optional(),
|
|
47
|
+
duration: z.coerce.number().optional(),
|
|
48
|
+
bit_rate: z.string().optional(),
|
|
49
|
+
disposition: z.record(z.unknown()),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
type VideoStreamSchema = z.infer<typeof VideoStreamSchema>;
|
|
7
53
|
|
|
8
54
|
import type { Client } from "../client.ts";
|
|
9
55
|
import { uploadChunks } from "../uploadChunks.ts";
|
|
@@ -38,7 +84,6 @@ export interface CreateISOBMFFTrackResult {
|
|
|
38
84
|
byte_size: number;
|
|
39
85
|
track_id: number;
|
|
40
86
|
file_id: string;
|
|
41
|
-
asset_id: string;
|
|
42
87
|
complete: boolean;
|
|
43
88
|
}
|
|
44
89
|
|
|
@@ -67,7 +112,7 @@ export const uploadISOBMFFTrack = (
|
|
|
67
112
|
client: Client,
|
|
68
113
|
fileId: string,
|
|
69
114
|
trackId: number,
|
|
70
|
-
fileStream:
|
|
115
|
+
fileStream: ReadableStream,
|
|
71
116
|
trackSize: number,
|
|
72
117
|
) => {
|
|
73
118
|
log("Uploading fragment track", fileId);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
getIsobmffProcessInfo,
|
|
4
|
+
getIsobmffProcessProgress,
|
|
5
|
+
} from "./process-isobmff.ts";
|
|
6
|
+
|
|
7
|
+
import { useMSW } from "TEST/useMSW.ts";
|
|
8
|
+
import { http, HttpResponse } from "msw";
|
|
9
|
+
import { Client } from "../client.ts";
|
|
10
|
+
|
|
11
|
+
const client = new Client("ef_TEST_TOKEN", "http://localhost");
|
|
12
|
+
|
|
13
|
+
describe("process-isobmff", () => {
|
|
14
|
+
const server = useMSW();
|
|
15
|
+
describe("getIsobmffProcessInfo", () => {
|
|
16
|
+
test("returns the process info", async () => {
|
|
17
|
+
server.use(
|
|
18
|
+
http.get("http://localhost/api/v1/process_isobmff/123", async () => {
|
|
19
|
+
return HttpResponse.json({
|
|
20
|
+
id: "123",
|
|
21
|
+
created_at: "2021-01-01T00:00:00.000Z",
|
|
22
|
+
updated_at: "2021-01-01T00:00:00.000Z",
|
|
23
|
+
});
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
const info = await getIsobmffProcessInfo(client, "123");
|
|
27
|
+
expect(info).toEqual({
|
|
28
|
+
id: "123",
|
|
29
|
+
created_at: "2021-01-01T00:00:00.000Z",
|
|
30
|
+
updated_at: "2021-01-01T00:00:00.000Z",
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("throws when the server returns an error", async () => {
|
|
35
|
+
server.use(
|
|
36
|
+
http.get("http://localhost/api/v1/process_isobmff/123", () =>
|
|
37
|
+
HttpResponse.text("Internal Server Error", { status: 500 }),
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
await expect(getIsobmffProcessInfo(client, "123")).rejects.toThrow(
|
|
41
|
+
"Failed to get isobmff process info 500 Internal Server Error",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("getIsobmffProcessProgress", () => {
|
|
47
|
+
test("returns the progress", async () => {
|
|
48
|
+
server.use(
|
|
49
|
+
http.get(
|
|
50
|
+
"http://localhost/api/v1/process_isobmff/123/progress",
|
|
51
|
+
async () => {
|
|
52
|
+
return HttpResponse.text("event: complete\ndata: {}\n\n");
|
|
53
|
+
},
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
const progress = await getIsobmffProcessProgress(client, "123");
|
|
57
|
+
await expect(progress.whenComplete()).resolves.toEqual([
|
|
58
|
+
{ type: "complete", data: {} },
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|