@editframe/cli 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/VERSION.d.ts +1 -1
- package/dist/VERSION.js +1 -1
- package/dist/commands/process-file.js +27 -30
- package/dist/commands/render.js +8 -2
- package/dist/operations/getRenderInfo.d.ts +2 -2
- package/dist/operations/syncAssetsDirectory/SubAssetSync.js +3 -0
- package/dist/operations/syncAssetsDirectory/SyncCaption.d.ts +2 -2
- package/dist/operations/syncAssetsDirectory/SyncCaption.js +19 -8
- package/dist/operations/syncAssetsDirectory/SyncFragmentIndex.d.ts +2 -2
- package/dist/operations/syncAssetsDirectory/SyncFragmentIndex.js +19 -12
- package/dist/operations/syncAssetsDirectory/SyncImage.d.ts +2 -2
- package/dist/operations/syncAssetsDirectory/SyncImage.js +20 -13
- package/dist/operations/syncAssetsDirectory/SyncStatus.d.ts +7 -11
- package/dist/operations/syncAssetsDirectory/SyncStatus.js +1 -1
- package/dist/operations/syncAssetsDirectory/SyncTrack.d.ts +4 -4
- package/dist/operations/syncAssetsDirectory/SyncTrack.js +14 -12
- package/dist/utils/createReadableStreamFromReadable.d.ts +4 -0
- package/dist/utils/createReadableStreamFromReadable.js +82 -0
- package/dist/utils/launchBrowserAndWaitForSDK.js +2 -2
- package/dist/utils/validateVideoResolution.d.ts +1 -1
- package/package.json +5 -5
- package/src/commands/process-file.ts +33 -37
- package/src/commands/render.ts +9 -2
- package/src/operations/syncAssetsDirectory/SubAssetSync.ts +4 -0
- package/src/operations/syncAssetsDirectory/SyncCaption.test.ts +29 -3
- package/src/operations/syncAssetsDirectory/SyncCaption.ts +22 -9
- package/src/operations/syncAssetsDirectory/SyncFragmentIndex.test.ts +30 -4
- package/src/operations/syncAssetsDirectory/SyncFragmentIndex.ts +22 -13
- package/src/operations/syncAssetsDirectory/SyncImage.test.ts +27 -3
- package/src/operations/syncAssetsDirectory/SyncImage.ts +27 -17
- package/src/operations/syncAssetsDirectory/SyncStatus.ts +3 -3
- package/src/operations/syncAssetsDirectory/SyncTrack.test.ts +38 -3
- package/src/operations/syncAssetsDirectory/SyncTrack.ts +20 -13
- package/src/operations/syncAssetsDirectory.test.ts +22 -1
- package/src/utils/createReadableStreamFromReadable.ts +117 -0
- package/src/utils/launchBrowserAndWaitForSDK.ts +2 -2
- package/src/utils/startDevServer.ts +1 -1
- package/test-fixtures/fixture.ts +0 -1
- package/test-fixtures/network.ts +72 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0-beta.10",
|
|
4
4
|
"description": "Command line interface for EditFrame",
|
|
5
5
|
"bin": {
|
|
6
6
|
"editframe": "./dist/index.js"
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"vite-tsconfig-paths": "^4.3.2"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@editframe/api": "0.
|
|
27
|
-
"@editframe/assets": "0.
|
|
28
|
-
"@editframe/elements": "0.
|
|
29
|
-
"@editframe/vite-plugin": "0.
|
|
26
|
+
"@editframe/api": "0.12.0-beta.10",
|
|
27
|
+
"@editframe/assets": "0.12.0-beta.10",
|
|
28
|
+
"@editframe/elements": "0.12.0-beta.10",
|
|
29
|
+
"@editframe/vite-plugin": "0.12.0-beta.10",
|
|
30
30
|
"@inquirer/prompts": "^5.3.8",
|
|
31
31
|
"axios": "^1.6.8",
|
|
32
32
|
"chalk": "^5.3.0",
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { stat } from "node:fs/promises";
|
|
3
|
-
import { createReadStream } from "node:fs";
|
|
4
|
-
|
|
5
1
|
import { program } from "commander";
|
|
6
2
|
|
|
7
3
|
import { withSpinner } from "../utils/withSpinner.ts";
|
|
8
4
|
|
|
9
|
-
import { getClient } from "../utils/index.ts";
|
|
10
5
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
createUnprocessedFileFromPath,
|
|
7
|
+
getIsobmffProcessInfo,
|
|
8
|
+
getIsobmffProcessProgress,
|
|
9
|
+
processIsobmffFile,
|
|
13
10
|
uploadUnprocessedFile,
|
|
14
11
|
} from "@editframe/api";
|
|
15
|
-
import
|
|
12
|
+
import ora from "ora";
|
|
13
|
+
import { getClient } from "../utils/index.ts";
|
|
16
14
|
|
|
17
15
|
program
|
|
18
16
|
.command("process-file <file>")
|
|
@@ -20,42 +18,40 @@ program
|
|
|
20
18
|
.action(async (path: string) => {
|
|
21
19
|
const client = getClient();
|
|
22
20
|
|
|
23
|
-
const md5 = await md5FilePath(path);
|
|
24
|
-
|
|
25
|
-
const byte_size = (await stat(path)).size;
|
|
26
|
-
|
|
27
21
|
const unprocessedFile = await withSpinner(
|
|
28
22
|
"Creating unprocessed file record",
|
|
29
|
-
|
|
30
|
-
await createUnprocessedFile(client, {
|
|
31
|
-
md5,
|
|
32
|
-
processes: [],
|
|
33
|
-
filename: basename(path),
|
|
34
|
-
byte_size,
|
|
35
|
-
}),
|
|
23
|
+
() => createUnprocessedFileFromPath(client, path),
|
|
36
24
|
);
|
|
37
25
|
|
|
38
|
-
const
|
|
26
|
+
const upload = await uploadUnprocessedFile(client, unprocessedFile, path);
|
|
27
|
+
const uploadSpinner = ora("Uploading file");
|
|
39
28
|
|
|
40
|
-
await
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
byte_size,
|
|
46
|
-
);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const updatedUnprocessedFile = await withSpinner(
|
|
29
|
+
for await (const event of upload) {
|
|
30
|
+
uploadSpinner.text = `Uploading file: ${(100 * event.progress).toFixed(2)}%`;
|
|
31
|
+
}
|
|
32
|
+
uploadSpinner.succeed("Upload complete");
|
|
33
|
+
const processorRecord = await withSpinner(
|
|
50
34
|
"Marking for processing",
|
|
51
|
-
async () =>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
35
|
+
async () => await processIsobmffFile(client, unprocessedFile.id),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const processSpinner = ora("Waiting for processing to complete");
|
|
39
|
+
processSpinner.start();
|
|
40
|
+
const progress = await getIsobmffProcessProgress(
|
|
41
|
+
client,
|
|
42
|
+
processorRecord.id,
|
|
56
43
|
);
|
|
57
44
|
|
|
58
|
-
|
|
45
|
+
for await (const event of progress) {
|
|
46
|
+
if (event.type === "progress") {
|
|
47
|
+
processSpinner.text = `Processing: ${(100 * event.data.progress).toFixed(2)}%`;
|
|
48
|
+
} else if (event.type === "complete") {
|
|
49
|
+
processSpinner.succeed("Processing complete");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const info = await getIsobmffProcessInfo(client, processorRecord.id);
|
|
59
54
|
|
|
60
|
-
console.log(
|
|
55
|
+
console.log("Processed file info");
|
|
56
|
+
console.log(info);
|
|
61
57
|
});
|
package/src/commands/render.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { RenderInfo, getRenderInfo } from "../operations/getRenderInfo.ts";
|
|
|
16
16
|
import { processRenderInfo } from "../operations/processRenderInfo.ts";
|
|
17
17
|
import { syncAssetDirectory } from "../operations/syncAssetsDirectory.ts";
|
|
18
18
|
import { SyncStatus } from "../operations/syncAssetsDirectory/SyncStatus.ts";
|
|
19
|
+
import { createReadableStreamFromReadable } from "../utils/createReadableStreamFromReadable.ts";
|
|
19
20
|
import { getFolderSize } from "../utils/getFolderSize.ts";
|
|
20
21
|
import { getClient } from "../utils/index.ts";
|
|
21
22
|
import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK.ts";
|
|
@@ -41,7 +42,7 @@ export const buildAssetId = async (
|
|
|
41
42
|
throw new Error(`SyncStatus info is not found for ${syncStatus.infoPath}`);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
return info.
|
|
45
|
+
return info.id;
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
program
|
|
@@ -179,8 +180,14 @@ program
|
|
|
179
180
|
);
|
|
180
181
|
const readable = new PassThrough();
|
|
181
182
|
tarStream.pipe(readable);
|
|
183
|
+
|
|
182
184
|
const folderSize = await getFolderSize(distDir);
|
|
183
|
-
await uploadRender(
|
|
185
|
+
await uploadRender(
|
|
186
|
+
getClient(),
|
|
187
|
+
render.id,
|
|
188
|
+
createReadableStreamFromReadable(readable),
|
|
189
|
+
folderSize,
|
|
190
|
+
);
|
|
184
191
|
process.stderr.write("Render assets uploaded\n");
|
|
185
192
|
process.stderr.write(inspect(render));
|
|
186
193
|
process.stderr.write("\n");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import debug from "debug";
|
|
1
2
|
import type { SyncStatus } from "./SyncStatus.ts";
|
|
2
3
|
|
|
3
4
|
import { SyncCaption } from "./SyncCaption.ts";
|
|
@@ -25,7 +26,10 @@ const fragmentIndexMatch = /\.tracks.json$/i;
|
|
|
25
26
|
const captionsMatch = /\.captions.json$/i;
|
|
26
27
|
const imageMatch = /\.(png|jpe?g|gif|webp)$/i;
|
|
27
28
|
|
|
29
|
+
const log = debug("ef:SubAssetSync");
|
|
30
|
+
|
|
28
31
|
export const getAssetSync = (subAssetPath: string, md5: string) => {
|
|
32
|
+
log("getAssetSync", { subAssetPath, md5 });
|
|
29
33
|
if (imageMatch.test(subAssetPath)) {
|
|
30
34
|
return new SyncImage(subAssetPath, md5);
|
|
31
35
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { useMSW } from "TEST/useMSW.ts";
|
|
1
2
|
import { describe, expect, test } from "vitest";
|
|
2
3
|
import { fixture, withFixtures } from "../../../test-fixtures/fixture.ts";
|
|
3
4
|
import {
|
|
4
5
|
mockCreateCaptionFile,
|
|
6
|
+
mockLookupCaptionFileByMd5,
|
|
7
|
+
mockLookupCaptionFileByMd5NotFound,
|
|
5
8
|
mockUploadCaptionFile,
|
|
6
|
-
useMSW,
|
|
7
9
|
} from "../../../test-fixtures/network.ts";
|
|
8
10
|
import { SyncCaption } from "./SyncCaption.ts";
|
|
9
|
-
|
|
10
11
|
describe("SyncCaption", async () => {
|
|
11
12
|
const server = useMSW();
|
|
12
13
|
await withFixtures(
|
|
@@ -37,8 +38,25 @@ describe("SyncCaption", async () => {
|
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
describe(".create()", () => {
|
|
41
|
+
test("uses matching data when file matches md5", async () => {
|
|
42
|
+
server.use(
|
|
43
|
+
mockLookupCaptionFileByMd5({
|
|
44
|
+
md5: video!.md5,
|
|
45
|
+
fixture: video!,
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
const syncCaption = new SyncCaption(
|
|
49
|
+
await generateCaptions(video!),
|
|
50
|
+
video!.md5,
|
|
51
|
+
);
|
|
52
|
+
await syncCaption.create();
|
|
53
|
+
expect(syncCaption.isComplete()).toBe(true);
|
|
54
|
+
});
|
|
40
55
|
test("isComplete() returns false when not created", async () => {
|
|
41
56
|
server.use(
|
|
57
|
+
mockLookupCaptionFileByMd5NotFound({
|
|
58
|
+
md5: video!.md5,
|
|
59
|
+
}),
|
|
42
60
|
mockCreateCaptionFile({
|
|
43
61
|
complete: false,
|
|
44
62
|
id: "123",
|
|
@@ -56,6 +74,9 @@ describe("SyncCaption", async () => {
|
|
|
56
74
|
|
|
57
75
|
test("isComplete() returns true when created", async () => {
|
|
58
76
|
server.use(
|
|
77
|
+
mockLookupCaptionFileByMd5NotFound({
|
|
78
|
+
md5: video!.md5,
|
|
79
|
+
}),
|
|
59
80
|
mockCreateCaptionFile({
|
|
60
81
|
complete: true,
|
|
61
82
|
id: "123",
|
|
@@ -83,6 +104,9 @@ describe("SyncCaption", async () => {
|
|
|
83
104
|
|
|
84
105
|
test("uploads caption", async () => {
|
|
85
106
|
server.use(
|
|
107
|
+
mockLookupCaptionFileByMd5NotFound({
|
|
108
|
+
md5: video!.md5,
|
|
109
|
+
}),
|
|
86
110
|
mockCreateCaptionFile({
|
|
87
111
|
complete: true,
|
|
88
112
|
id: "123",
|
|
@@ -115,6 +139,9 @@ describe("SyncCaption", async () => {
|
|
|
115
139
|
|
|
116
140
|
test("marks synced", async () => {
|
|
117
141
|
server.use(
|
|
142
|
+
mockLookupCaptionFileByMd5NotFound({
|
|
143
|
+
md5: video!.md5,
|
|
144
|
+
}),
|
|
118
145
|
mockCreateCaptionFile({
|
|
119
146
|
complete: true,
|
|
120
147
|
id: "123",
|
|
@@ -135,7 +162,6 @@ describe("SyncCaption", async () => {
|
|
|
135
162
|
complete: true,
|
|
136
163
|
id: "123",
|
|
137
164
|
md5: video!.md5,
|
|
138
|
-
asset_id: `${video!.md5}:test.mp4`,
|
|
139
165
|
byte_size: 35,
|
|
140
166
|
});
|
|
141
167
|
});
|
|
@@ -2,20 +2,24 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
type CreateCaptionFileResult,
|
|
5
|
+
type LookupCaptionFileByMd5Result,
|
|
5
6
|
createCaptionFile,
|
|
7
|
+
lookupCaptionFileByMd5,
|
|
6
8
|
uploadCaptionFile,
|
|
7
9
|
} from "@editframe/api";
|
|
8
10
|
|
|
9
11
|
import { Readable } from "node:stream";
|
|
10
12
|
import { getClient } from "../../utils/index.ts";
|
|
13
|
+
|
|
14
|
+
import { basename } from "node:path";
|
|
15
|
+
import { createReadableStreamFromReadable } from "../../utils/createReadableStreamFromReadable.ts";
|
|
11
16
|
import type { SubAssetSync } from "./SubAssetSync.ts";
|
|
12
17
|
import { SyncStatus } from "./SyncStatus.ts";
|
|
13
|
-
|
|
14
18
|
export class SyncCaption implements SubAssetSync<CreateCaptionFileResult> {
|
|
15
19
|
icon = "📝";
|
|
16
20
|
label = "captions";
|
|
17
21
|
syncStatus: SyncStatus = new SyncStatus(this.path);
|
|
18
|
-
created: CreateCaptionFileResult | null = null;
|
|
22
|
+
created: CreateCaptionFileResult | LookupCaptionFileByMd5Result | null = null;
|
|
19
23
|
constructor(
|
|
20
24
|
public path: string,
|
|
21
25
|
public md5: string,
|
|
@@ -30,11 +34,19 @@ export class SyncCaption implements SubAssetSync<CreateCaptionFileResult> {
|
|
|
30
34
|
async validate() {}
|
|
31
35
|
|
|
32
36
|
async create() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const maybeCaptionFile = await lookupCaptionFileByMd5(
|
|
38
|
+
getClient(),
|
|
39
|
+
this.md5,
|
|
40
|
+
);
|
|
41
|
+
if (maybeCaptionFile) {
|
|
42
|
+
this.created = maybeCaptionFile;
|
|
43
|
+
} else {
|
|
44
|
+
this.created = await createCaptionFile(getClient(), {
|
|
45
|
+
md5: this.md5,
|
|
46
|
+
filename: basename(this.path).replace(/\.captions.json$/, ""),
|
|
47
|
+
byte_size: await this.byteSize(),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
isComplete() {
|
|
@@ -52,7 +64,9 @@ export class SyncCaption implements SubAssetSync<CreateCaptionFileResult> {
|
|
|
52
64
|
this.created.id,
|
|
53
65
|
// It's not clear why we need to use Readable.from here, but it seems
|
|
54
66
|
// to fix an issue where the request is closed early in tests
|
|
55
|
-
|
|
67
|
+
createReadableStreamFromReadable(
|
|
68
|
+
Readable.from(await fs.readFile(this.path)),
|
|
69
|
+
),
|
|
56
70
|
await this.byteSize(),
|
|
57
71
|
);
|
|
58
72
|
}
|
|
@@ -69,7 +83,6 @@ export class SyncCaption implements SubAssetSync<CreateCaptionFileResult> {
|
|
|
69
83
|
complete: true,
|
|
70
84
|
id: this.created.id,
|
|
71
85
|
md5: this.md5,
|
|
72
|
-
asset_id: this.created.asset_id,
|
|
73
86
|
byte_size: byteSize,
|
|
74
87
|
});
|
|
75
88
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { useMSW } from "TEST/useMSW.ts";
|
|
1
2
|
import { describe, expect, test } from "vitest";
|
|
2
3
|
import { fixture, withFixtures } from "../../../test-fixtures/fixture.ts";
|
|
3
4
|
import {
|
|
4
5
|
mockCreateIsobmffFile,
|
|
6
|
+
mockLookupISOBMFFFileByMd5,
|
|
7
|
+
mockLookupISOBMFFFileByMd5NotFound,
|
|
5
8
|
mockUploadIsobmffFileIndex,
|
|
6
|
-
useMSW,
|
|
7
9
|
} from "../../../test-fixtures/network.ts";
|
|
8
10
|
import { SyncFragmentIndex } from "./SyncFragmentIndex.ts";
|
|
9
11
|
|
|
@@ -34,8 +36,25 @@ describe("SyncFragmentIndex", async () => {
|
|
|
34
36
|
await expect(syncFragmentIndex.validate()).resolves.toBeUndefined();
|
|
35
37
|
});
|
|
36
38
|
describe(".create()", () => {
|
|
39
|
+
test("uses matching data when file matches md5", async () => {
|
|
40
|
+
server.use(
|
|
41
|
+
mockLookupISOBMFFFileByMd5({
|
|
42
|
+
md5: video!.md5,
|
|
43
|
+
fixture: video!,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
const syncFragmentIndex = new SyncFragmentIndex(
|
|
47
|
+
await generateTrackFragmentIndex(video!),
|
|
48
|
+
video!.md5,
|
|
49
|
+
);
|
|
50
|
+
await syncFragmentIndex.create();
|
|
51
|
+
expect(syncFragmentIndex.isComplete()).toBe(false);
|
|
52
|
+
});
|
|
37
53
|
test("isComplete() returns false when not created", async () => {
|
|
38
54
|
server.use(
|
|
55
|
+
mockLookupISOBMFFFileByMd5NotFound({
|
|
56
|
+
md5: video!.md5,
|
|
57
|
+
}),
|
|
39
58
|
mockCreateIsobmffFile({
|
|
40
59
|
complete: false,
|
|
41
60
|
id: "123",
|
|
@@ -52,6 +71,9 @@ describe("SyncFragmentIndex", async () => {
|
|
|
52
71
|
});
|
|
53
72
|
test("isComplete() returns true when created", async () => {
|
|
54
73
|
server.use(
|
|
74
|
+
mockLookupISOBMFFFileByMd5NotFound({
|
|
75
|
+
md5: video!.md5,
|
|
76
|
+
}),
|
|
55
77
|
mockCreateIsobmffFile({
|
|
56
78
|
complete: true,
|
|
57
79
|
id: "123",
|
|
@@ -75,8 +97,11 @@ describe("SyncFragmentIndex", async () => {
|
|
|
75
97
|
);
|
|
76
98
|
await expect(syncFragmentIndex.upload()).rejects.toThrow();
|
|
77
99
|
});
|
|
78
|
-
test("uploads
|
|
100
|
+
test("uploads index", async () => {
|
|
79
101
|
server.use(
|
|
102
|
+
mockLookupISOBMFFFileByMd5NotFound({
|
|
103
|
+
md5: video!.md5,
|
|
104
|
+
}),
|
|
80
105
|
mockCreateIsobmffFile({
|
|
81
106
|
complete: true,
|
|
82
107
|
id: "123",
|
|
@@ -105,6 +130,9 @@ describe("SyncFragmentIndex", async () => {
|
|
|
105
130
|
});
|
|
106
131
|
test("marks synced", async () => {
|
|
107
132
|
server.use(
|
|
133
|
+
mockLookupISOBMFFFileByMd5NotFound({
|
|
134
|
+
md5: video!.md5,
|
|
135
|
+
}),
|
|
108
136
|
mockCreateIsobmffFile({
|
|
109
137
|
complete: true,
|
|
110
138
|
id: "123",
|
|
@@ -130,7 +158,6 @@ describe("SyncFragmentIndex", async () => {
|
|
|
130
158
|
complete: true,
|
|
131
159
|
id: "123",
|
|
132
160
|
md5: video!.md5,
|
|
133
|
-
asset_id: `${video!.md5}:test.mp4`,
|
|
134
161
|
byte_size: 31,
|
|
135
162
|
});
|
|
136
163
|
|
|
@@ -141,7 +168,6 @@ describe("SyncFragmentIndex", async () => {
|
|
|
141
168
|
complete: true,
|
|
142
169
|
id: "123",
|
|
143
170
|
md5: video!.md5,
|
|
144
|
-
asset_id: `${video!.md5}:test.mp4`,
|
|
145
171
|
byte_size: 31,
|
|
146
172
|
});
|
|
147
173
|
});
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
3
|
import { Readable } from "node:stream";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
type CreateISOBMFFFileResult,
|
|
7
|
+
type LookupISOBMFFFileByMd5Result,
|
|
7
8
|
createISOBMFFFile,
|
|
9
|
+
lookupISOBMFFFileByMd5,
|
|
8
10
|
uploadFragmentIndex,
|
|
9
11
|
} from "@editframe/api";
|
|
10
12
|
|
|
13
|
+
import { createReadableStreamFromReadable } from "../../utils/createReadableStreamFromReadable.ts";
|
|
11
14
|
import { getClient } from "../../utils/index.ts";
|
|
12
15
|
import type { SubAssetSync } from "./SubAssetSync.ts";
|
|
13
16
|
import { SyncStatus } from "./SyncStatus.ts";
|
|
@@ -17,11 +20,9 @@ export class SyncFragmentIndex
|
|
|
17
20
|
{
|
|
18
21
|
icon = "📋";
|
|
19
22
|
label = "fragment index";
|
|
20
|
-
syncStatus
|
|
21
|
-
fileSyncStatus
|
|
22
|
-
|
|
23
|
-
);
|
|
24
|
-
created: CreateISOBMFFFileResult | null = null;
|
|
23
|
+
syncStatus = new SyncStatus(this.path);
|
|
24
|
+
fileSyncStatus = new SyncStatus(join(dirname(this.path), "isobmff"));
|
|
25
|
+
created: CreateISOBMFFFileResult | LookupISOBMFFFileByMd5Result | null = null;
|
|
25
26
|
|
|
26
27
|
constructor(
|
|
27
28
|
public path: string,
|
|
@@ -37,10 +38,18 @@ export class SyncFragmentIndex
|
|
|
37
38
|
async validate() {}
|
|
38
39
|
|
|
39
40
|
async create() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
}
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
isComplete() {
|
|
@@ -58,7 +67,9 @@ export class SyncFragmentIndex
|
|
|
58
67
|
this.created.id,
|
|
59
68
|
// It is unclear why we need to use Readable.from here
|
|
60
69
|
// Tests fail when using createReadStream
|
|
61
|
-
|
|
70
|
+
createReadableStreamFromReadable(
|
|
71
|
+
Readable.from(await fs.readFile(this.path)),
|
|
72
|
+
),
|
|
62
73
|
await this.byteSize(),
|
|
63
74
|
);
|
|
64
75
|
}
|
|
@@ -76,7 +87,6 @@ export class SyncFragmentIndex
|
|
|
76
87
|
complete: true,
|
|
77
88
|
id: this.created.id,
|
|
78
89
|
md5: this.md5,
|
|
79
|
-
asset_id: this.created.asset_id,
|
|
80
90
|
byte_size: byteSize,
|
|
81
91
|
}),
|
|
82
92
|
this.fileSyncStatus.markSynced({
|
|
@@ -84,7 +94,6 @@ export class SyncFragmentIndex
|
|
|
84
94
|
complete: true,
|
|
85
95
|
id: this.created.id,
|
|
86
96
|
md5: this.md5,
|
|
87
|
-
asset_id: this.created.asset_id,
|
|
88
97
|
byte_size: byteSize,
|
|
89
98
|
}),
|
|
90
99
|
]);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { useMSW } from "TEST/useMSW.ts";
|
|
1
2
|
import { describe, expect, test } from "vitest";
|
|
2
3
|
import { fixture, withFixtures } from "../../../test-fixtures/fixture.ts";
|
|
3
4
|
import {
|
|
4
5
|
mockCreateImageFile,
|
|
5
6
|
mockGetUploadImageFile,
|
|
6
|
-
|
|
7
|
+
mockLookupImageFileByMd5,
|
|
8
|
+
mockLookupImageFileByMd5NotFound,
|
|
7
9
|
} from "../../../test-fixtures/network.ts";
|
|
8
10
|
import { SyncImage } from "./SyncImage.ts";
|
|
9
|
-
|
|
10
11
|
describe("SyncImage", async () => {
|
|
11
12
|
const server = useMSW();
|
|
12
13
|
await withFixtures(
|
|
@@ -37,8 +38,23 @@ describe("SyncImage", async () => {
|
|
|
37
38
|
const syncImage = new SyncImage(await cacheImage(image!), image!.md5);
|
|
38
39
|
await expect(syncImage.create()).rejects.toThrow();
|
|
39
40
|
});
|
|
41
|
+
test("Skips creation if image file matches md5", async () => {
|
|
42
|
+
server.use(
|
|
43
|
+
mockLookupImageFileByMd5({
|
|
44
|
+
md5: image!.md5,
|
|
45
|
+
fixture: image!,
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
const syncImage = new SyncImage(await cacheImage(image!), image!.md5);
|
|
49
|
+
await syncImage.prepare();
|
|
50
|
+
await syncImage.create();
|
|
51
|
+
expect(syncImage.isComplete()).toBe(true);
|
|
52
|
+
});
|
|
40
53
|
test("isComplete() returns false when not created", async () => {
|
|
41
54
|
server.use(
|
|
55
|
+
mockLookupImageFileByMd5NotFound({
|
|
56
|
+
md5: image!.md5,
|
|
57
|
+
}),
|
|
42
58
|
mockCreateImageFile({
|
|
43
59
|
complete: false,
|
|
44
60
|
id: "123",
|
|
@@ -53,6 +69,9 @@ describe("SyncImage", async () => {
|
|
|
53
69
|
});
|
|
54
70
|
test("isComplete() returns true when created", async () => {
|
|
55
71
|
server.use(
|
|
72
|
+
mockLookupImageFileByMd5NotFound({
|
|
73
|
+
md5: image!.md5,
|
|
74
|
+
}),
|
|
56
75
|
mockCreateImageFile({
|
|
57
76
|
complete: true,
|
|
58
77
|
id: "123",
|
|
@@ -74,6 +93,9 @@ describe("SyncImage", async () => {
|
|
|
74
93
|
});
|
|
75
94
|
test("uploads image", async () => {
|
|
76
95
|
server.use(
|
|
96
|
+
mockLookupImageFileByMd5NotFound({
|
|
97
|
+
md5: image!.md5,
|
|
98
|
+
}),
|
|
77
99
|
mockCreateImageFile({
|
|
78
100
|
complete: false,
|
|
79
101
|
id: "123",
|
|
@@ -98,6 +120,9 @@ describe("SyncImage", async () => {
|
|
|
98
120
|
});
|
|
99
121
|
test("marks synced", async () => {
|
|
100
122
|
server.use(
|
|
123
|
+
mockLookupImageFileByMd5NotFound({
|
|
124
|
+
md5: image!.md5,
|
|
125
|
+
}),
|
|
101
126
|
mockCreateImageFile({
|
|
102
127
|
complete: true,
|
|
103
128
|
id: "123",
|
|
@@ -119,7 +144,6 @@ describe("SyncImage", async () => {
|
|
|
119
144
|
complete: true,
|
|
120
145
|
id: "123",
|
|
121
146
|
md5: image!.md5,
|
|
122
|
-
asset_id: `${image!.md5}:test.png`,
|
|
123
147
|
byte_size: 276,
|
|
124
148
|
});
|
|
125
149
|
});
|
|
@@ -4,12 +4,15 @@ import path, { basename } from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
type CreateImageFileResult,
|
|
7
|
+
type LookupImageFileByMd5Result,
|
|
7
8
|
createImageFile,
|
|
9
|
+
lookupImageFileByMd5,
|
|
8
10
|
uploadImageFile,
|
|
9
11
|
} from "@editframe/api";
|
|
10
12
|
|
|
11
13
|
import { Probe } from "@editframe/assets";
|
|
12
14
|
|
|
15
|
+
import { createReadableStreamFromReadable } from "../../utils/createReadableStreamFromReadable.ts";
|
|
13
16
|
import { getClient } from "../../utils/index.ts";
|
|
14
17
|
import type { SubAssetSync } from "./SubAssetSync.ts";
|
|
15
18
|
import { SyncStatus } from "./SyncStatus.ts";
|
|
@@ -18,7 +21,7 @@ export class SyncImage implements SubAssetSync<CreateImageFileResult> {
|
|
|
18
21
|
icon = "🖼️";
|
|
19
22
|
label = "image";
|
|
20
23
|
syncStatus: SyncStatus = new SyncStatus(this.path);
|
|
21
|
-
created: CreateImageFileResult | null = null;
|
|
24
|
+
created: CreateImageFileResult | LookupImageFileByMd5Result | null = null;
|
|
22
25
|
|
|
23
26
|
constructor(
|
|
24
27
|
public path: string,
|
|
@@ -64,18 +67,24 @@ export class SyncImage implements SubAssetSync<CreateImageFileResult> {
|
|
|
64
67
|
"No video stream found in image. Should have been prevented by .validate()",
|
|
65
68
|
);
|
|
66
69
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
|
|
71
|
+
const maybeImageFile = await lookupImageFileByMd5(getClient(), this.md5);
|
|
72
|
+
if (maybeImageFile) {
|
|
73
|
+
this.created = maybeImageFile;
|
|
74
|
+
} else {
|
|
75
|
+
this.created = await createImageFile(getClient(), {
|
|
76
|
+
md5: this.md5,
|
|
77
|
+
filename: basename(this.path),
|
|
78
|
+
width: videoProbe.width,
|
|
79
|
+
height: videoProbe.height,
|
|
80
|
+
mime_type: `image/${this.extension}` as
|
|
81
|
+
| "image/jpeg"
|
|
82
|
+
| "image/png"
|
|
83
|
+
| "image/jpg"
|
|
84
|
+
| "image/webp",
|
|
85
|
+
byte_size: byteSize,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
79
88
|
}
|
|
80
89
|
isComplete() {
|
|
81
90
|
return !!this.created?.complete;
|
|
@@ -88,9 +97,11 @@ export class SyncImage implements SubAssetSync<CreateImageFileResult> {
|
|
|
88
97
|
}
|
|
89
98
|
await uploadImageFile(
|
|
90
99
|
getClient(),
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
{
|
|
101
|
+
id: this.created.id,
|
|
102
|
+
byte_size: Number.parseInt(this.probeResult.format.size || "0"),
|
|
103
|
+
},
|
|
104
|
+
createReadableStreamFromReadable(createReadStream(this.path)),
|
|
94
105
|
).whenUploaded();
|
|
95
106
|
}
|
|
96
107
|
async markSynced() {
|
|
@@ -105,7 +116,6 @@ export class SyncImage implements SubAssetSync<CreateImageFileResult> {
|
|
|
105
116
|
complete: true,
|
|
106
117
|
id: this.created.id,
|
|
107
118
|
md5: this.md5,
|
|
108
|
-
asset_id: this.created.asset_id,
|
|
109
119
|
byte_size: byteSize,
|
|
110
120
|
});
|
|
111
121
|
}
|
|
@@ -9,22 +9,22 @@ const SyncStatusSchema = z.object({
|
|
|
9
9
|
complete: z.boolean(),
|
|
10
10
|
id: z.string(),
|
|
11
11
|
md5: z.string(),
|
|
12
|
-
asset_id: z.string(),
|
|
13
12
|
byte_size: z.number(),
|
|
14
13
|
});
|
|
15
14
|
|
|
16
15
|
export interface SyncStatusInfo extends z.infer<typeof SyncStatusSchema> {}
|
|
17
16
|
|
|
18
17
|
export class SyncStatus {
|
|
19
|
-
constructor(private basePath: string) {}
|
|
20
|
-
|
|
21
18
|
infoPath = `${this.basePath}.info`;
|
|
22
19
|
|
|
20
|
+
constructor(private basePath: string) {}
|
|
21
|
+
|
|
23
22
|
async isSynced() {
|
|
24
23
|
const syncInfo = await this.readInfo();
|
|
25
24
|
if (!syncInfo) {
|
|
26
25
|
return false;
|
|
27
26
|
}
|
|
27
|
+
console.log("syncInfo.infoPath", this.infoPath);
|
|
28
28
|
return syncInfo.version === SYNC_VERSION && syncInfo.complete;
|
|
29
29
|
}
|
|
30
30
|
|