@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.
Files changed (61) hide show
  1. package/dist/VERSION.d.ts +1 -0
  2. package/dist/VERSION.js +4 -0
  3. package/dist/commands/auth.d.ts +9 -0
  4. package/dist/commands/auth.js +33 -0
  5. package/dist/commands/check.d.ts +1 -0
  6. package/dist/commands/check.js +114 -0
  7. package/dist/commands/preview.d.ts +1 -0
  8. package/dist/commands/preview.js +5 -0
  9. package/dist/commands/process-file.d.ts +1 -0
  10. package/dist/commands/process-file.js +35 -0
  11. package/dist/commands/process.d.ts +1 -0
  12. package/dist/commands/process.js +35 -0
  13. package/dist/commands/render.d.ts +1 -0
  14. package/dist/commands/render.js +153 -0
  15. package/dist/commands/sync.d.ts +1 -0
  16. package/dist/commands/sync.js +5 -0
  17. package/dist/commands/webhook.d.ts +7 -0
  18. package/dist/commands/webhook.js +61 -0
  19. package/dist/index.d.ts +0 -0
  20. package/dist/index.js +18 -0
  21. package/dist/operations/getRenderInfo.d.ts +15 -0
  22. package/dist/operations/getRenderInfo.js +59 -0
  23. package/dist/operations/processRenderInfo.d.ts +2 -0
  24. package/dist/operations/processRenderInfo.js +30 -0
  25. package/dist/operations/syncAssetsDirectory.d.ts +1 -0
  26. package/dist/operations/syncAssetsDirectory.js +302 -0
  27. package/dist/utils/attachWorkbench.d.ts +2 -0
  28. package/dist/utils/getFolderSize.d.ts +1 -0
  29. package/dist/utils/getFolderSize.js +22 -0
  30. package/dist/utils/index.d.ts +2 -0
  31. package/dist/utils/index.js +14 -0
  32. package/dist/utils/launchBrowserAndWaitForSDK.d.ts +9 -0
  33. package/dist/utils/launchBrowserAndWaitForSDK.js +49 -0
  34. package/dist/utils/startDevServer.d.ts +7 -0
  35. package/dist/utils/startPreviewServer.d.ts +7 -0
  36. package/dist/utils/startPreviewServer.js +38 -0
  37. package/dist/utils/validateVideoResolution.d.ts +9 -0
  38. package/dist/utils/validateVideoResolution.js +29 -0
  39. package/dist/utils/withSpinner.d.ts +1 -0
  40. package/dist/utils/withSpinner.js +15 -0
  41. package/package.json +9 -8
  42. package/src/commands/auth.ts +1 -1
  43. package/src/commands/process-file.ts +54 -0
  44. package/src/commands/process.ts +5 -5
  45. package/src/commands/render.ts +19 -10
  46. package/src/commands/sync.ts +1 -1
  47. package/src/commands/webhook.ts +76 -0
  48. package/src/operations/processRenderInfo.ts +1 -1
  49. package/src/operations/syncAssetsDirectory.ts +123 -50
  50. package/src/utils/getFolderSize.ts +24 -0
  51. package/src/utils/launchBrowserAndWaitForSDK.ts +1 -1
  52. package/src/utils/startDevServer.ts +1 -1
  53. package/src/utils/startPreviewServer.ts +1 -1
  54. package/src/utils/validateVideoResolution.ts +36 -0
  55. package/.env +0 -1
  56. package/.env.example +0 -1
  57. package/CHANGELOG.md +0 -8
  58. package/src/VERSION.ts +0 -1
  59. package/src/index.ts +0 -29
  60. package/tsconfig.json +0 -5
  61. 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,2 @@
1
+ import { Page } from 'playwright';
2
+ export declare const attachWorkbench: (page: Page) => void;
@@ -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,2 @@
1
+ import { Client } from '../../../api/src';
2
+ export declare const getClient: () => Client;
@@ -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,7 @@
1
+ import { ViteDevServer } from 'vite';
2
+ export declare class DevServer {
3
+ private devServer;
4
+ static start(directory: string): Promise<DevServer>;
5
+ constructor(devServer: ViteDevServer);
6
+ get url(): string;
7
+ }
@@ -0,0 +1,7 @@
1
+ import { ViteDevServer } from 'vite';
2
+ export declare class PreviewServer {
3
+ private previewServer;
4
+ static start(directory: string): Promise<PreviewServer>;
5
+ constructor(previewServer: ViteDevServer);
6
+ get url(): string;
7
+ }
@@ -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,9 @@
1
+ type VideoPayload = {
2
+ width: number;
3
+ height: number;
4
+ };
5
+ export declare const validateVideoResolution: (rawPayload: VideoPayload) => Promise<{
6
+ height: number;
7
+ width: number;
8
+ }>;
9
+ export {};
@@ -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.7.0-beta.9",
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.9",
18
+ "@types/node": "^20.14.13",
19
19
  "@types/promptly": "^3.0.5",
20
20
  "@types/tar": "^6.1.13",
21
- "vite-plugin-dts": "^3.9.1",
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.7.0-beta.8",
26
- "@editframe/assets": "0.7.0-beta.8",
27
- "@editframe/elements": "0.7.0-beta.9",
28
- "@editframe/vite-plugin": "0.7.0-beta.8",
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
  }
@@ -3,7 +3,7 @@ import ora from "ora";
3
3
  import chalk from "chalk";
4
4
  import debug from "debug";
5
5
 
6
- import { getClient } from "../utils";
6
+ import { getClient } from "../utils/index.ts";
7
7
 
8
8
  const log = debug("ef:cli:auth");
9
9
 
@@ -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
+ });
@@ -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]")