@editframe/cli 0.7.0-beta.9 → 0.8.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 -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 +35 -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 +153 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +5 -0
- package/dist/commands/webhook.d.ts +7 -0
- package/dist/commands/webhook.js +61 -0
- package/dist/index.d.ts +0 -0
- package/dist/index.js +18 -0
- package/dist/operations/getRenderInfo.d.ts +15 -0
- package/dist/operations/getRenderInfo.js +59 -0
- package/dist/operations/processRenderInfo.d.ts +2 -0
- package/dist/operations/processRenderInfo.js +30 -0
- package/dist/operations/syncAssetsDirectory.d.ts +1 -0
- package/dist/operations/syncAssetsDirectory.js +302 -0
- package/dist/utils/attachWorkbench.d.ts +2 -0
- package/dist/utils/getFolderSize.d.ts +1 -0
- package/dist/utils/getFolderSize.js +22 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +14 -0
- package/dist/utils/launchBrowserAndWaitForSDK.d.ts +9 -0
- package/dist/utils/launchBrowserAndWaitForSDK.js +49 -0
- package/dist/utils/startDevServer.d.ts +7 -0
- package/dist/utils/startPreviewServer.d.ts +7 -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 +9 -8
- package/src/commands/auth.ts +1 -1
- package/src/commands/process-file.ts +54 -0
- package/src/commands/process.ts +5 -5
- package/src/commands/render.ts +19 -10
- package/src/commands/sync.ts +1 -1
- package/src/commands/webhook.ts +76 -0
- package/src/operations/processRenderInfo.ts +1 -1
- package/src/operations/syncAssetsDirectory.ts +123 -50
- package/src/utils/getFolderSize.ts +24 -0
- 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,302 @@
|
|
|
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, statSync } 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
|
+
const errors = {};
|
|
53
|
+
const reportError = (path2, message) => {
|
|
54
|
+
errors[path2] ||= [];
|
|
55
|
+
errors[path2].push(message);
|
|
56
|
+
process.stderr.write(` 🚫 ${message}
|
|
57
|
+
`);
|
|
58
|
+
};
|
|
59
|
+
const reportSuccess = (_path, message) => {
|
|
60
|
+
process.stderr.write(` ✅ ${message}
|
|
61
|
+
`);
|
|
62
|
+
};
|
|
63
|
+
const reportInfo = (_path, message) => {
|
|
64
|
+
process.stderr.write(` ${message}
|
|
65
|
+
`);
|
|
66
|
+
};
|
|
67
|
+
for (const asset of assets) {
|
|
68
|
+
reportInfo(asset, `Syncing asset: ${asset}`);
|
|
69
|
+
const assetDir = path.join(fullPath, asset);
|
|
70
|
+
const stat2 = await fs.stat(assetDir);
|
|
71
|
+
if (!stat2.isDirectory()) {
|
|
72
|
+
reportError(asset, "Invalid asset. Did not find asset directory.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const subAssets = await fs.readdir(assetDir);
|
|
76
|
+
for (const subAsset of subAssets) {
|
|
77
|
+
if (subAsset.endsWith(".info")) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const subAssetPath = path.join(assetDir, subAsset);
|
|
81
|
+
const syncStatus = new SyncStatus(subAssetPath);
|
|
82
|
+
if (await syncStatus.isSynced()) {
|
|
83
|
+
reportInfo(
|
|
84
|
+
subAsset,
|
|
85
|
+
`✔ Sub-asset has already been synced: ${subAsset}`
|
|
86
|
+
);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
switch (true) {
|
|
90
|
+
case imageMatch.test(subAsset): {
|
|
91
|
+
const probeResult = await Probe.probePath(subAssetPath);
|
|
92
|
+
const [videoProbe] = probeResult.videoStreams;
|
|
93
|
+
const { format } = probeResult;
|
|
94
|
+
if (!videoProbe) {
|
|
95
|
+
reportError(subAsset, `No media info found in image: ${subAsset}`);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
const ext = path.extname(subAsset).slice(1);
|
|
99
|
+
if (!(ext === "jpg" || ext === "jpeg" || ext === "png" || ext === "webp")) {
|
|
100
|
+
reportError(subAsset, `Invalid image format: ${subAsset}`);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
reportInfo(subAsset, `🖼️ Syncing image: ${subAsset}`);
|
|
104
|
+
const created = await createImageFile(getClient(), {
|
|
105
|
+
id: asset,
|
|
106
|
+
filename: subAsset,
|
|
107
|
+
width: videoProbe.width,
|
|
108
|
+
height: videoProbe.height,
|
|
109
|
+
mime_type: `image/${ext}`,
|
|
110
|
+
byte_size: (await fs.stat(subAssetPath)).size
|
|
111
|
+
});
|
|
112
|
+
if (created) {
|
|
113
|
+
if (created.complete) {
|
|
114
|
+
reportInfo(subAsset, " ✔ Image has already been synced");
|
|
115
|
+
await syncStatus.markSynced();
|
|
116
|
+
} else {
|
|
117
|
+
await uploadImageFile(
|
|
118
|
+
getClient(),
|
|
119
|
+
created.id,
|
|
120
|
+
createReadStream(subAssetPath),
|
|
121
|
+
Number.parseInt(format.size || "0")
|
|
122
|
+
).then(() => {
|
|
123
|
+
reportSuccess(subAsset, "Image has been synced.");
|
|
124
|
+
return syncStatus.markSynced();
|
|
125
|
+
}).catch((error) => {
|
|
126
|
+
reportError(
|
|
127
|
+
subAsset,
|
|
128
|
+
`Error uploading image: ${error.message}`
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case trackMatch.test(subAsset): {
|
|
136
|
+
reportInfo(subAsset, `📼 Syncing a/v track: ${subAsset}`);
|
|
137
|
+
const createdFile = await createISOBMFFFile(getClient(), {
|
|
138
|
+
id: asset,
|
|
139
|
+
filename: subAsset.replace(/\.track-[\d]+.mp4$/, "")
|
|
140
|
+
});
|
|
141
|
+
if (createdFile) {
|
|
142
|
+
const probe = await Probe.probePath(subAssetPath);
|
|
143
|
+
const trackId = subAsset.match(/track-([\d]+).mp4/)?.[1];
|
|
144
|
+
if (!trackId) {
|
|
145
|
+
reportError(subAsset, `No track ID found for track: ${subAsset}`);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
const [track] = probe.streams;
|
|
149
|
+
if (!track) {
|
|
150
|
+
reportError(
|
|
151
|
+
subAsset,
|
|
152
|
+
`No track stream found in track: ${subAsset}`
|
|
153
|
+
);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
if (track.duration === void 0) {
|
|
157
|
+
reportError(subAsset, `No duration found in track: ${subAsset}`);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
const stat3 = await fs.stat(subAssetPath);
|
|
161
|
+
const createPayload = track.codec_type === "audio" ? {
|
|
162
|
+
type: track.codec_type,
|
|
163
|
+
file_id: asset,
|
|
164
|
+
track_id: Number(trackId),
|
|
165
|
+
probe_info: track,
|
|
166
|
+
duration_ms: Math.round(track.duration * 1e3),
|
|
167
|
+
codec_name: track.codec_name,
|
|
168
|
+
byte_size: stat3.size
|
|
169
|
+
} : {
|
|
170
|
+
type: track.codec_type,
|
|
171
|
+
file_id: asset,
|
|
172
|
+
track_id: Number(trackId),
|
|
173
|
+
probe_info: track,
|
|
174
|
+
duration_ms: Math.round(track.duration * 1e3),
|
|
175
|
+
codec_name: track.codec_name,
|
|
176
|
+
byte_size: stat3.size
|
|
177
|
+
};
|
|
178
|
+
const createdTrack = await createISOBMFFTrack(
|
|
179
|
+
getClient(),
|
|
180
|
+
createPayload
|
|
181
|
+
);
|
|
182
|
+
if (createdTrack) {
|
|
183
|
+
if (createdTrack.next_byte === createdTrack.byte_size) {
|
|
184
|
+
reportInfo(subAsset, "✔ Track has already been synced.");
|
|
185
|
+
await syncStatus.markSynced();
|
|
186
|
+
} else {
|
|
187
|
+
await uploadISOBMFFTrack(
|
|
188
|
+
getClient(),
|
|
189
|
+
createdFile.id,
|
|
190
|
+
Number(trackId),
|
|
191
|
+
createReadStream(subAssetPath),
|
|
192
|
+
createdTrack.byte_size
|
|
193
|
+
).then(() => {
|
|
194
|
+
reportSuccess(subAsset, "Track has been synced.");
|
|
195
|
+
return syncStatus.markSynced();
|
|
196
|
+
}).catch((error) => {
|
|
197
|
+
reportError(
|
|
198
|
+
subAsset,
|
|
199
|
+
`Error uploading track: ${error.message}`
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case fragmentIndexMatch.test(subAsset): {
|
|
208
|
+
reportInfo(subAsset, `📋 Syncing fragment index: ${subAsset}`);
|
|
209
|
+
const createdFile = await createISOBMFFFile(getClient(), {
|
|
210
|
+
id: asset,
|
|
211
|
+
filename: subAsset.replace(/\.tracks.json$/, "")
|
|
212
|
+
});
|
|
213
|
+
if (createdFile) {
|
|
214
|
+
if (createdFile.fragment_index_complete) {
|
|
215
|
+
reportInfo(subAsset, "✔ Fragment index has already been synced.");
|
|
216
|
+
await syncStatus.markSynced();
|
|
217
|
+
} else {
|
|
218
|
+
const stats = statSync(subAssetPath);
|
|
219
|
+
const readStream = createReadStream(subAssetPath);
|
|
220
|
+
await uploadFragmentIndex(
|
|
221
|
+
getClient(),
|
|
222
|
+
asset,
|
|
223
|
+
readStream,
|
|
224
|
+
stats.size
|
|
225
|
+
).then(() => {
|
|
226
|
+
reportSuccess(subAsset, "Fragment index has been synced.");
|
|
227
|
+
return syncStatus.markSynced();
|
|
228
|
+
}).catch((error) => {
|
|
229
|
+
reportError(
|
|
230
|
+
subAsset,
|
|
231
|
+
`Error uploading fragment index: ${error.message}`
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
reportError(
|
|
237
|
+
subAsset,
|
|
238
|
+
`No file found for fragment index: ${subAsset}`
|
|
239
|
+
);
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case captionsMatch.test(subAsset): {
|
|
245
|
+
reportInfo(subAsset, `📝 Syncing captions: ${subAsset}`);
|
|
246
|
+
const createdFile = await createCaptionFile(getClient(), {
|
|
247
|
+
id: asset,
|
|
248
|
+
filename: subAsset.replace(/\.captions.json$/, ""),
|
|
249
|
+
byte_size: (await fs.stat(subAsset)).size
|
|
250
|
+
});
|
|
251
|
+
if (createdFile) {
|
|
252
|
+
if (createdFile.complete) {
|
|
253
|
+
reportInfo(subAsset, "✔ Captions have already been synced.");
|
|
254
|
+
await syncStatus.markSynced();
|
|
255
|
+
} else {
|
|
256
|
+
const readStream = createReadStream(subAssetPath);
|
|
257
|
+
const stats = statSync(subAssetPath);
|
|
258
|
+
await uploadCaptionFile(
|
|
259
|
+
getClient(),
|
|
260
|
+
asset,
|
|
261
|
+
readStream,
|
|
262
|
+
stats.size
|
|
263
|
+
).then(() => {
|
|
264
|
+
reportSuccess(subAsset, "Captions have been synced.");
|
|
265
|
+
return syncStatus.markSynced();
|
|
266
|
+
}).catch((error) => {
|
|
267
|
+
reportError(
|
|
268
|
+
subAsset,
|
|
269
|
+
`Error uploading captions: ${error.message}`
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
reportError(subAsset, `No file found for captions: ${subAsset}`);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
default: {
|
|
280
|
+
reportError(subAsset, `Unknown sub-asset: ${subAsset}`);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (Object.keys(errors).length) {
|
|
287
|
+
process.stderr.write("\n\n❌ Encountered errors while syncing assets:\n");
|
|
288
|
+
for (const [asset, messages] of Object.entries(errors)) {
|
|
289
|
+
process.stderr.write(`
|
|
290
|
+
🚫 ${asset}
|
|
291
|
+
`);
|
|
292
|
+
for (const message of messages) {
|
|
293
|
+
process.stderr.write(` - ${message}
|
|
294
|
+
`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
throw new Error("Failed to sync assets");
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
export {
|
|
301
|
+
syncAssetDirectory
|
|
302
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getFolderSize: (folderPath: string) => Promise<number>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { promises } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const getFolderSize = async (folderPath) => {
|
|
4
|
+
let totalSize = 0;
|
|
5
|
+
async function calculateSize(dir) {
|
|
6
|
+
const files = await promises.readdir(dir);
|
|
7
|
+
for (const file of files) {
|
|
8
|
+
const filePath = path.join(dir, file);
|
|
9
|
+
const stats = await promises.stat(filePath);
|
|
10
|
+
if (stats.isDirectory()) {
|
|
11
|
+
await calculateSize(filePath);
|
|
12
|
+
} else {
|
|
13
|
+
totalSize += stats.size;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
await calculateSize(folderPath);
|
|
18
|
+
return totalSize;
|
|
19
|
+
};
|
|
20
|
+
export {
|
|
21
|
+
getFolderSize
|
|
22
|
+
};
|
|
@@ -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,9 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
interface LaunchOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
headless?: boolean;
|
|
5
|
+
interactive?: boolean;
|
|
6
|
+
efInteractive?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function launchBrowserAndWaitForSDK(options: LaunchOptions, fn: (page: Page) => Promise<void>): Promise<void>;
|
|
9
|
+
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.10",
|
|
4
4
|
"description": "Command line interface for EditFrame",
|
|
5
5
|
"bin": {
|
|
6
6
|
"editframe": "./dist/index.js"
|
|
@@ -15,17 +15,19 @@
|
|
|
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
|
-
"
|
|
21
|
+
"typescript": "^5.5.4",
|
|
22
|
+
"vite-plugin-dts": "^4.0.3",
|
|
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.10",
|
|
27
|
+
"@editframe/assets": "0.8.0-beta.10",
|
|
28
|
+
"@editframe/elements": "0.8.0-beta.10",
|
|
29
|
+
"@editframe/vite-plugin": "0.8.0-beta.10",
|
|
30
|
+
"@inquirer/prompts": "^5.3.8",
|
|
29
31
|
"axios": "^1.6.8",
|
|
30
32
|
"chalk": "^5.3.0",
|
|
31
33
|
"commander": "^12.0.0",
|
|
@@ -41,7 +43,6 @@
|
|
|
41
43
|
"promptly": "^3.2.0",
|
|
42
44
|
"tailwindcss": "^3.4.3",
|
|
43
45
|
"tar": "^7.1.0",
|
|
44
|
-
"typescript": "^5.2.2",
|
|
45
46
|
"vite": "^5.2.11",
|
|
46
47
|
"zod": "^3.23.8"
|
|
47
48
|
}
|
package/src/commands/auth.ts
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { createReadStream } from "node:fs";
|
|
4
|
+
|
|
5
|
+
import { program } from "commander";
|
|
6
|
+
|
|
7
|
+
import { withSpinner } from "../utils/withSpinner.ts";
|
|
8
|
+
|
|
9
|
+
import { getClient } from "../utils/index.ts";
|
|
10
|
+
import {
|
|
11
|
+
createUnprocessedFile,
|
|
12
|
+
updateUnprocessedFile,
|
|
13
|
+
uploadUnprocessedFile,
|
|
14
|
+
} from "@editframe/api";
|
|
15
|
+
import { md5FilePath } from "@editframe/assets";
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.command("process-file <file>")
|
|
19
|
+
.description("Upload a audio/video to Editframe for processing.")
|
|
20
|
+
.action(async (path: string) => {
|
|
21
|
+
const client = getClient();
|
|
22
|
+
|
|
23
|
+
const fileId = await md5FilePath(path);
|
|
24
|
+
|
|
25
|
+
const byte_size = (await stat(path)).size;
|
|
26
|
+
|
|
27
|
+
await withSpinner("Creating unprocessed file record", async () => {
|
|
28
|
+
await createUnprocessedFile(client, {
|
|
29
|
+
id: fileId,
|
|
30
|
+
processes: [],
|
|
31
|
+
filename: basename(path),
|
|
32
|
+
byte_size,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const readStream = createReadStream(path);
|
|
37
|
+
|
|
38
|
+
await withSpinner("Uploading file", async () => {
|
|
39
|
+
await uploadUnprocessedFile(client, fileId, readStream, byte_size);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const unprocessedFile = await withSpinner(
|
|
43
|
+
"Marking for processing",
|
|
44
|
+
async () => {
|
|
45
|
+
return await updateUnprocessedFile(client, fileId, {
|
|
46
|
+
processes: ["isobmff"],
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
process.stderr.write("File uploaded and marked for processing.\n");
|
|
52
|
+
|
|
53
|
+
console.log(JSON.stringify(unprocessedFile, null, 2));
|
|
54
|
+
});
|
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]")
|