@editframe/cli 0.10.0-beta.4 → 0.10.0-beta.6
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/render.d.ts +1 -1
- package/dist/commands/render.js +77 -51
- package/dist/commands/sync.js +5 -2
- package/dist/operations/getRenderInfo.d.ts +40 -4
- package/dist/operations/getRenderInfo.js +13 -0
- package/dist/operations/processRenderInfo.js +3 -0
- package/dist/operations/syncAssetsDirectory/SubAssetSync.d.ts +20 -0
- package/dist/operations/syncAssetsDirectory/SubAssetSync.js +26 -0
- package/dist/operations/syncAssetsDirectory/SyncCaption.d.ts +19 -0
- package/dist/operations/syncAssetsDirectory/SyncCaption.js +66 -0
- package/dist/operations/syncAssetsDirectory/SyncCaption.test.d.ts +1 -0
- package/dist/operations/syncAssetsDirectory/SyncFragmentIndex.d.ts +20 -0
- package/dist/operations/syncAssetsDirectory/SyncFragmentIndex.js +79 -0
- package/dist/operations/syncAssetsDirectory/SyncFragmentIndex.test.d.ts +1 -0
- package/dist/operations/syncAssetsDirectory/SyncImage.d.ts +23 -0
- package/dist/operations/syncAssetsDirectory/SyncImage.js +95 -0
- package/dist/operations/syncAssetsDirectory/SyncImage.test.d.ts +1 -0
- package/dist/operations/syncAssetsDirectory/SyncStatus.d.ts +41 -0
- package/dist/operations/syncAssetsDirectory/SyncStatus.js +43 -0
- package/dist/operations/syncAssetsDirectory/SyncTrack.d.ts +70 -0
- package/dist/operations/syncAssetsDirectory/SyncTrack.js +138 -0
- package/dist/operations/syncAssetsDirectory/SyncTrack.test.d.ts +1 -0
- package/dist/operations/syncAssetsDirectory/doAssetSync.d.ts +5 -0
- package/dist/operations/syncAssetsDirectory/doAssetSync.js +48 -0
- package/dist/operations/syncAssetsDirectory/doAssetSync.test.d.ts +1 -0
- package/dist/operations/syncAssetsDirectory.d.ts +1 -1
- package/dist/operations/syncAssetsDirectory.js +20 -240
- package/dist/test-fixtures/fixture.d.ts +26 -0
- package/dist/utils/index.js +4 -1
- package/package.json +5 -5
- package/src/commands/process.ts +0 -1
- package/src/commands/render.ts +79 -58
- package/src/commands/sync.ts +5 -2
- package/src/operations/getRenderInfo.ts +14 -0
- package/src/operations/processRenderInfo.ts +3 -0
- package/src/operations/syncAssetsDirectory/SubAssetSync.ts +42 -0
- package/src/operations/syncAssetsDirectory/SyncCaption.test.ts +145 -0
- package/src/operations/syncAssetsDirectory/SyncCaption.ts +76 -0
- package/src/operations/syncAssetsDirectory/SyncFragmentIndex.test.ts +151 -0
- package/src/operations/syncAssetsDirectory/SyncFragmentIndex.ts +92 -0
- package/src/operations/syncAssetsDirectory/SyncImage.test.ts +131 -0
- package/src/operations/syncAssetsDirectory/SyncImage.ts +112 -0
- package/src/operations/syncAssetsDirectory/SyncStatus.ts +51 -0
- package/src/operations/syncAssetsDirectory/SyncTrack.test.ts +222 -0
- package/src/operations/syncAssetsDirectory/SyncTrack.ts +164 -0
- package/src/operations/syncAssetsDirectory/doAssetSync.test.ts +134 -0
- package/src/operations/syncAssetsDirectory/doAssetSync.ts +62 -0
- package/src/operations/syncAssetsDirectory.test.ts +482 -0
- package/src/operations/syncAssetsDirectory.ts +22 -283
- package/src/utils/index.ts +4 -1
- package/test-fixtures/fixture.ts +141 -0
- package/test-fixtures/network.ts +181 -0
- package/test-fixtures/test-captions.json +9 -0
- package/test-fixtures/test.mp4 +0 -0
- package/test-fixtures/test.png +0 -0
- package/src/commands/render.test.ts +0 -34
- /package/dist/{commands/render.test.d.ts → operations/syncAssetsDirectory.test.d.ts} +0 -0
package/dist/VERSION.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.10.0-beta.
|
|
1
|
+
export declare const VERSION = "0.10.0-beta.6";
|
package/dist/VERSION.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const buildAssetId: (
|
|
1
|
+
export declare const buildAssetId: (srcDir: string, src: string, basename: string) => Promise<string>;
|
package/dist/commands/render.js
CHANGED
|
@@ -1,63 +1,69 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
2
|
+
import path, { join, basename } from "node:path";
|
|
4
3
|
import { PassThrough } from "node:stream";
|
|
5
|
-
import
|
|
4
|
+
import { inspect } from "node:util";
|
|
6
5
|
import { program, Option } from "commander";
|
|
7
6
|
import { parse } from "node-html-parser";
|
|
8
|
-
import
|
|
7
|
+
import * as tar from "tar";
|
|
9
8
|
import { md5Directory, md5FilePath } from "@editframe/assets";
|
|
10
|
-
import {
|
|
11
|
-
import { syncAssetDirectory } from "../operations/syncAssetsDirectory.js";
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
12
10
|
import { createRender, uploadRender } from "@editframe/api";
|
|
11
|
+
import debug from "debug";
|
|
12
|
+
import { RenderInfo, getRenderInfo } from "../operations/getRenderInfo.js";
|
|
13
|
+
import { processRenderInfo } from "../operations/processRenderInfo.js";
|
|
14
|
+
import { syncAssetDirectory } from "../operations/syncAssetsDirectory.js";
|
|
15
|
+
import { SyncStatus } from "../operations/syncAssetsDirectory/SyncStatus.js";
|
|
16
|
+
import { getFolderSize } from "../utils/getFolderSize.js";
|
|
17
|
+
import { getClient } from "../utils/index.js";
|
|
13
18
|
import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK.js";
|
|
14
19
|
import { PreviewServer } from "../utils/startPreviewServer.js";
|
|
15
|
-
import { getClient } from "../utils/index.js";
|
|
16
|
-
import { getRenderInfo } from "../operations/getRenderInfo.js";
|
|
17
|
-
import { processRenderInfo } from "../operations/processRenderInfo.js";
|
|
18
20
|
import { validateVideoResolution } from "../utils/validateVideoResolution.js";
|
|
19
|
-
import {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return buildAssetId(assetPath);
|
|
21
|
+
import { withSpinner } from "../utils/withSpinner.js";
|
|
22
|
+
const log = debug("ef:cli:render");
|
|
23
|
+
const buildAssetId = async (srcDir, src, basename2) => {
|
|
24
|
+
log(`Building image asset id for ${src}
|
|
25
|
+
`);
|
|
26
|
+
const assetPath = path.join(srcDir, src);
|
|
27
|
+
const assetMd5 = await md5FilePath(assetPath);
|
|
28
|
+
const syncStatus = new SyncStatus(
|
|
29
|
+
join(srcDir, "assets", ".cache", assetMd5, basename2)
|
|
30
|
+
);
|
|
31
|
+
const info = await syncStatus.readInfo();
|
|
32
|
+
if (!info) {
|
|
33
|
+
throw new Error(`SyncStatus info is not found for ${syncStatus.infoPath}`);
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
const strategyBuilders = {
|
|
36
|
-
v1: new V1Builder(),
|
|
37
|
-
v2: new V2Builder()
|
|
35
|
+
return info.asset_id;
|
|
38
36
|
};
|
|
39
37
|
program.command("render [directory]").description(
|
|
40
38
|
"Render a directory's index.html file as a video in the editframe cloud"
|
|
41
39
|
).addOption(
|
|
42
|
-
new Option("-s, --strategy <strategy>", "Render strategy").choices(["v1"
|
|
40
|
+
new Option("-s, --strategy <strategy>", "Render strategy").choices(["v1"]).default("v1")
|
|
43
41
|
).action(async (directory, options) => {
|
|
44
42
|
directory ??= ".";
|
|
45
|
-
await syncAssetDirectory(
|
|
43
|
+
await syncAssetDirectory(
|
|
44
|
+
join(process.cwd(), directory, "src", "assets", ".cache")
|
|
45
|
+
);
|
|
46
46
|
const srcDir = path.join(directory, "src");
|
|
47
47
|
const distDir = path.join(directory, "dist");
|
|
48
|
-
const builder = strategyBuilders[options.strategy];
|
|
49
|
-
if (!builder) {
|
|
50
|
-
console.error("Invalid strategy", options.strategy);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
48
|
await withSpinner("Building\n", async () => {
|
|
54
49
|
try {
|
|
55
|
-
await
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
await withSpinner("Building\n", async () => {
|
|
51
|
+
spawnSync(
|
|
52
|
+
"npx",
|
|
53
|
+
// biome-ignore format: Grouping CLI arguments
|
|
54
|
+
[
|
|
55
|
+
"vite",
|
|
56
|
+
"build",
|
|
57
|
+
directory,
|
|
58
|
+
"--clearScreen",
|
|
59
|
+
"false",
|
|
60
|
+
"--logLevel",
|
|
61
|
+
"debug"
|
|
62
|
+
],
|
|
63
|
+
{
|
|
64
|
+
stdio: "inherit"
|
|
65
|
+
}
|
|
66
|
+
);
|
|
61
67
|
});
|
|
62
68
|
} catch (error) {
|
|
63
69
|
console.error("Build failed:", error);
|
|
@@ -75,7 +81,7 @@ program.command("render [directory]").description(
|
|
|
75
81
|
headless: true
|
|
76
82
|
},
|
|
77
83
|
async (page) => {
|
|
78
|
-
const renderInfo = await page.evaluate(getRenderInfo);
|
|
84
|
+
const renderInfo = RenderInfo.parse(await page.evaluate(getRenderInfo));
|
|
79
85
|
validateVideoResolution({
|
|
80
86
|
width: renderInfo.width,
|
|
81
87
|
height: renderInfo.height
|
|
@@ -84,19 +90,39 @@ program.command("render [directory]").description(
|
|
|
84
90
|
const doc = parse(
|
|
85
91
|
await readFile(path.join(distDir, "index.html"), "utf-8")
|
|
86
92
|
);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
log("Building asset IDs");
|
|
94
|
+
for (const element of doc.querySelectorAll(
|
|
95
|
+
"ef-image, ef-audio, ef-video"
|
|
96
|
+
)) {
|
|
97
|
+
log(`Processing ${element.tagName}`);
|
|
98
|
+
if (element.hasAttribute("asset-id")) {
|
|
99
|
+
log(
|
|
100
|
+
`Asset ID for ${element.tagName} ${element.getAttribute("src")} is ${element.getAttribute("asset-id")}`
|
|
101
|
+
);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
91
104
|
const src = element.getAttribute("src");
|
|
92
105
|
if (!src) {
|
|
106
|
+
log(`No src attribute for ${element.tagName}`);
|
|
93
107
|
continue;
|
|
94
108
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
switch (element.tagName) {
|
|
110
|
+
case "EF-IMAGE":
|
|
111
|
+
element.setAttribute(
|
|
112
|
+
"asset-id",
|
|
113
|
+
await buildAssetId(srcDir, src, basename(src))
|
|
114
|
+
);
|
|
115
|
+
break;
|
|
116
|
+
case "EF-AUDIO":
|
|
117
|
+
case "EF-VIDEO":
|
|
118
|
+
element.setAttribute(
|
|
119
|
+
"asset-id",
|
|
120
|
+
await buildAssetId(srcDir, src, "isobmff")
|
|
121
|
+
);
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
log(`Unknown element type: ${element.tagName}`);
|
|
125
|
+
}
|
|
100
126
|
}
|
|
101
127
|
await writeFile(path.join(distDir, "index.html"), doc.toString());
|
|
102
128
|
const md5 = await md5Directory(distDir);
|
|
@@ -126,7 +152,7 @@ program.command("render [directory]").description(
|
|
|
126
152
|
const readable = new PassThrough();
|
|
127
153
|
tarStream.pipe(readable);
|
|
128
154
|
const folderSize = await getFolderSize(distDir);
|
|
129
|
-
await uploadRender(getClient(),
|
|
155
|
+
await uploadRender(getClient(), render.id, readable, folderSize);
|
|
130
156
|
process.stderr.write("Render assets uploaded\n");
|
|
131
157
|
process.stderr.write(inspect(render));
|
|
132
158
|
process.stderr.write("\n");
|
package/dist/commands/sync.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import { program } from "commander";
|
|
2
3
|
import { syncAssetDirectory } from "../operations/syncAssetsDirectory.js";
|
|
3
|
-
program.command("sync").description("Sync assets to Editframe servers for rendering").argument("[directory]", "Path to project directory to sync.").action(async (
|
|
4
|
-
await syncAssetDirectory(
|
|
4
|
+
program.command("sync").description("Sync assets to Editframe servers for rendering").argument("[directory]", "Path to project directory to sync.").action(async (directory = ".") => {
|
|
5
|
+
await syncAssetDirectory(
|
|
6
|
+
join(process.cwd(), directory, "src", "assets", ".cache")
|
|
7
|
+
);
|
|
5
8
|
});
|
|
@@ -1,7 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const RenderInfo: z.ZodObject<{
|
|
3
|
+
width: z.ZodNumber;
|
|
4
|
+
height: z.ZodNumber;
|
|
5
|
+
fps: z.ZodNumber;
|
|
6
|
+
durationMs: z.ZodNumber;
|
|
7
|
+
assets: z.ZodObject<{
|
|
8
|
+
efMedia: z.ZodRecord<z.ZodString, z.ZodAny>;
|
|
9
|
+
efCaptions: z.ZodArray<z.ZodString, "many">;
|
|
10
|
+
efImage: z.ZodArray<z.ZodString, "many">;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
efMedia: Record<string, any>;
|
|
13
|
+
efCaptions: string[];
|
|
14
|
+
efImage: string[];
|
|
15
|
+
}, {
|
|
16
|
+
efMedia: Record<string, any>;
|
|
17
|
+
efCaptions: string[];
|
|
18
|
+
efImage: string[];
|
|
19
|
+
}>;
|
|
20
|
+
}, "strip", z.ZodTypeAny, {
|
|
21
|
+
height: number;
|
|
22
|
+
width: number;
|
|
23
|
+
fps: number;
|
|
24
|
+
assets: {
|
|
25
|
+
efMedia: Record<string, any>;
|
|
26
|
+
efCaptions: string[];
|
|
27
|
+
efImage: string[];
|
|
28
|
+
};
|
|
29
|
+
durationMs: number;
|
|
30
|
+
}, {
|
|
31
|
+
height: number;
|
|
32
|
+
width: number;
|
|
33
|
+
fps: number;
|
|
34
|
+
assets: {
|
|
35
|
+
efMedia: Record<string, any>;
|
|
36
|
+
efCaptions: string[];
|
|
37
|
+
efImage: string[];
|
|
38
|
+
};
|
|
39
|
+
durationMs: number;
|
|
40
|
+
}>;
|
|
5
41
|
export declare const getRenderInfo: () => Promise<{
|
|
6
42
|
width: number;
|
|
7
43
|
height: number;
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const RenderInfo = z.object({
|
|
3
|
+
width: z.number().positive(),
|
|
4
|
+
height: z.number().positive(),
|
|
5
|
+
fps: z.number().positive(),
|
|
6
|
+
durationMs: z.number().positive(),
|
|
7
|
+
assets: z.object({
|
|
8
|
+
efMedia: z.record(z.any()),
|
|
9
|
+
efCaptions: z.array(z.string()),
|
|
10
|
+
efImage: z.array(z.string())
|
|
11
|
+
})
|
|
12
|
+
});
|
|
1
13
|
const getRenderInfo = async () => {
|
|
2
14
|
const rootTimeGroup = document.querySelector("ef-timegroup");
|
|
3
15
|
if (!rootTimeGroup) {
|
|
@@ -55,5 +67,6 @@ const getRenderInfo = async () => {
|
|
|
55
67
|
return renderInfo;
|
|
56
68
|
};
|
|
57
69
|
export {
|
|
70
|
+
RenderInfo,
|
|
58
71
|
getRenderInfo
|
|
59
72
|
};
|
|
@@ -5,6 +5,9 @@ const processRenderInfo = async (renderInfo) => {
|
|
|
5
5
|
process.stderr.write(src);
|
|
6
6
|
process.stderr.write("\n");
|
|
7
7
|
for (const trackId in tracks) {
|
|
8
|
+
process.stderr.write("Generating track: ");
|
|
9
|
+
process.stderr.write(trackId);
|
|
10
|
+
process.stderr.write("\n");
|
|
8
11
|
await generateTrack(
|
|
9
12
|
"./src/assets",
|
|
10
13
|
`./src${src}`,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SyncStatus } from './SyncStatus.ts';
|
|
2
|
+
import { SyncCaption } from './SyncCaption.ts';
|
|
3
|
+
import { SyncFragmentIndex } from './SyncFragmentIndex.ts';
|
|
4
|
+
import { SyncImage } from './SyncImage.ts';
|
|
5
|
+
import { SyncTrack } from './SyncTrack.ts';
|
|
6
|
+
export interface SubAssetSync<CreationType> {
|
|
7
|
+
icon: string;
|
|
8
|
+
label: string;
|
|
9
|
+
path: string;
|
|
10
|
+
md5: string;
|
|
11
|
+
prepare: () => Promise<void>;
|
|
12
|
+
validate: () => Promise<void>;
|
|
13
|
+
create: () => Promise<void>;
|
|
14
|
+
upload: () => Promise<void>;
|
|
15
|
+
syncStatus: SyncStatus;
|
|
16
|
+
isComplete: () => boolean;
|
|
17
|
+
markSynced: () => Promise<void>;
|
|
18
|
+
created: CreationType | null;
|
|
19
|
+
}
|
|
20
|
+
export declare const getAssetSync: (subAssetPath: string, md5: string) => SyncCaption | SyncFragmentIndex | SyncImage | SyncTrack;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SyncCaption } from "./SyncCaption.js";
|
|
2
|
+
import { SyncFragmentIndex } from "./SyncFragmentIndex.js";
|
|
3
|
+
import { SyncImage } from "./SyncImage.js";
|
|
4
|
+
import { SyncTrack } from "./SyncTrack.js";
|
|
5
|
+
const trackMatch = /\.track-[\d]+.mp4$/i;
|
|
6
|
+
const fragmentIndexMatch = /\.tracks.json$/i;
|
|
7
|
+
const captionsMatch = /\.captions.json$/i;
|
|
8
|
+
const imageMatch = /\.(png|jpe?g|gif|webp)$/i;
|
|
9
|
+
const getAssetSync = (subAssetPath, md5) => {
|
|
10
|
+
if (imageMatch.test(subAssetPath)) {
|
|
11
|
+
return new SyncImage(subAssetPath, md5);
|
|
12
|
+
}
|
|
13
|
+
if (trackMatch.test(subAssetPath)) {
|
|
14
|
+
return new SyncTrack(subAssetPath, md5);
|
|
15
|
+
}
|
|
16
|
+
if (fragmentIndexMatch.test(subAssetPath)) {
|
|
17
|
+
return new SyncFragmentIndex(subAssetPath, md5);
|
|
18
|
+
}
|
|
19
|
+
if (captionsMatch.test(subAssetPath)) {
|
|
20
|
+
return new SyncCaption(subAssetPath, md5);
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Unrecognized sub-asset type: ${subAssetPath}`);
|
|
23
|
+
};
|
|
24
|
+
export {
|
|
25
|
+
getAssetSync
|
|
26
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CreateCaptionFileResult } from '../../../../api/src';
|
|
2
|
+
import { SubAssetSync } from './SubAssetSync.ts';
|
|
3
|
+
import { SyncStatus } from './SyncStatus.ts';
|
|
4
|
+
export declare class SyncCaption implements SubAssetSync<CreateCaptionFileResult> {
|
|
5
|
+
path: string;
|
|
6
|
+
md5: string;
|
|
7
|
+
icon: string;
|
|
8
|
+
label: string;
|
|
9
|
+
syncStatus: SyncStatus;
|
|
10
|
+
created: CreateCaptionFileResult | null;
|
|
11
|
+
constructor(path: string, md5: string);
|
|
12
|
+
byteSize(): Promise<number>;
|
|
13
|
+
prepare(): Promise<void>;
|
|
14
|
+
validate(): Promise<void>;
|
|
15
|
+
create(): Promise<void>;
|
|
16
|
+
isComplete(): boolean;
|
|
17
|
+
upload(): Promise<void>;
|
|
18
|
+
markSynced(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { createCaptionFile, uploadCaptionFile } from "@editframe/api";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { getClient } from "../../utils/index.js";
|
|
5
|
+
import { SyncStatus } from "./SyncStatus.js";
|
|
6
|
+
class SyncCaption {
|
|
7
|
+
constructor(path, md5) {
|
|
8
|
+
this.path = path;
|
|
9
|
+
this.md5 = md5;
|
|
10
|
+
this.icon = "📝";
|
|
11
|
+
this.label = "captions";
|
|
12
|
+
this.syncStatus = new SyncStatus(this.path);
|
|
13
|
+
this.created = null;
|
|
14
|
+
}
|
|
15
|
+
async byteSize() {
|
|
16
|
+
return (await fs.stat(this.path)).size;
|
|
17
|
+
}
|
|
18
|
+
async prepare() {
|
|
19
|
+
}
|
|
20
|
+
async validate() {
|
|
21
|
+
}
|
|
22
|
+
async create() {
|
|
23
|
+
this.created = await createCaptionFile(getClient(), {
|
|
24
|
+
md5: this.md5,
|
|
25
|
+
filename: this.path.replace(/\.captions.json$/, ""),
|
|
26
|
+
byte_size: await this.byteSize()
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
isComplete() {
|
|
30
|
+
return !!this.created?.complete;
|
|
31
|
+
}
|
|
32
|
+
async upload() {
|
|
33
|
+
if (!this.created) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"Caption not created. Should have been prevented by .isComplete()"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
await uploadCaptionFile(
|
|
39
|
+
getClient(),
|
|
40
|
+
this.created.id,
|
|
41
|
+
// It's not clear why we need to use Readable.from here, but it seems
|
|
42
|
+
// to fix an issue where the request is closed early in tests
|
|
43
|
+
Readable.from(await fs.readFile(this.path)),
|
|
44
|
+
await this.byteSize()
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
async markSynced() {
|
|
48
|
+
if (!this.created) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Caption not created. Should have been prevented by .isComplete()"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const byteSize = await this.byteSize();
|
|
54
|
+
await this.syncStatus.markSynced({
|
|
55
|
+
version: "1",
|
|
56
|
+
complete: true,
|
|
57
|
+
id: this.created.id,
|
|
58
|
+
md5: this.md5,
|
|
59
|
+
asset_id: this.created.asset_id,
|
|
60
|
+
byte_size: byteSize
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
SyncCaption
|
|
66
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CreateISOBMFFFileResult } from '../../../../api/src';
|
|
2
|
+
import { SubAssetSync } from './SubAssetSync.ts';
|
|
3
|
+
import { SyncStatus } from './SyncStatus.ts';
|
|
4
|
+
export declare class SyncFragmentIndex implements SubAssetSync<CreateISOBMFFFileResult> {
|
|
5
|
+
path: string;
|
|
6
|
+
md5: string;
|
|
7
|
+
icon: string;
|
|
8
|
+
label: string;
|
|
9
|
+
syncStatus: SyncStatus;
|
|
10
|
+
fileSyncStatus: SyncStatus;
|
|
11
|
+
created: CreateISOBMFFFileResult | null;
|
|
12
|
+
constructor(path: string, md5: string);
|
|
13
|
+
byteSize(): Promise<number>;
|
|
14
|
+
prepare(): Promise<void>;
|
|
15
|
+
validate(): Promise<void>;
|
|
16
|
+
create(): Promise<void>;
|
|
17
|
+
isComplete(): boolean;
|
|
18
|
+
upload(): Promise<void>;
|
|
19
|
+
markSynced(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { createISOBMFFFile, uploadFragmentIndex } from "@editframe/api";
|
|
5
|
+
import { getClient } from "../../utils/index.js";
|
|
6
|
+
import { SyncStatus } from "./SyncStatus.js";
|
|
7
|
+
class SyncFragmentIndex {
|
|
8
|
+
constructor(path, md5) {
|
|
9
|
+
this.path = path;
|
|
10
|
+
this.md5 = md5;
|
|
11
|
+
this.icon = "📋";
|
|
12
|
+
this.label = "fragment index";
|
|
13
|
+
this.syncStatus = new SyncStatus(this.path);
|
|
14
|
+
this.fileSyncStatus = new SyncStatus(
|
|
15
|
+
join(dirname(this.path), "isobmff")
|
|
16
|
+
);
|
|
17
|
+
this.created = null;
|
|
18
|
+
}
|
|
19
|
+
async byteSize() {
|
|
20
|
+
return (await fs.stat(this.path)).size;
|
|
21
|
+
}
|
|
22
|
+
async prepare() {
|
|
23
|
+
}
|
|
24
|
+
async validate() {
|
|
25
|
+
}
|
|
26
|
+
async create() {
|
|
27
|
+
this.created = await createISOBMFFFile(getClient(), {
|
|
28
|
+
md5: this.md5,
|
|
29
|
+
filename: this.path.replace(/\.tracks.json$/, "")
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
isComplete() {
|
|
33
|
+
return !!this.created?.fragment_index_complete;
|
|
34
|
+
}
|
|
35
|
+
async upload() {
|
|
36
|
+
if (!this.created) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"Fragment index not created. Should have been prevented by .isComplete()"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
await uploadFragmentIndex(
|
|
42
|
+
getClient(),
|
|
43
|
+
this.created.id,
|
|
44
|
+
// It is unclear why we need to use Readable.from here
|
|
45
|
+
// Tests fail when using createReadStream
|
|
46
|
+
Readable.from(await fs.readFile(this.path)),
|
|
47
|
+
await this.byteSize()
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
async markSynced() {
|
|
51
|
+
if (!this.created) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
"Fragment index not created. Should have been prevented by .isComplete()"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const byteSize = await this.byteSize();
|
|
57
|
+
await Promise.all([
|
|
58
|
+
this.syncStatus.markSynced({
|
|
59
|
+
version: "1",
|
|
60
|
+
complete: true,
|
|
61
|
+
id: this.created.id,
|
|
62
|
+
md5: this.md5,
|
|
63
|
+
asset_id: this.created.asset_id,
|
|
64
|
+
byte_size: byteSize
|
|
65
|
+
}),
|
|
66
|
+
this.fileSyncStatus.markSynced({
|
|
67
|
+
version: "1",
|
|
68
|
+
complete: true,
|
|
69
|
+
id: this.created.id,
|
|
70
|
+
md5: this.md5,
|
|
71
|
+
asset_id: this.created.asset_id,
|
|
72
|
+
byte_size: byteSize
|
|
73
|
+
})
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
SyncFragmentIndex
|
|
79
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CreateImageFileResult } from '../../../../api/src';
|
|
2
|
+
import { Probe } from '../../../../assets/src';
|
|
3
|
+
import { SubAssetSync } from './SubAssetSync.ts';
|
|
4
|
+
import { SyncStatus } from './SyncStatus.ts';
|
|
5
|
+
export declare class SyncImage implements SubAssetSync<CreateImageFileResult> {
|
|
6
|
+
path: string;
|
|
7
|
+
md5: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
label: string;
|
|
10
|
+
syncStatus: SyncStatus;
|
|
11
|
+
created: CreateImageFileResult | null;
|
|
12
|
+
constructor(path: string, md5: string);
|
|
13
|
+
private _probeResult;
|
|
14
|
+
prepare(): Promise<void>;
|
|
15
|
+
get probeResult(): Probe;
|
|
16
|
+
get extension(): string;
|
|
17
|
+
byteSize(): Promise<number>;
|
|
18
|
+
validate(): Promise<void>;
|
|
19
|
+
create(): Promise<void>;
|
|
20
|
+
isComplete(): boolean;
|
|
21
|
+
upload(): Promise<void>;
|
|
22
|
+
markSynced(): Promise<void>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path, { basename } from "node:path";
|
|
4
|
+
import { createImageFile, uploadImageFile } from "@editframe/api";
|
|
5
|
+
import { Probe } from "@editframe/assets";
|
|
6
|
+
import { getClient } from "../../utils/index.js";
|
|
7
|
+
import { SyncStatus } from "./SyncStatus.js";
|
|
8
|
+
class SyncImage {
|
|
9
|
+
constructor(path2, md5) {
|
|
10
|
+
this.path = path2;
|
|
11
|
+
this.md5 = md5;
|
|
12
|
+
this.icon = "🖼️";
|
|
13
|
+
this.label = "image";
|
|
14
|
+
this.syncStatus = new SyncStatus(this.path);
|
|
15
|
+
this.created = null;
|
|
16
|
+
this._probeResult = null;
|
|
17
|
+
}
|
|
18
|
+
async prepare() {
|
|
19
|
+
this._probeResult = await Probe.probePath(this.path);
|
|
20
|
+
}
|
|
21
|
+
get probeResult() {
|
|
22
|
+
if (!this._probeResult) {
|
|
23
|
+
throw new Error("Probe result not found. Call prepare() first.");
|
|
24
|
+
}
|
|
25
|
+
return this._probeResult;
|
|
26
|
+
}
|
|
27
|
+
get extension() {
|
|
28
|
+
return path.extname(this.path).slice(1);
|
|
29
|
+
}
|
|
30
|
+
async byteSize() {
|
|
31
|
+
return (await fs.stat(this.path)).size;
|
|
32
|
+
}
|
|
33
|
+
async validate() {
|
|
34
|
+
const [videoProbe] = this.probeResult.videoStreams;
|
|
35
|
+
if (!videoProbe) {
|
|
36
|
+
throw new Error(`No media info found in image: ${this.path}`);
|
|
37
|
+
}
|
|
38
|
+
const ext = this.extension;
|
|
39
|
+
if (!(ext === "jpg" || ext === "jpeg" || ext === "png" || ext === "webp")) {
|
|
40
|
+
throw new Error(`Invalid image format: ${this.path}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async create() {
|
|
44
|
+
const byteSize = (await fs.stat(this.path)).size;
|
|
45
|
+
const [videoProbe] = this.probeResult.videoStreams;
|
|
46
|
+
if (!videoProbe) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"No video stream found in image. Should have been prevented by .validate()"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
this.created = await createImageFile(getClient(), {
|
|
52
|
+
md5: this.md5,
|
|
53
|
+
filename: basename(this.path),
|
|
54
|
+
width: videoProbe.width,
|
|
55
|
+
height: videoProbe.height,
|
|
56
|
+
mime_type: `image/${this.extension}`,
|
|
57
|
+
byte_size: byteSize
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
isComplete() {
|
|
61
|
+
return !!this.created?.complete;
|
|
62
|
+
}
|
|
63
|
+
upload() {
|
|
64
|
+
if (!this.created) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"Image not created. Should have been prevented by .isComplete()"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return uploadImageFile(
|
|
70
|
+
getClient(),
|
|
71
|
+
this.created.id,
|
|
72
|
+
createReadStream(this.path),
|
|
73
|
+
Number.parseInt(this.probeResult.format.size || "0")
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
async markSynced() {
|
|
77
|
+
if (!this.created) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"Image not created. Should have been prevented by .isComplete()"
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const byteSize = await this.byteSize();
|
|
83
|
+
return this.syncStatus.markSynced({
|
|
84
|
+
version: "1",
|
|
85
|
+
complete: true,
|
|
86
|
+
id: this.created.id,
|
|
87
|
+
md5: this.md5,
|
|
88
|
+
asset_id: this.created.asset_id,
|
|
89
|
+
byte_size: byteSize
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
SyncImage
|
|
95
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|