@editframe/cli 0.7.0-beta.9 → 0.8.0-beta.1

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 (55) 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 +32 -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 +151 -0
  15. package/dist/commands/sync.d.ts +1 -0
  16. package/dist/commands/sync.js +5 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +17 -0
  19. package/dist/operations/getRenderInfo.d.ts +15 -0
  20. package/dist/operations/getRenderInfo.js +59 -0
  21. package/dist/operations/processRenderInfo.d.ts +3 -0
  22. package/dist/operations/processRenderInfo.js +30 -0
  23. package/dist/operations/syncAssetsDirectory.d.ts +1 -0
  24. package/dist/operations/syncAssetsDirectory.js +250 -0
  25. package/dist/utils/attachWorkbench.d.ts +3 -0
  26. package/dist/utils/index.d.ts +3 -0
  27. package/dist/utils/index.js +14 -0
  28. package/dist/utils/launchBrowserAndWaitForSDK.d.ts +10 -0
  29. package/dist/utils/launchBrowserAndWaitForSDK.js +49 -0
  30. package/dist/utils/startDevServer.d.ts +8 -0
  31. package/dist/utils/startPreviewServer.d.ts +8 -0
  32. package/dist/utils/startPreviewServer.js +38 -0
  33. package/dist/utils/validateVideoResolution.d.ts +9 -0
  34. package/dist/utils/validateVideoResolution.js +29 -0
  35. package/dist/utils/withSpinner.d.ts +1 -0
  36. package/dist/utils/withSpinner.js +15 -0
  37. package/package.json +7 -7
  38. package/src/commands/auth.ts +1 -1
  39. package/src/commands/process-file.ts +50 -0
  40. package/src/commands/process.ts +5 -5
  41. package/src/commands/render.ts +16 -9
  42. package/src/commands/sync.ts +1 -1
  43. package/src/operations/processRenderInfo.ts +1 -1
  44. package/src/operations/syncAssetsDirectory.ts +1 -1
  45. package/src/utils/launchBrowserAndWaitForSDK.ts +1 -1
  46. package/src/utils/startDevServer.ts +1 -1
  47. package/src/utils/startPreviewServer.ts +1 -1
  48. package/src/utils/validateVideoResolution.ts +36 -0
  49. package/.env +0 -1
  50. package/.env.example +0 -1
  51. package/CHANGELOG.md +0 -8
  52. package/src/VERSION.ts +0 -1
  53. package/src/index.ts +0 -29
  54. package/tsconfig.json +0 -5
  55. package/vite.config.ts +0 -22
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.8.0-beta.1";
@@ -0,0 +1,4 @@
1
+ const VERSION = "0.8.0-beta.1";
2
+ export {
3
+ VERSION
4
+ };
@@ -0,0 +1,9 @@
1
+ export interface APIOrgResult {
2
+ name: string;
3
+ id: string;
4
+ org_id: string;
5
+ created_at: unknown;
6
+ updated_at: unknown;
7
+ org_display_name: string;
8
+ }
9
+ export declare const getApiData: () => Promise<APIOrgResult>;
@@ -0,0 +1,33 @@
1
+ import { program } from "commander";
2
+ import ora from "ora";
3
+ import chalk from "chalk";
4
+ import debug from "debug";
5
+ import { getClient } from "../utils/index.js";
6
+ const log = debug("ef:cli:auth");
7
+ const getApiData = async () => {
8
+ const response = await getClient().authenticatedFetch("/api/org");
9
+ return response.json();
10
+ };
11
+ const authCommand = program.command("auth").description("Fetch organization data using API token").action(async () => {
12
+ const options = authCommand.opts();
13
+ log("Options:", options);
14
+ const spinner = ora("Loading...").start();
15
+ try {
16
+ const apiData = await getApiData();
17
+ spinner.succeed("You are authenticated! 🎉");
18
+ process.stderr.write(chalk.green(`Name: ${apiData.name}
19
+ `));
20
+ process.stderr.write(
21
+ chalk.green(`Welcome to ${apiData.org_display_name}!
22
+ `)
23
+ );
24
+ } catch (error) {
25
+ spinner.fail("Authentication failed!");
26
+ process.stderr.write(error?.message);
27
+ process.stderr.write("\n");
28
+ log("Error:", error);
29
+ }
30
+ });
31
+ export {
32
+ getApiData
33
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,114 @@
1
+ import { exec } from "node:child_process";
2
+ import os from "node:os";
3
+ import { program } from "commander";
4
+ import ora from "ora";
5
+ import chalk from "chalk";
6
+ const checks = {
7
+ ffmpeg: {
8
+ message: () => {
9
+ const platform = os.platform();
10
+ const message = [
11
+ "Processing assets for <ef-video>, <ef-audio>, <ef-captions>, and <ef-waveform>\n elements requires ffmpeg to be installed."
12
+ ];
13
+ switch (platform) {
14
+ case "darwin": {
15
+ message.push(
16
+ "On platform=darwin you can install ffmpeg using Homebrew:"
17
+ );
18
+ message.push(" - brew install ffmpeg");
19
+ message.push(
20
+ "Or you can download ffmpeg from https://ffmpeg.org/download.html"
21
+ );
22
+ break;
23
+ }
24
+ case "linux": {
25
+ message.push(
26
+ "You can install ffmpeg using your distribution's package manager."
27
+ );
28
+ break;
29
+ }
30
+ case "win32": {
31
+ message.push(
32
+ "You can download ffmpeg from https://ffmpeg.org/download.html"
33
+ );
34
+ message.push(
35
+ "You can use package managers like Chocolatey or Scoop to install ffmpeg."
36
+ );
37
+ message.push(" - choco install ffmpeg-full");
38
+ message.push(" - scoop install ffmpeg");
39
+ message.push(" - winget install ffmpeg");
40
+ break;
41
+ }
42
+ default: {
43
+ message.push(`Unrecognized platform ${platform}`);
44
+ message.push(
45
+ "You can download ffmpeg from https://ffmpeg.org/download.html"
46
+ );
47
+ message.push(
48
+ "Or try installing it from your operating system's package manager"
49
+ );
50
+ break;
51
+ }
52
+ }
53
+ return message;
54
+ },
55
+ check: async () => {
56
+ return new Promise((resolve, reject) => {
57
+ exec("ffmpeg -version", (error, stdout, _stderr) => {
58
+ if (error) {
59
+ reject(error);
60
+ return;
61
+ }
62
+ resolve(stdout);
63
+ });
64
+ });
65
+ }
66
+ },
67
+ whisper_timestamped: {
68
+ message: () => {
69
+ const message = [
70
+ "<ef-captions> Requires whisper_timestamped to be installed."
71
+ ];
72
+ message.push("whisper_timestamped depends on python3");
73
+ message.push(" - pip3 install whisper_timestamped");
74
+ message.push("Alternate installation instructions are availble at:");
75
+ message.push(
76
+ "https://github.com/linto-ai/whisper-timestamped#installation"
77
+ );
78
+ return message;
79
+ },
80
+ check: async () => {
81
+ return new Promise((resolve, reject) => {
82
+ exec(
83
+ "whisper_timestamped --version",
84
+ (error, stdout, _stderr) => {
85
+ if (error) {
86
+ reject(error);
87
+ return;
88
+ }
89
+ resolve(stdout);
90
+ }
91
+ );
92
+ });
93
+ }
94
+ }
95
+ };
96
+ program.command("check").description("Check on dependencies and other requirements").action(async () => {
97
+ for (const checkName in checks) {
98
+ const check = checks[checkName];
99
+ if (!check) {
100
+ continue;
101
+ }
102
+ const spinner = ora(`Checking ${checkName}`).start();
103
+ try {
104
+ await check.check();
105
+ spinner.succeed(
106
+ chalk.white.bgGreen(` Check for ${checkName} passed `)
107
+ );
108
+ } catch (error) {
109
+ spinner.fail(chalk.white.bgRed(` Check for ${checkName} failed `));
110
+ process.stderr.write(chalk.red(check.message().join("\n\n")));
111
+ process.stderr.write("\n");
112
+ }
113
+ }
114
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { program } from "commander";
2
+ import { spawn } from "node:child_process";
3
+ program.command("preview [directory]").description("Preview a directory's index.html file").action(async (projectDirectory = ".") => {
4
+ spawn("npx", ["vite", "dev"], { cwd: projectDirectory, stdio: "inherit" });
5
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import { basename } from "node:path";
2
+ import { program } from "commander";
3
+ import { withSpinner } from "../utils/withSpinner.js";
4
+ import { getClient } from "../utils/index.js";
5
+ import { createReadStream } from "node:fs";
6
+ import { createUnprocessedFile, uploadUnprocessedFile, updateUnprocessedFile } from "@editframe/api";
7
+ import { md5FilePath } from "@editframe/assets";
8
+ program.command("process-file <file>").description("Upload a audio/video to Editframe for processing.").action(async (path) => {
9
+ const client = getClient();
10
+ const fileId = await md5FilePath(path);
11
+ await withSpinner("Creating unprocessed file record", async () => {
12
+ await createUnprocessedFile(client, {
13
+ id: fileId,
14
+ processes: [],
15
+ filename: basename(path)
16
+ });
17
+ });
18
+ const readStream = createReadStream(path);
19
+ await withSpinner("Uploading file", async () => {
20
+ await uploadUnprocessedFile(client, fileId, readStream);
21
+ });
22
+ const unprocessedFile = await withSpinner(
23
+ "Marking for processing",
24
+ async () => {
25
+ return await updateUnprocessedFile(client, fileId, {
26
+ processes: ["isobmff"]
27
+ });
28
+ }
29
+ );
30
+ process.stderr.write("File uploaded and marked for processing.\n");
31
+ console.log(JSON.stringify(unprocessedFile, null, 2));
32
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import path from "node:path";
3
+ import { program } from "commander";
4
+ import { withSpinner } from "../utils/withSpinner.js";
5
+ import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK.js";
6
+ import { PreviewServer } from "../utils/startPreviewServer.js";
7
+ import { getRenderInfo } from "../operations/getRenderInfo.js";
8
+ import { processRenderInfo } from "../operations/processRenderInfo.js";
9
+ program.command("process [directory]").description(
10
+ "Process's a directory's index.html file, analyzing assets and processing them for rendering"
11
+ ).action(async (directory) => {
12
+ directory ??= ".";
13
+ const distDir = path.join(directory, "dist");
14
+ await withSpinner("Building\n", async () => {
15
+ spawnSync("npx", ["vite", "build", directory], {
16
+ stdio: "inherit"
17
+ });
18
+ });
19
+ const previewServer = await PreviewServer.start(distDir);
20
+ process.stderr.write("Preview server started at ");
21
+ process.stderr.write(previewServer.url);
22
+ process.stderr.write("\n");
23
+ await launchBrowserAndWaitForSDK(
24
+ {
25
+ url: previewServer.url,
26
+ efInteractive: false,
27
+ interactive: false,
28
+ headless: true
29
+ },
30
+ async (page) => {
31
+ const renderInfo = await page.evaluate(getRenderInfo);
32
+ await processRenderInfo(renderInfo);
33
+ }
34
+ );
35
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,151 @@
1
+ import path from "node:path";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { inspect } from "node:util";
4
+ import { PassThrough } from "node:stream";
5
+ import * as tar from "tar";
6
+ import { program, Option } from "commander";
7
+ import { parse } from "node-html-parser";
8
+ import { build } from "vite";
9
+ import { md5Directory, md5FilePath } from "@editframe/assets";
10
+ import { withSpinner } from "../utils/withSpinner.js";
11
+ import { syncAssetDirectory } from "../operations/syncAssetsDirectory.js";
12
+ import { createRender, uploadRender } from "@editframe/api";
13
+ import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK.js";
14
+ 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
+ import { validateVideoResolution } from "../utils/validateVideoResolution.js";
19
+ const buildProductionUrl = async (origin, tagName, assetPath) => {
20
+ const md5Sum = await md5FilePath(assetPath);
21
+ const basename = path.basename(assetPath);
22
+ switch (tagName) {
23
+ case "ef-audio":
24
+ case "ef-video": {
25
+ return `${origin}/api/video2/isobmff_files/${md5Sum}/${basename}`;
26
+ }
27
+ case "ef-captions": {
28
+ return `${origin}/api/video2/caption_files/${md5Sum}/${basename}`;
29
+ }
30
+ case "ef-image": {
31
+ return `${origin}/api/video2/image_files/${md5Sum}/${basename}`;
32
+ }
33
+ default: {
34
+ return assetPath;
35
+ }
36
+ }
37
+ };
38
+ class V1Builder {
39
+ async buildProductionUrl(tagName, assetPath) {
40
+ return buildProductionUrl("editframe://", tagName, assetPath);
41
+ }
42
+ }
43
+ class V2Builder {
44
+ async buildProductionUrl(tagName, assetPath) {
45
+ const efRenderHost = program.opts().efRenderHost;
46
+ return buildProductionUrl(efRenderHost, tagName, assetPath);
47
+ }
48
+ }
49
+ const strategyBuilders = {
50
+ v1: new V1Builder(),
51
+ v2: new V2Builder()
52
+ };
53
+ program.command("render [directory]").description(
54
+ "Render a directory's index.html file as a video in the editframe cloud"
55
+ ).addOption(
56
+ new Option("-s, --strategy <strategy>", "Render strategy").choices(["v1", "v2"]).default("v1")
57
+ ).action(async (directory, options) => {
58
+ directory ??= ".";
59
+ await syncAssetDirectory(directory);
60
+ const srcDir = path.join(directory, "src");
61
+ const distDir = path.join(directory, "dist");
62
+ const builder = strategyBuilders[options.strategy];
63
+ if (!builder) {
64
+ console.error("Invalid strategy", options.strategy);
65
+ return;
66
+ }
67
+ await withSpinner("Building\n", async () => {
68
+ try {
69
+ await build({
70
+ root: directory,
71
+ logLevel: "info",
72
+ // Optional: adjust log level as needed
73
+ clearScreen: false
74
+ // Optional: keep console output clean
75
+ });
76
+ } catch (error) {
77
+ console.error("Build failed:", error);
78
+ }
79
+ });
80
+ const previewServer = await PreviewServer.start(distDir);
81
+ process.stderr.write("Preview server started at:");
82
+ process.stderr.write(previewServer.url);
83
+ process.stderr.write("\n");
84
+ await launchBrowserAndWaitForSDK(
85
+ {
86
+ url: previewServer.url,
87
+ efInteractive: false,
88
+ interactive: false,
89
+ headless: true
90
+ },
91
+ async (page) => {
92
+ const renderInfo = await page.evaluate(getRenderInfo);
93
+ validateVideoResolution({
94
+ width: renderInfo.width,
95
+ height: renderInfo.height
96
+ });
97
+ await processRenderInfo(renderInfo);
98
+ const doc = parse(
99
+ await readFile(path.join(distDir, "index.html"), "utf-8")
100
+ );
101
+ const elements = doc.querySelectorAll(
102
+ "ef-audio, ef-video, ef-image, ef-captions"
103
+ );
104
+ for (const element of elements) {
105
+ const src = element.getAttribute("src");
106
+ if (!src) {
107
+ continue;
108
+ }
109
+ const assetPath = path.join(srcDir, src);
110
+ element.setAttribute(
111
+ "src",
112
+ await builder.buildProductionUrl(
113
+ element.tagName.toLowerCase(),
114
+ assetPath
115
+ )
116
+ );
117
+ }
118
+ await writeFile(path.join(distDir, "index.html"), doc.toString());
119
+ const md5 = await md5Directory(distDir);
120
+ const render = await createRender(getClient(), {
121
+ id: md5,
122
+ width: renderInfo.width,
123
+ height: renderInfo.height,
124
+ fps: renderInfo.fps,
125
+ duration_ms: renderInfo.durationMs,
126
+ work_slice_ms: 4e3,
127
+ strategy: options.strategy
128
+ });
129
+ if (render?.status !== "created") {
130
+ process.stderr.write(
131
+ `Render is in '${render?.status}' status. It cannot be recreated while in this status.
132
+ `
133
+ );
134
+ return;
135
+ }
136
+ const tarStream = tar.create(
137
+ {
138
+ gzip: true,
139
+ cwd: distDir
140
+ },
141
+ ["."]
142
+ );
143
+ const readable = new PassThrough();
144
+ tarStream.pipe(readable);
145
+ await uploadRender(getClient(), md5, readable);
146
+ process.stderr.write("Render assets uploaded\n");
147
+ process.stderr.write(inspect(render));
148
+ process.stderr.write("\n");
149
+ }
150
+ );
151
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { program } from "commander";
2
+ 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 (projectDirectory = ".") => {
4
+ await syncAssetDirectory(projectDirectory);
5
+ });
@@ -0,0 +1 @@
1
+
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { program, Option } from "commander";
4
+ import { VERSION } from "./VERSION.js";
5
+ import "./commands/auth.js";
6
+ import "./commands/sync.js";
7
+ import "./commands/render.js";
8
+ import "./commands/preview.js";
9
+ import "./commands/process.js";
10
+ import "./commands/process-file.js";
11
+ import "./commands/check.js";
12
+ program.name("editframe").addOption(new Option("-t, --token <token>", "API Token").env("EF_TOKEN")).addOption(
13
+ new Option("--ef-host <host>", "Editframe Host").env("EF_HOST").default("https://editframe.dev")
14
+ ).addOption(
15
+ new Option("--ef-render-host <host>", "Editframe Render Host").env("EF_RENDER_HOST").default("https://editframe.dev")
16
+ ).version(VERSION);
17
+ program.parse(process.argv);
@@ -0,0 +1,15 @@
1
+ /**
2
+ * THIS MODULE DOESNT USE THE DEBUG LOGGER BECAUSE IT IS
3
+ * RUN IN A WEB BROWSER. LOGS ARE CAPTURED AND RE-LOGGED.
4
+ */
5
+ export declare const getRenderInfo: () => Promise<{
6
+ width: number;
7
+ height: number;
8
+ fps: number;
9
+ durationMs: number;
10
+ assets: {
11
+ efMedia: Record<string, any>;
12
+ efCaptions: string[];
13
+ efImage: string[];
14
+ };
15
+ }>;
@@ -0,0 +1,59 @@
1
+ const getRenderInfo = async () => {
2
+ const rootTimeGroup = document.querySelector("ef-timegroup");
3
+ if (!rootTimeGroup) {
4
+ throw new Error("No ef-timegroup found");
5
+ }
6
+ console.error("Waiting for media durations", rootTimeGroup);
7
+ await rootTimeGroup.waitForMediaDurations();
8
+ const width = rootTimeGroup.clientWidth;
9
+ const height = rootTimeGroup.clientHeight;
10
+ const fps = 30;
11
+ const durationMs = Math.round(rootTimeGroup.durationMs);
12
+ const elements = document.querySelectorAll(
13
+ "ef-audio, ef-video, ef-image, ef-captions"
14
+ );
15
+ const assets = {
16
+ efMedia: {},
17
+ efCaptions: /* @__PURE__ */ new Set(),
18
+ efImage: /* @__PURE__ */ new Set()
19
+ };
20
+ for (const element of elements) {
21
+ switch (element.tagName) {
22
+ case "EF-AUDIO":
23
+ case "EF-VIDEO": {
24
+ const src = element.src;
25
+ console.error("Processing element", element.tagName, src);
26
+ assets.efMedia[src] = element.trackFragmentIndexLoader.value;
27
+ break;
28
+ }
29
+ case "EF-IMAGE": {
30
+ const src = element.src;
31
+ console.error("Processing element", element.tagName, src);
32
+ assets.efImage.add(src);
33
+ break;
34
+ }
35
+ case "EF-CAPTIONS": {
36
+ const src = element.targetElement?.src;
37
+ console.error("Processing element", element.tagName, src);
38
+ assets.efCaptions.add(src);
39
+ break;
40
+ }
41
+ }
42
+ }
43
+ const renderInfo = {
44
+ width,
45
+ height,
46
+ fps,
47
+ durationMs,
48
+ assets: {
49
+ efMedia: assets.efMedia,
50
+ efCaptions: Array.from(assets.efCaptions),
51
+ efImage: Array.from(assets.efImage)
52
+ }
53
+ };
54
+ console.error("Render info", renderInfo);
55
+ return renderInfo;
56
+ };
57
+ export {
58
+ getRenderInfo
59
+ };
@@ -0,0 +1,3 @@
1
+ import { getRenderInfo } from './getRenderInfo.ts';
2
+
3
+ export declare const processRenderInfo: (renderInfo: Awaited<ReturnType<typeof getRenderInfo>>) => Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { generateTrack, cacheImage, findOrCreateCaptions } from "@editframe/assets";
2
+ const processRenderInfo = async (renderInfo) => {
3
+ for (const [src, tracks] of Object.entries(renderInfo.assets.efMedia)) {
4
+ process.stderr.write("Processing media asset: ");
5
+ process.stderr.write(src);
6
+ process.stderr.write("\n");
7
+ for (const trackId in tracks) {
8
+ await generateTrack(
9
+ "./src/assets",
10
+ `./src${src}`,
11
+ `src?trackId=${trackId}`
12
+ );
13
+ }
14
+ }
15
+ for (const imageAsset of renderInfo.assets.efImage) {
16
+ process.stderr.write("Processing image asset: ");
17
+ process.stderr.write(imageAsset);
18
+ process.stderr.write("\n");
19
+ await cacheImage("./src/assets", `./src${imageAsset}`);
20
+ }
21
+ for (const captionsAsset of renderInfo.assets.efCaptions) {
22
+ process.stderr.write("Processing captions asset: ");
23
+ process.stderr.write(captionsAsset);
24
+ process.stderr.write("\n");
25
+ await findOrCreateCaptions("./src/assets", `./src${captionsAsset}`);
26
+ }
27
+ };
28
+ export {
29
+ processRenderInfo
30
+ };
@@ -0,0 +1 @@
1
+ export declare const syncAssetDirectory: (projectDirectory: string) => Promise<void>;