@editframe/cli 0.26.3-beta.0 → 0.30.0-beta.13
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/VERSION.js +1 -1
- package/dist/VERSION.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/commands/auth.ts +0 -46
- package/src/commands/check.ts +0 -129
- package/src/commands/mux.ts +0 -10
- package/src/commands/preview.ts +0 -9
- package/src/commands/process-file.ts +0 -55
- package/src/commands/process.ts +0 -41
- package/src/commands/render.ts +0 -190
- package/src/commands/sync.ts +0 -13
- package/src/commands/test-asset.file +0 -0
- package/src/commands/webhook.ts +0 -76
- package/src/operations/processRenderInfo.ts +0 -40
- package/src/operations/syncAssetsDirectory/SubAssetSync.ts +0 -45
- package/src/operations/syncAssetsDirectory/SyncCaption.test.ts +0 -180
- package/src/operations/syncAssetsDirectory/SyncCaption.ts +0 -87
- package/src/operations/syncAssetsDirectory/SyncFragmentIndex.test.ts +0 -185
- package/src/operations/syncAssetsDirectory/SyncFragmentIndex.ts +0 -101
- package/src/operations/syncAssetsDirectory/SyncImage.test.ts +0 -162
- package/src/operations/syncAssetsDirectory/SyncImage.ts +0 -123
- package/src/operations/syncAssetsDirectory/SyncStatus.ts +0 -50
- package/src/operations/syncAssetsDirectory/SyncTrack.test.ts +0 -265
- package/src/operations/syncAssetsDirectory/SyncTrack.ts +0 -175
- package/src/operations/syncAssetsDirectory/doAssetSync.test.ts +0 -134
- package/src/operations/syncAssetsDirectory/doAssetSync.ts +0 -62
- package/src/operations/syncAssetsDirectory.test.ts +0 -510
- package/src/operations/syncAssetsDirectory.ts +0 -91
- package/src/utils/attachWorkbench.ts +0 -16
- package/src/utils/createReadableStreamFromReadable.ts +0 -113
- package/src/utils/getFolderSize.ts +0 -20
- package/src/utils/index.ts +0 -20
- package/src/utils/launchBrowserAndWaitForSDK.ts +0 -64
- package/src/utils/startDevServer.ts +0 -61
- package/src/utils/startPreviewServer.ts +0 -38
- package/src/utils/validateVideoResolution.ts +0 -36
- package/src/utils/withSpinner.ts +0 -16
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import debug from "debug";
|
|
2
|
-
import { SyncCaption } from "./SyncCaption.js";
|
|
3
|
-
import { SyncFragmentIndex } from "./SyncFragmentIndex.js";
|
|
4
|
-
import { SyncImage } from "./SyncImage.js";
|
|
5
|
-
import type { SyncStatus } from "./SyncStatus.js";
|
|
6
|
-
import { SyncTrack } from "./SyncTrack.js";
|
|
7
|
-
|
|
8
|
-
export interface SubAssetSync<CreationType> {
|
|
9
|
-
icon: string;
|
|
10
|
-
label: string;
|
|
11
|
-
path: string;
|
|
12
|
-
md5: string;
|
|
13
|
-
prepare: () => Promise<void>;
|
|
14
|
-
validate: () => Promise<void>;
|
|
15
|
-
create: () => Promise<void>;
|
|
16
|
-
upload: () => Promise<void>;
|
|
17
|
-
syncStatus: SyncStatus;
|
|
18
|
-
isComplete: () => boolean;
|
|
19
|
-
markSynced: () => Promise<void>;
|
|
20
|
-
created: CreationType | null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const trackMatch = /\.track-[\d]+.mp4$/i;
|
|
24
|
-
const fragmentIndexMatch = /\.tracks.json$/i;
|
|
25
|
-
const captionsMatch = /\.captions.json$/i;
|
|
26
|
-
const imageMatch = /\.(png|jpe?g|gif|webp)$/i;
|
|
27
|
-
|
|
28
|
-
const log = debug("ef:SubAssetSync");
|
|
29
|
-
|
|
30
|
-
export const getAssetSync = (subAssetPath: string, md5: string) => {
|
|
31
|
-
log("getAssetSync", { subAssetPath, md5 });
|
|
32
|
-
if (imageMatch.test(subAssetPath)) {
|
|
33
|
-
return new SyncImage(subAssetPath, md5);
|
|
34
|
-
}
|
|
35
|
-
if (trackMatch.test(subAssetPath)) {
|
|
36
|
-
return new SyncTrack(subAssetPath, md5);
|
|
37
|
-
}
|
|
38
|
-
if (fragmentIndexMatch.test(subAssetPath)) {
|
|
39
|
-
return new SyncFragmentIndex(subAssetPath, md5);
|
|
40
|
-
}
|
|
41
|
-
if (captionsMatch.test(subAssetPath)) {
|
|
42
|
-
return new SyncCaption(subAssetPath, md5);
|
|
43
|
-
}
|
|
44
|
-
throw new Error(`Unrecognized sub-asset type: ${subAssetPath}`);
|
|
45
|
-
};
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { setupServer } from "msw/node";
|
|
2
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
3
|
-
import { fixture, withFixtures } from "../../../test-fixtures/fixture.js";
|
|
4
|
-
import {
|
|
5
|
-
mockCreateCaptionFile,
|
|
6
|
-
mockLookupCaptionFileByMd5,
|
|
7
|
-
mockLookupCaptionFileByMd5NotFound,
|
|
8
|
-
mockUploadCaptionFile,
|
|
9
|
-
} from "../../../test-fixtures/network.js";
|
|
10
|
-
import { SyncCaption } from "./SyncCaption.js";
|
|
11
|
-
|
|
12
|
-
const server = setupServer();
|
|
13
|
-
|
|
14
|
-
describe("SyncCaption", async () => {
|
|
15
|
-
beforeAll(() => {
|
|
16
|
-
server.listen();
|
|
17
|
-
process.env.EF_TOKEN = "ef_SECRET_TOKEN";
|
|
18
|
-
process.env.EF_HOST = "http://localhost:3000";
|
|
19
|
-
});
|
|
20
|
-
afterAll(() => server.close());
|
|
21
|
-
afterEach(() => server.resetHandlers());
|
|
22
|
-
await withFixtures(
|
|
23
|
-
[fixture("test.mp4", "test.mp4")],
|
|
24
|
-
async ({ files: [video], generateCaptions }) => {
|
|
25
|
-
test("Reads byte size", async () => {
|
|
26
|
-
const syncCaption = new SyncCaption(
|
|
27
|
-
await generateCaptions(video!),
|
|
28
|
-
video!.md5,
|
|
29
|
-
);
|
|
30
|
-
await expect(syncCaption.byteSize()).resolves.toEqual(35);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("prepare() is noop", async () => {
|
|
34
|
-
const syncCaption = new SyncCaption(
|
|
35
|
-
await generateCaptions(video!),
|
|
36
|
-
video!.md5,
|
|
37
|
-
);
|
|
38
|
-
await expect(syncCaption.prepare()).resolves.toBeUndefined();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("validate() is noop", async () => {
|
|
42
|
-
const syncCaption = new SyncCaption(
|
|
43
|
-
await generateCaptions(video!),
|
|
44
|
-
video!.md5,
|
|
45
|
-
);
|
|
46
|
-
await expect(syncCaption.validate()).resolves.toBeUndefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe(".create()", () => {
|
|
50
|
-
test("uses matching data when file matches md5", async () => {
|
|
51
|
-
server.use(
|
|
52
|
-
mockLookupCaptionFileByMd5({
|
|
53
|
-
md5: video!.md5,
|
|
54
|
-
fixture: video!,
|
|
55
|
-
}),
|
|
56
|
-
);
|
|
57
|
-
const syncCaption = new SyncCaption(
|
|
58
|
-
await generateCaptions(video!),
|
|
59
|
-
video!.md5,
|
|
60
|
-
);
|
|
61
|
-
await syncCaption.create();
|
|
62
|
-
expect(syncCaption.isComplete()).toBe(true);
|
|
63
|
-
});
|
|
64
|
-
test("isComplete() returns false when not created", async () => {
|
|
65
|
-
server.use(
|
|
66
|
-
mockLookupCaptionFileByMd5NotFound({
|
|
67
|
-
md5: video!.md5,
|
|
68
|
-
}),
|
|
69
|
-
mockCreateCaptionFile({
|
|
70
|
-
complete: false,
|
|
71
|
-
id: "123",
|
|
72
|
-
filename: "test.mp4",
|
|
73
|
-
fixture: video!,
|
|
74
|
-
}),
|
|
75
|
-
);
|
|
76
|
-
const syncCaption = new SyncCaption(
|
|
77
|
-
await generateCaptions(video!),
|
|
78
|
-
video!.md5,
|
|
79
|
-
);
|
|
80
|
-
await syncCaption.create();
|
|
81
|
-
expect(syncCaption.isComplete()).toBe(false);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("isComplete() returns true when created", async () => {
|
|
85
|
-
server.use(
|
|
86
|
-
mockLookupCaptionFileByMd5NotFound({
|
|
87
|
-
md5: video!.md5,
|
|
88
|
-
}),
|
|
89
|
-
mockCreateCaptionFile({
|
|
90
|
-
complete: true,
|
|
91
|
-
id: "123",
|
|
92
|
-
filename: "test.mp4",
|
|
93
|
-
fixture: video!,
|
|
94
|
-
}),
|
|
95
|
-
);
|
|
96
|
-
const syncCaption = new SyncCaption(
|
|
97
|
-
await generateCaptions(video!),
|
|
98
|
-
video!.md5,
|
|
99
|
-
);
|
|
100
|
-
await syncCaption.create();
|
|
101
|
-
expect(syncCaption.isComplete()).toBe(true);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe(".upload()", () => {
|
|
106
|
-
test("throws when not created", async () => {
|
|
107
|
-
const syncCaption = new SyncCaption(
|
|
108
|
-
await generateCaptions(video!),
|
|
109
|
-
video!.md5,
|
|
110
|
-
);
|
|
111
|
-
await expect(syncCaption.upload()).rejects.toThrow();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("uploads caption", async () => {
|
|
115
|
-
server.use(
|
|
116
|
-
mockLookupCaptionFileByMd5NotFound({
|
|
117
|
-
md5: video!.md5,
|
|
118
|
-
}),
|
|
119
|
-
mockCreateCaptionFile({
|
|
120
|
-
complete: true,
|
|
121
|
-
id: "123",
|
|
122
|
-
filename: "test.mp4",
|
|
123
|
-
fixture: video!,
|
|
124
|
-
}),
|
|
125
|
-
mockUploadCaptionFile({
|
|
126
|
-
id: "123",
|
|
127
|
-
filename: "test.mp4",
|
|
128
|
-
fixture: video!,
|
|
129
|
-
}),
|
|
130
|
-
);
|
|
131
|
-
const syncCaption = new SyncCaption(
|
|
132
|
-
await generateCaptions(video!),
|
|
133
|
-
video!.md5,
|
|
134
|
-
);
|
|
135
|
-
await syncCaption.create();
|
|
136
|
-
await expect(syncCaption.upload()).resolves.toBeUndefined();
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe(".markSynced()", () => {
|
|
141
|
-
test("throws when not created", async () => {
|
|
142
|
-
const syncCaption = new SyncCaption(
|
|
143
|
-
await generateCaptions(video!),
|
|
144
|
-
video!.md5,
|
|
145
|
-
);
|
|
146
|
-
await expect(syncCaption.markSynced()).rejects.toThrow();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("marks synced", async () => {
|
|
150
|
-
server.use(
|
|
151
|
-
mockLookupCaptionFileByMd5NotFound({
|
|
152
|
-
md5: video!.md5,
|
|
153
|
-
}),
|
|
154
|
-
mockCreateCaptionFile({
|
|
155
|
-
complete: true,
|
|
156
|
-
id: "123",
|
|
157
|
-
filename: "test.mp4",
|
|
158
|
-
fixture: video!,
|
|
159
|
-
}),
|
|
160
|
-
);
|
|
161
|
-
const syncCaption = new SyncCaption(
|
|
162
|
-
await generateCaptions(video!),
|
|
163
|
-
video!.md5,
|
|
164
|
-
);
|
|
165
|
-
await syncCaption.create();
|
|
166
|
-
await syncCaption.markSynced();
|
|
167
|
-
|
|
168
|
-
await expect(syncCaption.syncStatus.isSynced()).resolves.toBe(true);
|
|
169
|
-
await expect(syncCaption.syncStatus.readInfo()).resolves.toEqual({
|
|
170
|
-
version: "1",
|
|
171
|
-
complete: true,
|
|
172
|
-
id: "123",
|
|
173
|
-
md5: video!.md5,
|
|
174
|
-
byte_size: 35,
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
},
|
|
179
|
-
);
|
|
180
|
-
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import { basename } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { Readable } from "node:stream";
|
|
5
|
-
import {
|
|
6
|
-
type CreateCaptionFileResult,
|
|
7
|
-
createCaptionFile,
|
|
8
|
-
type LookupCaptionFileByMd5Result,
|
|
9
|
-
lookupCaptionFileByMd5,
|
|
10
|
-
uploadCaptionFile,
|
|
11
|
-
} from "@editframe/api";
|
|
12
|
-
import { createReadableStreamFromReadable } from "../../utils/createReadableStreamFromReadable.js";
|
|
13
|
-
import { getClient } from "../../utils/index.js";
|
|
14
|
-
import type { SubAssetSync } from "./SubAssetSync.js";
|
|
15
|
-
import { SyncStatus } from "./SyncStatus.js";
|
|
16
|
-
export class SyncCaption implements SubAssetSync<CreateCaptionFileResult> {
|
|
17
|
-
icon = "📝";
|
|
18
|
-
label = "captions";
|
|
19
|
-
syncStatus: SyncStatus = new SyncStatus(this.path);
|
|
20
|
-
created: CreateCaptionFileResult | LookupCaptionFileByMd5Result | null = null;
|
|
21
|
-
constructor(
|
|
22
|
-
public path: string,
|
|
23
|
-
public md5: string,
|
|
24
|
-
) {}
|
|
25
|
-
|
|
26
|
-
async byteSize() {
|
|
27
|
-
return (await fs.stat(this.path)).size;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async prepare() {}
|
|
31
|
-
|
|
32
|
-
async validate() {}
|
|
33
|
-
|
|
34
|
-
async create() {
|
|
35
|
-
const maybeCaptionFile = await lookupCaptionFileByMd5(
|
|
36
|
-
getClient(),
|
|
37
|
-
this.md5,
|
|
38
|
-
);
|
|
39
|
-
if (maybeCaptionFile) {
|
|
40
|
-
this.created = maybeCaptionFile;
|
|
41
|
-
} else {
|
|
42
|
-
this.created = await createCaptionFile(getClient(), {
|
|
43
|
-
md5: this.md5,
|
|
44
|
-
filename: basename(this.path).replace(/\.captions.json$/, ""),
|
|
45
|
-
byte_size: await this.byteSize(),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
isComplete() {
|
|
51
|
-
return !!this.created?.complete;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async upload() {
|
|
55
|
-
if (!this.created) {
|
|
56
|
-
throw new Error(
|
|
57
|
-
"Caption not created. Should have been prevented by .isComplete()",
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
await uploadCaptionFile(
|
|
61
|
-
getClient(),
|
|
62
|
-
this.created.id,
|
|
63
|
-
// It's not clear why we need to use Readable.from here, but it seems
|
|
64
|
-
// to fix an issue where the request is closed early in tests
|
|
65
|
-
createReadableStreamFromReadable(
|
|
66
|
-
Readable.from(await fs.readFile(this.path)),
|
|
67
|
-
),
|
|
68
|
-
await this.byteSize(),
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async markSynced() {
|
|
73
|
-
if (!this.created) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
"Caption not created. Should have been prevented by .isComplete()",
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
const byteSize = await this.byteSize();
|
|
79
|
-
await this.syncStatus.markSynced({
|
|
80
|
-
version: "1",
|
|
81
|
-
complete: true,
|
|
82
|
-
id: this.created.id,
|
|
83
|
-
md5: this.md5,
|
|
84
|
-
byte_size: byteSize,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { setupServer } from "msw/node";
|
|
2
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
3
|
-
import { fixture, withFixtures } from "../../../test-fixtures/fixture.js";
|
|
4
|
-
import {
|
|
5
|
-
mockCreateIsobmffFile,
|
|
6
|
-
mockLookupISOBMFFFileByMd5,
|
|
7
|
-
mockLookupISOBMFFFileByMd5NotFound,
|
|
8
|
-
mockUploadIsobmffFileIndex,
|
|
9
|
-
} from "../../../test-fixtures/network.js";
|
|
10
|
-
import { SyncFragmentIndex } from "./SyncFragmentIndex.js";
|
|
11
|
-
|
|
12
|
-
const server = setupServer();
|
|
13
|
-
|
|
14
|
-
describe("SyncFragmentIndex", async () => {
|
|
15
|
-
beforeAll(() => {
|
|
16
|
-
server.listen();
|
|
17
|
-
process.env.EF_TOKEN = "ef_SECRET_TOKEN";
|
|
18
|
-
process.env.EF_HOST = "http://localhost:3000";
|
|
19
|
-
});
|
|
20
|
-
afterAll(() => server.close());
|
|
21
|
-
afterEach(() => server.resetHandlers());
|
|
22
|
-
await withFixtures(
|
|
23
|
-
[fixture("test.mp4", "test.mp4")],
|
|
24
|
-
async ({ files: [video], generateTrackFragmentIndex }) => {
|
|
25
|
-
test("Reads byte size", async () => {
|
|
26
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
27
|
-
await generateTrackFragmentIndex(video!),
|
|
28
|
-
video!.md5,
|
|
29
|
-
);
|
|
30
|
-
await expect(syncFragmentIndex.byteSize()).resolves.toEqual(31);
|
|
31
|
-
});
|
|
32
|
-
test("prepare() is noop", async () => {
|
|
33
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
34
|
-
await generateTrackFragmentIndex(video!),
|
|
35
|
-
video!.md5,
|
|
36
|
-
);
|
|
37
|
-
await expect(syncFragmentIndex.prepare()).resolves.toBeUndefined();
|
|
38
|
-
});
|
|
39
|
-
test("validate() is noop", async () => {
|
|
40
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
41
|
-
await generateTrackFragmentIndex(video!),
|
|
42
|
-
video!.md5,
|
|
43
|
-
);
|
|
44
|
-
await expect(syncFragmentIndex.validate()).resolves.toBeUndefined();
|
|
45
|
-
});
|
|
46
|
-
describe(".create()", () => {
|
|
47
|
-
test("uses matching data when file matches md5", async () => {
|
|
48
|
-
server.use(
|
|
49
|
-
mockLookupISOBMFFFileByMd5({
|
|
50
|
-
md5: video!.md5,
|
|
51
|
-
fixture: video!,
|
|
52
|
-
}),
|
|
53
|
-
);
|
|
54
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
55
|
-
await generateTrackFragmentIndex(video!),
|
|
56
|
-
video!.md5,
|
|
57
|
-
);
|
|
58
|
-
await syncFragmentIndex.create();
|
|
59
|
-
expect(syncFragmentIndex.isComplete()).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
test("isComplete() returns false when not created", async () => {
|
|
62
|
-
server.use(
|
|
63
|
-
mockLookupISOBMFFFileByMd5NotFound({
|
|
64
|
-
md5: video!.md5,
|
|
65
|
-
}),
|
|
66
|
-
mockCreateIsobmffFile({
|
|
67
|
-
complete: false,
|
|
68
|
-
id: "123",
|
|
69
|
-
filename: "test.mp4",
|
|
70
|
-
fixture: video!,
|
|
71
|
-
}),
|
|
72
|
-
);
|
|
73
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
74
|
-
await generateTrackFragmentIndex(video!),
|
|
75
|
-
video!.md5,
|
|
76
|
-
);
|
|
77
|
-
await syncFragmentIndex.create();
|
|
78
|
-
expect(syncFragmentIndex.isComplete()).toBe(false);
|
|
79
|
-
});
|
|
80
|
-
test("isComplete() returns true when created", async () => {
|
|
81
|
-
server.use(
|
|
82
|
-
mockLookupISOBMFFFileByMd5NotFound({
|
|
83
|
-
md5: video!.md5,
|
|
84
|
-
}),
|
|
85
|
-
mockCreateIsobmffFile({
|
|
86
|
-
complete: true,
|
|
87
|
-
id: "123",
|
|
88
|
-
filename: "test.mp4",
|
|
89
|
-
fixture: video!,
|
|
90
|
-
}),
|
|
91
|
-
);
|
|
92
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
93
|
-
await generateTrackFragmentIndex(video!),
|
|
94
|
-
video!.md5,
|
|
95
|
-
);
|
|
96
|
-
await syncFragmentIndex.create();
|
|
97
|
-
expect(syncFragmentIndex.isComplete()).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
describe(".upload()", () => {
|
|
101
|
-
test("throws when not created", async () => {
|
|
102
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
103
|
-
await generateTrackFragmentIndex(video!),
|
|
104
|
-
video!.md5,
|
|
105
|
-
);
|
|
106
|
-
await expect(syncFragmentIndex.upload()).rejects.toThrow();
|
|
107
|
-
});
|
|
108
|
-
test("uploads index", async () => {
|
|
109
|
-
server.use(
|
|
110
|
-
mockLookupISOBMFFFileByMd5NotFound({
|
|
111
|
-
md5: video!.md5,
|
|
112
|
-
}),
|
|
113
|
-
mockCreateIsobmffFile({
|
|
114
|
-
complete: true,
|
|
115
|
-
id: "123",
|
|
116
|
-
filename: "test.mp4",
|
|
117
|
-
fixture: video!,
|
|
118
|
-
}),
|
|
119
|
-
mockUploadIsobmffFileIndex({
|
|
120
|
-
id: "123",
|
|
121
|
-
}),
|
|
122
|
-
);
|
|
123
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
124
|
-
await generateTrackFragmentIndex(video!),
|
|
125
|
-
video!.md5,
|
|
126
|
-
);
|
|
127
|
-
await syncFragmentIndex.create();
|
|
128
|
-
await expect(syncFragmentIndex.upload()).resolves.toBeUndefined();
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
describe(".markSynced()", () => {
|
|
132
|
-
test("throws when not created", async () => {
|
|
133
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
134
|
-
await generateTrackFragmentIndex(video!),
|
|
135
|
-
video!.md5,
|
|
136
|
-
);
|
|
137
|
-
await expect(syncFragmentIndex.markSynced()).rejects.toThrow();
|
|
138
|
-
});
|
|
139
|
-
test("marks synced", async () => {
|
|
140
|
-
server.use(
|
|
141
|
-
mockLookupISOBMFFFileByMd5NotFound({
|
|
142
|
-
md5: video!.md5,
|
|
143
|
-
}),
|
|
144
|
-
mockCreateIsobmffFile({
|
|
145
|
-
complete: true,
|
|
146
|
-
id: "123",
|
|
147
|
-
fixture: video!,
|
|
148
|
-
}),
|
|
149
|
-
mockUploadIsobmffFileIndex({
|
|
150
|
-
id: "123",
|
|
151
|
-
}),
|
|
152
|
-
);
|
|
153
|
-
const syncFragmentIndex = new SyncFragmentIndex(
|
|
154
|
-
await generateTrackFragmentIndex(video!),
|
|
155
|
-
video!.md5,
|
|
156
|
-
);
|
|
157
|
-
await syncFragmentIndex.create();
|
|
158
|
-
await syncFragmentIndex.markSynced();
|
|
159
|
-
await expect(syncFragmentIndex.syncStatus.isSynced()).resolves.toBe(
|
|
160
|
-
true,
|
|
161
|
-
);
|
|
162
|
-
await expect(
|
|
163
|
-
syncFragmentIndex.syncStatus.readInfo(),
|
|
164
|
-
).resolves.toEqual({
|
|
165
|
-
version: "1",
|
|
166
|
-
complete: true,
|
|
167
|
-
id: "123",
|
|
168
|
-
md5: video!.md5,
|
|
169
|
-
byte_size: 31,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
await expect(
|
|
173
|
-
syncFragmentIndex.fileSyncStatus.readInfo(),
|
|
174
|
-
).resolves.toEqual({
|
|
175
|
-
version: "1",
|
|
176
|
-
complete: true,
|
|
177
|
-
id: "123",
|
|
178
|
-
md5: video!.md5,
|
|
179
|
-
byte_size: 31,
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
},
|
|
184
|
-
);
|
|
185
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
3
|
-
import { Readable } from "node:stream";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
type CreateISOBMFFFileResult,
|
|
7
|
-
createISOBMFFFile,
|
|
8
|
-
type LookupISOBMFFFileByMd5Result,
|
|
9
|
-
lookupISOBMFFFileByMd5,
|
|
10
|
-
uploadFragmentIndex,
|
|
11
|
-
} from "@editframe/api";
|
|
12
|
-
|
|
13
|
-
import { createReadableStreamFromReadable } from "../../utils/createReadableStreamFromReadable.js";
|
|
14
|
-
import { getClient } from "../../utils/index.js";
|
|
15
|
-
import type { SubAssetSync } from "./SubAssetSync.js";
|
|
16
|
-
import { SyncStatus } from "./SyncStatus.js";
|
|
17
|
-
|
|
18
|
-
export class SyncFragmentIndex
|
|
19
|
-
implements SubAssetSync<CreateISOBMFFFileResult>
|
|
20
|
-
{
|
|
21
|
-
icon = "📋";
|
|
22
|
-
label = "fragment index";
|
|
23
|
-
syncStatus = new SyncStatus(this.path);
|
|
24
|
-
fileSyncStatus = new SyncStatus(join(dirname(this.path), "isobmff"));
|
|
25
|
-
created: CreateISOBMFFFileResult | LookupISOBMFFFileByMd5Result | null = null;
|
|
26
|
-
|
|
27
|
-
constructor(
|
|
28
|
-
public path: string,
|
|
29
|
-
public md5: string,
|
|
30
|
-
) {}
|
|
31
|
-
|
|
32
|
-
async byteSize() {
|
|
33
|
-
return (await fs.stat(this.path)).size;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async prepare() {}
|
|
37
|
-
|
|
38
|
-
async validate() {}
|
|
39
|
-
|
|
40
|
-
async create() {
|
|
41
|
-
const maybeISOBMFFFile = await lookupISOBMFFFileByMd5(
|
|
42
|
-
getClient(),
|
|
43
|
-
this.md5,
|
|
44
|
-
);
|
|
45
|
-
if (maybeISOBMFFFile) {
|
|
46
|
-
this.created = maybeISOBMFFFile;
|
|
47
|
-
} else {
|
|
48
|
-
this.created = await createISOBMFFFile(getClient(), {
|
|
49
|
-
md5: this.md5,
|
|
50
|
-
filename: basename(this.path).replace(/\.tracks.json$/, ""),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
isComplete() {
|
|
56
|
-
return !!this.created?.fragment_index_complete;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async upload() {
|
|
60
|
-
if (!this.created) {
|
|
61
|
-
throw new Error(
|
|
62
|
-
"Fragment index not created. Should have been prevented by .isComplete()",
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
await uploadFragmentIndex(
|
|
66
|
-
getClient(),
|
|
67
|
-
this.created.id,
|
|
68
|
-
// It is unclear why we need to use Readable.from here
|
|
69
|
-
// Tests fail when using createReadStream
|
|
70
|
-
createReadableStreamFromReadable(
|
|
71
|
-
Readable.from(await fs.readFile(this.path)),
|
|
72
|
-
),
|
|
73
|
-
await this.byteSize(),
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async markSynced() {
|
|
78
|
-
if (!this.created) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
"Fragment index not created. Should have been prevented by .isComplete()",
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
const byteSize = await this.byteSize();
|
|
84
|
-
await Promise.all([
|
|
85
|
-
this.syncStatus.markSynced({
|
|
86
|
-
version: "1",
|
|
87
|
-
complete: true,
|
|
88
|
-
id: this.created.id,
|
|
89
|
-
md5: this.md5,
|
|
90
|
-
byte_size: byteSize,
|
|
91
|
-
}),
|
|
92
|
-
this.fileSyncStatus.markSynced({
|
|
93
|
-
version: "1",
|
|
94
|
-
complete: true,
|
|
95
|
-
id: this.created.id,
|
|
96
|
-
md5: this.md5,
|
|
97
|
-
byte_size: byteSize,
|
|
98
|
-
}),
|
|
99
|
-
]);
|
|
100
|
-
}
|
|
101
|
-
}
|