@editframe/cli 0.7.0-beta.9 → 0.8.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/VERSION.d.ts +1 -0
- package/dist/VERSION.js +4 -0
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +33 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.js +114 -0
- package/dist/commands/preview.d.ts +1 -0
- package/dist/commands/preview.js +5 -0
- package/dist/commands/process-file.d.ts +1 -0
- package/dist/commands/process-file.js +32 -0
- package/dist/commands/process.d.ts +1 -0
- package/dist/commands/process.js +35 -0
- package/dist/commands/render.d.ts +1 -0
- package/dist/commands/render.js +151 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/operations/getRenderInfo.d.ts +15 -0
- package/dist/operations/getRenderInfo.js +59 -0
- package/dist/operations/processRenderInfo.d.ts +3 -0
- package/dist/operations/processRenderInfo.js +30 -0
- package/dist/operations/syncAssetsDirectory.d.ts +1 -0
- package/dist/operations/syncAssetsDirectory.js +250 -0
- package/dist/utils/attachWorkbench.d.ts +3 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +14 -0
- package/dist/utils/launchBrowserAndWaitForSDK.d.ts +10 -0
- package/dist/utils/launchBrowserAndWaitForSDK.js +49 -0
- package/dist/utils/startDevServer.d.ts +8 -0
- package/dist/utils/startPreviewServer.d.ts +8 -0
- package/dist/utils/startPreviewServer.js +38 -0
- package/dist/utils/validateVideoResolution.d.ts +9 -0
- package/dist/utils/validateVideoResolution.js +29 -0
- package/dist/utils/withSpinner.d.ts +1 -0
- package/dist/utils/withSpinner.js +15 -0
- package/package.json +7 -7
- package/src/commands/auth.ts +1 -1
- package/src/commands/process-file.ts +50 -0
- package/src/commands/process.ts +5 -5
- package/src/commands/render.ts +16 -9
- package/src/commands/sync.ts +1 -1
- package/src/operations/processRenderInfo.ts +1 -1
- package/src/operations/syncAssetsDirectory.ts +1 -1
- package/src/utils/launchBrowserAndWaitForSDK.ts +1 -1
- package/src/utils/startDevServer.ts +1 -1
- package/src/utils/startPreviewServer.ts +1 -1
- package/src/utils/validateVideoResolution.ts +36 -0
- package/.env +0 -1
- package/.env.example +0 -1
- package/CHANGELOG.md +0 -8
- package/src/VERSION.ts +0 -1
- package/src/index.ts +0 -29
- package/tsconfig.json +0 -5
- package/vite.config.ts +0 -22
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { Probe } from "@editframe/assets";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createCaptionFile, uploadCaptionFile, createISOBMFFFile, uploadFragmentIndex, createISOBMFFTrack, uploadISOBMFFTrack, createImageFile, uploadImageFile } from "@editframe/api";
|
|
5
|
+
import { createReadStream } from "node:fs";
|
|
6
|
+
import { getClient } from "../utils/index.js";
|
|
7
|
+
const imageMatch = /\.(png|jpe?g|gif|webp)$/i;
|
|
8
|
+
const trackMatch = /\.track-[\d]+.mp4$/i;
|
|
9
|
+
const fragmentIndexMatch = /\.tracks.json$/i;
|
|
10
|
+
const captionsMatch = /\.captions.json$/i;
|
|
11
|
+
class SyncStatus {
|
|
12
|
+
constructor(assetPath) {
|
|
13
|
+
this.assetPath = assetPath;
|
|
14
|
+
this.infoPath = `${this.assetPath}.info`;
|
|
15
|
+
}
|
|
16
|
+
async isSynced() {
|
|
17
|
+
const result = await fs.stat(this.infoPath).catch((error) => {
|
|
18
|
+
if (error.code === "ENOENT") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
});
|
|
23
|
+
return !!result;
|
|
24
|
+
}
|
|
25
|
+
async markSynced() {
|
|
26
|
+
process.stderr.write(`✏️ Marking asset as synced: ${this.assetPath}
|
|
27
|
+
`);
|
|
28
|
+
await fs.writeFile(this.infoPath, "{}", "utf-8");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const syncAssetDirectory = async (projectDirectory) => {
|
|
32
|
+
const fullPath = path.join(
|
|
33
|
+
process.cwd(),
|
|
34
|
+
projectDirectory,
|
|
35
|
+
"src",
|
|
36
|
+
"assets",
|
|
37
|
+
".cache"
|
|
38
|
+
);
|
|
39
|
+
const stat = await fs.stat(fullPath).catch((error) => {
|
|
40
|
+
if (error.code === "ENOENT") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
});
|
|
45
|
+
if (!stat?.isDirectory()) {
|
|
46
|
+
console.error(`No assets cache directory found at ${fullPath}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const assets = await fs.readdir(fullPath);
|
|
50
|
+
process.stderr.write(`Syncing asset dir: ${fullPath}
|
|
51
|
+
`);
|
|
52
|
+
for (const asset of assets) {
|
|
53
|
+
process.stderr.write(`Syncing asset: ${asset}
|
|
54
|
+
`);
|
|
55
|
+
const assetDir = path.join(fullPath, asset);
|
|
56
|
+
const stat2 = await fs.stat(assetDir);
|
|
57
|
+
if (!stat2.isDirectory()) {
|
|
58
|
+
process.stderr.write("Invalid asset. Did not find asset directory.\n");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const subAssets = await fs.readdir(assetDir);
|
|
62
|
+
for (const subAsset of subAssets) {
|
|
63
|
+
if (subAsset.endsWith(".info")) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const subAssetPath = path.join(assetDir, subAsset);
|
|
67
|
+
const syncStatus = new SyncStatus(subAssetPath);
|
|
68
|
+
if (await syncStatus.isSynced()) {
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
` ✔ Sub-asset has already been synced: ${subAsset}
|
|
71
|
+
`
|
|
72
|
+
);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
switch (true) {
|
|
76
|
+
case imageMatch.test(subAsset): {
|
|
77
|
+
const probeResult = await Probe.probePath(subAssetPath);
|
|
78
|
+
const [videoProbe] = probeResult.videoStreams;
|
|
79
|
+
if (!videoProbe) {
|
|
80
|
+
process.stderr.write(
|
|
81
|
+
`🚫 No video stream found in image: ${subAsset}
|
|
82
|
+
`
|
|
83
|
+
);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
const ext = path.extname(subAsset).slice(1);
|
|
87
|
+
if (!(ext === "jpg" || ext === "jpeg" || ext === "png" || ext === "webp")) {
|
|
88
|
+
process.stderr.write(`🚫 Invalid image format: ${subAsset}
|
|
89
|
+
`);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
process.stderr.write(`🖼️ Syncing image: ${subAsset}
|
|
93
|
+
`);
|
|
94
|
+
const created = await createImageFile(getClient(), {
|
|
95
|
+
id: asset,
|
|
96
|
+
filename: subAsset,
|
|
97
|
+
width: videoProbe.width,
|
|
98
|
+
height: videoProbe.height,
|
|
99
|
+
mime_type: `image/${ext}`
|
|
100
|
+
});
|
|
101
|
+
if (created) {
|
|
102
|
+
if (created.complete) {
|
|
103
|
+
process.stderr.write(" ✔ Image has already been synced.\n");
|
|
104
|
+
} else {
|
|
105
|
+
await uploadImageFile(
|
|
106
|
+
getClient(),
|
|
107
|
+
created.id,
|
|
108
|
+
createReadStream(subAssetPath)
|
|
109
|
+
);
|
|
110
|
+
process.stderr.write(" ✅ Image has been synced.\n");
|
|
111
|
+
}
|
|
112
|
+
await syncStatus.markSynced();
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case trackMatch.test(subAsset): {
|
|
117
|
+
process.stderr.write(`📼 Syncing a/v track: ${subAsset}
|
|
118
|
+
`);
|
|
119
|
+
const createdFile = await createISOBMFFFile(getClient(), {
|
|
120
|
+
id: asset,
|
|
121
|
+
filename: subAsset.replace(/\.track-[\d]+.mp4$/, "")
|
|
122
|
+
});
|
|
123
|
+
if (createdFile) {
|
|
124
|
+
const probe = await Probe.probePath(subAssetPath);
|
|
125
|
+
const trackId = subAsset.match(/track-([\d]+).mp4/)?.[1];
|
|
126
|
+
if (!trackId) {
|
|
127
|
+
process.stderr.write(
|
|
128
|
+
`🚫 No track ID found for track: ${subAsset}
|
|
129
|
+
`
|
|
130
|
+
);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
const [track] = probe.streams;
|
|
134
|
+
if (!track) {
|
|
135
|
+
process.stderr.write(
|
|
136
|
+
`🚫 No track stream found in track: ${subAsset}
|
|
137
|
+
`
|
|
138
|
+
);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (track.duration === void 0) {
|
|
142
|
+
process.stderr.write(
|
|
143
|
+
`🚫 No duration found in track: ${subAsset}
|
|
144
|
+
`
|
|
145
|
+
);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
const stat3 = await fs.stat(subAssetPath);
|
|
149
|
+
const createPayload = track.codec_type === "audio" ? {
|
|
150
|
+
type: track.codec_type,
|
|
151
|
+
file_id: asset,
|
|
152
|
+
track_id: Number(trackId),
|
|
153
|
+
probe_info: track,
|
|
154
|
+
duration_ms: Math.round(track.duration * 1e3),
|
|
155
|
+
codec_name: track.codec_name,
|
|
156
|
+
byte_size: stat3.size
|
|
157
|
+
} : {
|
|
158
|
+
type: track.codec_type,
|
|
159
|
+
file_id: asset,
|
|
160
|
+
track_id: Number(trackId),
|
|
161
|
+
probe_info: track,
|
|
162
|
+
duration_ms: Math.round(track.duration * 1e3),
|
|
163
|
+
codec_name: track.codec_name,
|
|
164
|
+
byte_size: stat3.size
|
|
165
|
+
};
|
|
166
|
+
const createdTrack = await createISOBMFFTrack(
|
|
167
|
+
getClient(),
|
|
168
|
+
createPayload
|
|
169
|
+
);
|
|
170
|
+
if (createdTrack) {
|
|
171
|
+
if (createdTrack.last_received_byte === createdTrack.byte_size - 1) {
|
|
172
|
+
process.stderr.write(" ✔ Track has already been synced.\n");
|
|
173
|
+
} else {
|
|
174
|
+
await uploadISOBMFFTrack(
|
|
175
|
+
getClient(),
|
|
176
|
+
createdFile.id,
|
|
177
|
+
Number(trackId),
|
|
178
|
+
createReadStream(subAssetPath)
|
|
179
|
+
);
|
|
180
|
+
process.stderr.write(" ✅ Track has been synced.\n");
|
|
181
|
+
}
|
|
182
|
+
await syncStatus.markSynced();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case fragmentIndexMatch.test(subAsset): {
|
|
188
|
+
process.stderr.write(`📋 Syncing fragment index: ${subAsset}
|
|
189
|
+
`);
|
|
190
|
+
const createdFile = await createISOBMFFFile(getClient(), {
|
|
191
|
+
id: asset,
|
|
192
|
+
filename: subAsset.replace(/\.tracks.json$/, "")
|
|
193
|
+
});
|
|
194
|
+
if (createdFile) {
|
|
195
|
+
if (createdFile.fragment_index_complete) {
|
|
196
|
+
process.stderr.write(
|
|
197
|
+
" ✔ Fragment index has already been synced.\n"
|
|
198
|
+
);
|
|
199
|
+
} else {
|
|
200
|
+
const readStream = createReadStream(subAssetPath);
|
|
201
|
+
await uploadFragmentIndex(getClient(), asset, readStream);
|
|
202
|
+
process.stderr.write(" ✅ Fragment index has been synced.\n");
|
|
203
|
+
}
|
|
204
|
+
await syncStatus.markSynced();
|
|
205
|
+
} else {
|
|
206
|
+
process.stderr.write(
|
|
207
|
+
`🚫 No file found for fragment index: ${subAsset}
|
|
208
|
+
`
|
|
209
|
+
);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case captionsMatch.test(subAsset): {
|
|
215
|
+
process.stderr.write(`📝 Syncing captions: ${subAsset}
|
|
216
|
+
`);
|
|
217
|
+
const createdFile = await createCaptionFile(getClient(), {
|
|
218
|
+
id: asset,
|
|
219
|
+
filename: subAsset.replace(/\.captions.json$/, "")
|
|
220
|
+
});
|
|
221
|
+
if (createdFile) {
|
|
222
|
+
if (createdFile.complete) {
|
|
223
|
+
process.stderr.write(" ✔ Captions have already been synced.\n");
|
|
224
|
+
} else {
|
|
225
|
+
const readStream = createReadStream(subAssetPath);
|
|
226
|
+
await uploadCaptionFile(getClient(), asset, readStream);
|
|
227
|
+
process.stderr.write(" ✅ Captions have been synced.\n");
|
|
228
|
+
}
|
|
229
|
+
await syncStatus.markSynced();
|
|
230
|
+
} else {
|
|
231
|
+
process.stderr.write(
|
|
232
|
+
`🚫 No file found for captions: ${subAsset}
|
|
233
|
+
`
|
|
234
|
+
);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
default: {
|
|
240
|
+
process.stderr.write(`🚫 Unknown sub-asset: ${subAsset}
|
|
241
|
+
`);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
export {
|
|
249
|
+
syncAssetDirectory
|
|
250
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { program } from "commander";
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { Client } from "@editframe/api";
|
|
4
|
+
let client;
|
|
5
|
+
const getClient = () => {
|
|
6
|
+
if (!client) {
|
|
7
|
+
const programOpts = program.opts();
|
|
8
|
+
client = new Client(programOpts.token, programOpts.efHost);
|
|
9
|
+
}
|
|
10
|
+
return client;
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
getClient
|
|
14
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
|
|
3
|
+
interface LaunchOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
headless?: boolean;
|
|
6
|
+
interactive?: boolean;
|
|
7
|
+
efInteractive?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function launchBrowserAndWaitForSDK(options: LaunchOptions, fn: (page: Page) => Promise<void>): Promise<void>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { chromium } from "playwright";
|
|
3
|
+
import debug from "debug";
|
|
4
|
+
import { withSpinner } from "./withSpinner.js";
|
|
5
|
+
const browserLog = debug("ef:cli::browser");
|
|
6
|
+
async function launchBrowserAndWaitForSDK(options, fn) {
|
|
7
|
+
const browser = await withSpinner("Launching chrome", async () => {
|
|
8
|
+
return chromium.launch({
|
|
9
|
+
channel: "chrome",
|
|
10
|
+
headless: options.headless ?? true,
|
|
11
|
+
// headless: false,
|
|
12
|
+
devtools: options.interactive === true
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
const page = await withSpinner("Loading EditFrame SDK", async () => {
|
|
16
|
+
const pageOptions = {};
|
|
17
|
+
if (options.interactive === true) {
|
|
18
|
+
pageOptions.viewport = null;
|
|
19
|
+
}
|
|
20
|
+
const page2 = await browser.newPage(pageOptions);
|
|
21
|
+
page2.on("console", (msg) => {
|
|
22
|
+
browserLog(chalk.blue(`browser (${msg.type()}) |`), msg.text());
|
|
23
|
+
});
|
|
24
|
+
const url = options.url + (options.efInteractive ? "" : "?EF_NONINTERACTIVE=1");
|
|
25
|
+
process.stderr.write("\nLoading url: ");
|
|
26
|
+
process.stderr.write(url);
|
|
27
|
+
process.stderr.write("\n");
|
|
28
|
+
await page2.goto(url);
|
|
29
|
+
await page2.waitForFunction(
|
|
30
|
+
() => {
|
|
31
|
+
return (
|
|
32
|
+
// @ts-expect-error
|
|
33
|
+
window.EF_REGISTERED
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
[],
|
|
37
|
+
{ timeout: 1e4 }
|
|
38
|
+
);
|
|
39
|
+
return page2;
|
|
40
|
+
});
|
|
41
|
+
await fn(page);
|
|
42
|
+
if (options.interactive !== true) {
|
|
43
|
+
await browser.close();
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
launchBrowserAndWaitForSDK
|
|
49
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createServer } from "vite";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { vitePluginEditframe } from "@editframe/vite-plugin";
|
|
4
|
+
import { withSpinner } from "./withSpinner.js";
|
|
5
|
+
class PreviewServer {
|
|
6
|
+
constructor(previewServer) {
|
|
7
|
+
this.previewServer = previewServer;
|
|
8
|
+
}
|
|
9
|
+
static async start(directory) {
|
|
10
|
+
return new PreviewServer(await startPreviewServer(directory));
|
|
11
|
+
}
|
|
12
|
+
get url() {
|
|
13
|
+
return `http://localhost:${this.previewServer.config.server.port}`;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const startPreviewServer = async (directory) => {
|
|
17
|
+
return await withSpinner("Starting vite...", async () => {
|
|
18
|
+
const resolvedDirectory = path.resolve(process.cwd(), directory);
|
|
19
|
+
const cacheRoot = path.join(resolvedDirectory, "assets");
|
|
20
|
+
const devServer = await createServer({
|
|
21
|
+
server: {
|
|
22
|
+
watch: null
|
|
23
|
+
},
|
|
24
|
+
root: resolvedDirectory,
|
|
25
|
+
plugins: [
|
|
26
|
+
vitePluginEditframe({
|
|
27
|
+
root: resolvedDirectory,
|
|
28
|
+
cacheRoot
|
|
29
|
+
})
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
await devServer.listen();
|
|
33
|
+
return devServer;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
export {
|
|
37
|
+
PreviewServer
|
|
38
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import debug from "debug";
|
|
4
|
+
const log = debug("ef:cli:validateVideoResolution");
|
|
5
|
+
const schema = z.object({
|
|
6
|
+
width: z.number().int(),
|
|
7
|
+
height: z.number().int()
|
|
8
|
+
}).refine((data) => data.width % 2 === 0, {
|
|
9
|
+
message: "Width must be divisible by 2",
|
|
10
|
+
path: ["width"]
|
|
11
|
+
}).refine((data) => data.height % 2 === 0, {
|
|
12
|
+
message: "Height must be divisible by 2"
|
|
13
|
+
});
|
|
14
|
+
const validateVideoResolution = async (rawPayload) => {
|
|
15
|
+
const spinner = ora("Validating video resolution").start();
|
|
16
|
+
const result = schema.safeParse(rawPayload);
|
|
17
|
+
if (result.success) {
|
|
18
|
+
spinner.succeed("Video resolution is valid");
|
|
19
|
+
return result.data;
|
|
20
|
+
}
|
|
21
|
+
spinner.fail("Invalid video resolution");
|
|
22
|
+
process.stderr.write(result.error?.errors.map((e) => e.message).join("\n"));
|
|
23
|
+
process.stderr.write("\n");
|
|
24
|
+
log("Error:", result.error?.errors.map((e) => e.message).join("\n"));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
};
|
|
27
|
+
export {
|
|
28
|
+
validateVideoResolution
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const withSpinner: <T extends unknown>(label: string, fn: () => Promise<T>) => Promise<T>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
const withSpinner = async (label, fn) => {
|
|
3
|
+
const spinner = ora(label).start();
|
|
4
|
+
try {
|
|
5
|
+
const result = await fn();
|
|
6
|
+
spinner.succeed();
|
|
7
|
+
return result;
|
|
8
|
+
} catch (error) {
|
|
9
|
+
spinner.fail();
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
withSpinner
|
|
15
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-beta.2",
|
|
4
4
|
"description": "Command line interface for EditFrame",
|
|
5
5
|
"bin": {
|
|
6
6
|
"editframe": "./dist/index.js"
|
|
@@ -15,17 +15,18 @@
|
|
|
15
15
|
"license": "UNLICENSED",
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/dom-webcodecs": "^0.1.11",
|
|
18
|
-
"@types/node": "^20.14.
|
|
18
|
+
"@types/node": "^20.14.13",
|
|
19
19
|
"@types/promptly": "^3.0.5",
|
|
20
20
|
"@types/tar": "^6.1.13",
|
|
21
|
+
"typescript": "^5.5.4",
|
|
21
22
|
"vite-plugin-dts": "^3.9.1",
|
|
22
23
|
"vite-tsconfig-paths": "^4.3.2"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"@editframe/api": "0.
|
|
26
|
-
"@editframe/assets": "0.
|
|
27
|
-
"@editframe/elements": "0.
|
|
28
|
-
"@editframe/vite-plugin": "0.
|
|
26
|
+
"@editframe/api": "0.8.0-beta.2",
|
|
27
|
+
"@editframe/assets": "0.8.0-beta.2",
|
|
28
|
+
"@editframe/elements": "0.8.0-beta.2",
|
|
29
|
+
"@editframe/vite-plugin": "0.8.0-beta.2",
|
|
29
30
|
"axios": "^1.6.8",
|
|
30
31
|
"chalk": "^5.3.0",
|
|
31
32
|
"commander": "^12.0.0",
|
|
@@ -41,7 +42,6 @@
|
|
|
41
42
|
"promptly": "^3.2.0",
|
|
42
43
|
"tailwindcss": "^3.4.3",
|
|
43
44
|
"tar": "^7.1.0",
|
|
44
|
-
"typescript": "^5.2.2",
|
|
45
45
|
"vite": "^5.2.11",
|
|
46
46
|
"zod": "^3.23.8"
|
|
47
47
|
}
|
package/src/commands/auth.ts
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
|
|
5
|
+
import { withSpinner } from "../utils/withSpinner.ts";
|
|
6
|
+
|
|
7
|
+
import { getClient } from "../utils/index.ts";
|
|
8
|
+
import { createReadStream } from "node:fs";
|
|
9
|
+
import {
|
|
10
|
+
createUnprocessedFile,
|
|
11
|
+
updateUnprocessedFile,
|
|
12
|
+
uploadUnprocessedFile,
|
|
13
|
+
} from "@editframe/api";
|
|
14
|
+
import { md5FilePath } from "@editframe/assets";
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command("process-file <file>")
|
|
18
|
+
.description("Upload a audio/video to Editframe for processing.")
|
|
19
|
+
.action(async (path: string) => {
|
|
20
|
+
const client = getClient();
|
|
21
|
+
|
|
22
|
+
const fileId = await md5FilePath(path);
|
|
23
|
+
|
|
24
|
+
await withSpinner("Creating unprocessed file record", async () => {
|
|
25
|
+
await createUnprocessedFile(client, {
|
|
26
|
+
id: fileId,
|
|
27
|
+
processes: [],
|
|
28
|
+
filename: basename(path),
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const readStream = createReadStream(path);
|
|
33
|
+
|
|
34
|
+
await withSpinner("Uploading file", async () => {
|
|
35
|
+
await uploadUnprocessedFile(client, fileId, readStream);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const unprocessedFile = await withSpinner(
|
|
39
|
+
"Marking for processing",
|
|
40
|
+
async () => {
|
|
41
|
+
return await updateUnprocessedFile(client, fileId, {
|
|
42
|
+
processes: ["isobmff"],
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
process.stderr.write("File uploaded and marked for processing.\n");
|
|
48
|
+
|
|
49
|
+
console.log(JSON.stringify(unprocessedFile, null, 2));
|
|
50
|
+
});
|
package/src/commands/process.ts
CHANGED
|
@@ -3,11 +3,11 @@ import path from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { program } from "commander";
|
|
5
5
|
|
|
6
|
-
import { withSpinner } from "../utils/withSpinner";
|
|
7
|
-
import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK";
|
|
8
|
-
import { PreviewServer } from "../utils/startPreviewServer";
|
|
9
|
-
import { getRenderInfo } from "../operations/getRenderInfo";
|
|
10
|
-
import { processRenderInfo } from "../operations/processRenderInfo";
|
|
6
|
+
import { withSpinner } from "../utils/withSpinner.ts";
|
|
7
|
+
import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK.ts";
|
|
8
|
+
import { PreviewServer } from "../utils/startPreviewServer.ts";
|
|
9
|
+
import { getRenderInfo } from "../operations/getRenderInfo.ts";
|
|
10
|
+
import { processRenderInfo } from "../operations/processRenderInfo.ts";
|
|
11
11
|
|
|
12
12
|
program
|
|
13
13
|
.command("process [directory]")
|
package/src/commands/render.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { inspect } from "node:util";
|
|
4
|
+
import { PassThrough } from "node:stream";
|
|
3
5
|
|
|
4
6
|
import * as tar from "tar";
|
|
5
7
|
import { program, Option } from "commander";
|
|
@@ -8,16 +10,15 @@ import { build } from "vite";
|
|
|
8
10
|
|
|
9
11
|
import { md5Directory, md5FilePath } from "@editframe/assets";
|
|
10
12
|
|
|
11
|
-
import { withSpinner } from "../utils/withSpinner";
|
|
13
|
+
import { withSpinner } from "../utils/withSpinner.ts";
|
|
14
|
+
import { syncAssetDirectory } from "../operations/syncAssetsDirectory.ts";
|
|
12
15
|
import { createRender, uploadRender } from "@editframe/api";
|
|
13
|
-
import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK";
|
|
14
|
-
import { PreviewServer } from "../utils/startPreviewServer";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { processRenderInfo } from "../operations/processRenderInfo";
|
|
20
|
-
import { inspect } from "node:util";
|
|
16
|
+
import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK.ts";
|
|
17
|
+
import { PreviewServer } from "../utils/startPreviewServer.ts";
|
|
18
|
+
import { getClient } from "../utils/index.ts";
|
|
19
|
+
import { getRenderInfo } from "../operations/getRenderInfo.ts";
|
|
20
|
+
import { processRenderInfo } from "../operations/processRenderInfo.ts";
|
|
21
|
+
import { validateVideoResolution } from "../utils/validateVideoResolution.ts";
|
|
21
22
|
|
|
22
23
|
interface StrategyBuilder {
|
|
23
24
|
buildProductionUrl: (tagName: string, assetPath: string) => Promise<string>;
|
|
@@ -114,6 +115,12 @@ program
|
|
|
114
115
|
},
|
|
115
116
|
async (page) => {
|
|
116
117
|
const renderInfo = await page.evaluate(getRenderInfo);
|
|
118
|
+
|
|
119
|
+
validateVideoResolution({
|
|
120
|
+
width: renderInfo.width,
|
|
121
|
+
height: renderInfo.height,
|
|
122
|
+
});
|
|
123
|
+
|
|
117
124
|
await processRenderInfo(renderInfo);
|
|
118
125
|
|
|
119
126
|
const doc = parseHTML(
|
package/src/commands/sync.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
cacheImage,
|
|
4
4
|
findOrCreateCaptions,
|
|
5
5
|
} from "@editframe/assets";
|
|
6
|
-
import type { getRenderInfo } from "./getRenderInfo";
|
|
6
|
+
import type { getRenderInfo } from "./getRenderInfo.ts";
|
|
7
7
|
|
|
8
8
|
export const processRenderInfo = async (
|
|
9
9
|
renderInfo: Awaited<ReturnType<typeof getRenderInfo>>,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
|
|
16
16
|
import { createReadStream } from "node:fs";
|
|
17
17
|
import type { z } from "zod";
|
|
18
|
-
import { getClient } from "../utils";
|
|
18
|
+
import { getClient } from "../utils/index.ts";
|
|
19
19
|
|
|
20
20
|
const imageMatch = /\.(png|jpe?g|gif|webp)$/i;
|
|
21
21
|
const trackMatch = /\.track-[\d]+.mp4$/i;
|
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { type Browser, type Page, chromium } from "playwright";
|
|
3
3
|
import debug from "debug";
|
|
4
4
|
|
|
5
|
-
import { withSpinner } from "./withSpinner";
|
|
5
|
+
import { withSpinner } from "./withSpinner.ts";
|
|
6
6
|
|
|
7
7
|
const browserLog = debug("ef:cli::browser");
|
|
8
8
|
|
|
@@ -5,7 +5,7 @@ import tailwindcss from "tailwindcss";
|
|
|
5
5
|
|
|
6
6
|
import { vitePluginEditframe as editframe } from "@editframe/vite-plugin";
|
|
7
7
|
|
|
8
|
-
import { withSpinner } from "./withSpinner";
|
|
8
|
+
import { withSpinner } from "./withSpinner.ts";
|
|
9
9
|
|
|
10
10
|
export class DevServer {
|
|
11
11
|
static async start(directory: string) {
|