@editframe/cli 0.45.2 → 0.45.3
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.js +1 -1
- package/dist/VERSION.js.map +1 -1
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/cloud-render.js.map +1 -1
- package/dist/commands/process-file.js.map +1 -1
- package/dist/commands/render.js.map +1 -1
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/transcribe.js.map +1 -1
- package/dist/commands/webhook.js.map +1 -1
- package/dist/operations/processRenderInfo.js.map +1 -1
- package/dist/operations/syncAssetsDirectory/SyncCaption.js.map +1 -1
- package/dist/operations/syncAssetsDirectory/SyncFragmentIndex.js.map +1 -1
- package/dist/operations/syncAssetsDirectory/SyncImage.js.map +1 -1
- package/dist/operations/syncAssetsDirectory/SyncStatus.js.map +1 -1
- package/dist/operations/syncAssetsDirectory/SyncTrack.js.map +1 -1
- package/dist/operations/syncAssetsDirectory/doAssetSync.js.map +1 -1
- package/dist/utils/createReadableStreamFromReadable.js.map +1 -1
- package/dist/utils/detectChrome.js.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/launchBrowserAndWaitForSDK.js.map +1 -1
- package/dist/utils/patchFragmentedMp4.js.map +1 -1
- package/dist/utils/profileRender.js.map +1 -1
- package/dist/utils/spawnViteServer.js.map +1 -1
- package/package.json +1 -1
- package/test-fixtures/fixture.ts +4 -19
- package/test-fixtures/network.ts +41 -78
package/dist/VERSION.js
CHANGED
package/dist/VERSION.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VERSION.js","names":[],"sources":["../src/VERSION.ts"],"sourcesContent":["export const VERSION = \"0.45.
|
|
1
|
+
{"version":3,"file":"VERSION.js","names":[],"sources":["../src/VERSION.ts"],"sourcesContent":["export const VERSION = \"0.45.3\";\n"],"mappings":";AAAA,MAAa,UAAU"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","names":["error: any"],"sources":["../../src/commands/auth.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { program } from \"commander\";\nimport debug from \"debug\";\nimport ora from \"ora\";\n\nimport { getClient } from \"../utils/index.js\";\n\nconst log = debug(\"ef:cli:auth\");\n\nexport interface APIOrgResult {\n apiKeyName: string;\n id: string;\n org_id: string;\n created_at: unknown;\n updated_at: unknown;\n displayName: string;\n}\n\nexport const getApiData = async () => {\n const response = await getClient().authenticatedFetch(\"/api/v1/organization\");\n return response.json() as Promise<APIOrgResult>;\n};\n\nconst authCommand = program\n .command(\"auth\")\n .description(\"Fetch organization data using API token\")\n .action(async () => {\n const options = authCommand.opts();\n log(\"Options:\", options);\n\n const spinner = ora(\"Loading...\").start();\n\n try {\n const apiData = await getApiData();\n spinner.succeed(\"You are authenticated! 🎉\");\n process.stderr.write(
|
|
1
|
+
{"version":3,"file":"auth.js","names":["error: any"],"sources":["../../src/commands/auth.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { program } from \"commander\";\nimport debug from \"debug\";\nimport ora from \"ora\";\n\nimport { getClient } from \"../utils/index.js\";\n\nconst log = debug(\"ef:cli:auth\");\n\nexport interface APIOrgResult {\n apiKeyName: string;\n id: string;\n org_id: string;\n created_at: unknown;\n updated_at: unknown;\n displayName: string;\n}\n\nexport const getApiData = async () => {\n const response = await getClient().authenticatedFetch(\"/api/v1/organization\");\n return response.json() as Promise<APIOrgResult>;\n};\n\nconst authCommand = program\n .command(\"auth\")\n .description(\"Fetch organization data using API token\")\n .action(async () => {\n const options = authCommand.opts();\n log(\"Options:\", options);\n\n const spinner = ora(\"Loading...\").start();\n\n try {\n const apiData = await getApiData();\n spinner.succeed(\"You are authenticated! 🎉\");\n process.stderr.write(chalk.green(`You're using ${apiData.apiKeyName} API key 🚀\\n`));\n process.stderr.write(chalk.blue(`Welcome to ${apiData.displayName} organization 🎉\\n`));\n } catch (error: any) {\n spinner.fail(\"Authentication failed!\");\n log(\"Error:\", error);\n }\n });\n"],"mappings":";;;;;;;AAOA,MAAM,MAAM,MAAM,cAAc;AAWhC,MAAa,aAAa,YAAY;AAEpC,SADiB,MAAM,WAAW,CAAC,mBAAmB,uBAAuB,EAC7D,MAAM;;AAGxB,MAAM,cAAc,QACjB,QAAQ,OAAO,CACf,YAAY,0CAA0C,CACtD,OAAO,YAAY;AAElB,KAAI,YADY,YAAY,MAAM,CACV;CAExB,MAAM,UAAU,IAAI,aAAa,CAAC,OAAO;AAEzC,KAAI;EACF,MAAM,UAAU,MAAM,YAAY;AAClC,UAAQ,QAAQ,4BAA4B;AAC5C,UAAQ,OAAO,MAAM,MAAM,MAAM,gBAAgB,QAAQ,WAAW,eAAe,CAAC;AACpF,UAAQ,OAAO,MAAM,MAAM,KAAK,cAAc,QAAQ,YAAY,oBAAoB,CAAC;UAChFA,OAAY;AACnB,UAAQ,KAAK,yBAAyB;AACtC,MAAI,UAAU,MAAM;;EAEtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.js","names":["checks: Record<string, CheckDescriptor>","platform"],"sources":["../../src/commands/check.ts"],"sourcesContent":["import { exec } from \"node:child_process\";\nimport os from \"node:os\";\nimport chalk from \"chalk\";\nimport { program } from \"commander\";\nimport ora from \"ora\";\n\ninterface CheckDescriptor {\n check(): Promise<boolean>;\n message(): string[];\n}\n\nconst checks: Record<string, CheckDescriptor> = {\n ffmpeg: {\n message: () => {\n const platform = os.platform();\n const message = [\n \"Processing assets for <ef-video>, <ef-audio>, <ef-captions>, and <ef-waveform>\\n elements requires ffmpeg to be installed.\",\n ];\n switch (platform) {\n case \"darwin\": {\n message.push(\
|
|
1
|
+
{"version":3,"file":"check.js","names":["checks: Record<string, CheckDescriptor>","platform"],"sources":["../../src/commands/check.ts"],"sourcesContent":["import { exec } from \"node:child_process\";\nimport os from \"node:os\";\nimport chalk from \"chalk\";\nimport { program } from \"commander\";\nimport ora from \"ora\";\n\ninterface CheckDescriptor {\n check(): Promise<boolean>;\n message(): string[];\n}\n\nconst checks: Record<string, CheckDescriptor> = {\n ffmpeg: {\n message: () => {\n const platform = os.platform();\n const message = [\n \"Processing assets for <ef-video>, <ef-audio>, <ef-captions>, and <ef-waveform>\\n elements requires ffmpeg to be installed.\",\n ];\n switch (platform) {\n case \"darwin\": {\n message.push(\"On platform=darwin you can install ffmpeg using Homebrew:\");\n message.push(\" - brew install ffmpeg\");\n message.push(\"Or you can download ffmpeg from https://ffmpeg.org/download.html\");\n break;\n }\n case \"linux\": {\n message.push(\"You can install ffmpeg using your distribution's package manager.\");\n break;\n }\n case \"win32\": {\n message.push(\"You can download ffmpeg from https://ffmpeg.org/download.html\");\n message.push(\"You can use package managers like Chocolatey or Scoop to install ffmpeg.\");\n message.push(\" - choco install ffmpeg-full\");\n message.push(\" - scoop install ffmpeg\");\n message.push(\" - winget install ffmpeg\");\n break;\n }\n default: {\n message.push(`Unrecognized platform ${platform}`);\n message.push(\"You can download ffmpeg from https://ffmpeg.org/download.html\");\n message.push(\"Or try installing it from your operating system's package manager\");\n break;\n }\n }\n return message;\n },\n check: async () => {\n return new Promise((resolve, reject) => {\n exec(\"ffmpeg -version\", (error: any, stdout: any, _stderr: any) => {\n if (error) {\n reject(error);\n return;\n }\n resolve(stdout);\n });\n });\n },\n },\n\n whisper_timestamped: {\n message: () => {\n const message = [\"<ef-captions> Requires whisper_timestamped to be installed.\"];\n\n message.push(\"whisper_timestamped depends on python3\");\n\n message.push(\" - pip3 install whisper_timestamped\");\n\n message.push(\"Alternate installation instructions are availble at:\");\n message.push(\"https://github.com/linto-ai/whisper-timestamped#installation\");\n\n return message;\n },\n check: async () => {\n return new Promise((resolve, reject) => {\n exec(\"whisper_timestamped --version\", (error: any, stdout: any, _stderr: any) => {\n if (error) {\n reject(error);\n return;\n }\n resolve(stdout);\n });\n });\n },\n },\n};\n\nprogram\n .command(\"check\")\n .description(\"Check on dependencies and other requirements\")\n .action(async () => {\n for (const checkName in checks) {\n const check = checks[checkName];\n if (!check) {\n continue;\n }\n const spinner = ora(`Checking ${checkName}`).start();\n try {\n await check.check();\n spinner.succeed(chalk.white.bgGreen(` Check for ${checkName} passed `));\n } catch (_error) {\n spinner.fail(chalk.white.bgRed(` Check for ${checkName} failed `));\n process.stderr.write(chalk.red(check.message().join(\"\\n\\n\")));\n process.stderr.write(\"\\n\");\n }\n }\n });\n"],"mappings":";;;;;;;AAWA,MAAMA,SAA0C;CAC9C,QAAQ;EACN,eAAe;GACb,MAAMC,aAAW,GAAG,UAAU;GAC9B,MAAM,UAAU,CACd,6HACD;AACD,WAAQA,YAAR;IACE,KAAK;AACH,aAAQ,KAAK,4DAA4D;AACzE,aAAQ,KAAK,yBAAyB;AACtC,aAAQ,KAAK,mEAAmE;AAChF;IAEF,KAAK;AACH,aAAQ,KAAK,oEAAoE;AACjF;IAEF,KAAK;AACH,aAAQ,KAAK,gEAAgE;AAC7E,aAAQ,KAAK,2EAA2E;AACxF,aAAQ,KAAK,+BAA+B;AAC5C,aAAQ,KAAK,0BAA0B;AACvC,aAAQ,KAAK,2BAA2B;AACxC;IAEF;AACE,aAAQ,KAAK,yBAAyBA,aAAW;AACjD,aAAQ,KAAK,gEAAgE;AAC7E,aAAQ,KAAK,oEAAoE;AACjF;;AAGJ,UAAO;;EAET,OAAO,YAAY;AACjB,UAAO,IAAI,SAAS,SAAS,WAAW;AACtC,SAAK,oBAAoB,OAAY,QAAa,YAAiB;AACjE,SAAI,OAAO;AACT,aAAO,MAAM;AACb;;AAEF,aAAQ,OAAO;MACf;KACF;;EAEL;CAED,qBAAqB;EACnB,eAAe;GACb,MAAM,UAAU,CAAC,8DAA8D;AAE/E,WAAQ,KAAK,yCAAyC;AAEtD,WAAQ,KAAK,sCAAsC;AAEnD,WAAQ,KAAK,uDAAuD;AACpE,WAAQ,KAAK,+DAA+D;AAE5E,UAAO;;EAET,OAAO,YAAY;AACjB,UAAO,IAAI,SAAS,SAAS,WAAW;AACtC,SAAK,kCAAkC,OAAY,QAAa,YAAiB;AAC/E,SAAI,OAAO;AACT,aAAO,MAAM;AACb;;AAEF,aAAQ,OAAO;MACf;KACF;;EAEL;CACF;AAED,QACG,QAAQ,QAAQ,CAChB,YAAY,+CAA+C,CAC3D,OAAO,YAAY;AAClB,MAAK,MAAM,aAAa,QAAQ;EAC9B,MAAM,QAAQ,OAAO;AACrB,MAAI,CAAC,MACH;EAEF,MAAM,UAAU,IAAI,YAAY,YAAY,CAAC,OAAO;AACpD,MAAI;AACF,SAAM,MAAM,OAAO;AACnB,WAAQ,QAAQ,MAAM,MAAM,QAAQ,cAAc,UAAU,WAAW,CAAC;WACjE,QAAQ;AACf,WAAQ,KAAK,MAAM,MAAM,MAAM,eAAe,UAAU,WAAW,CAAC;AACpE,WAAQ,OAAO,MAAM,MAAM,IAAI,MAAM,SAAS,CAAC,KAAK,OAAO,CAAC,CAAC;AAC7D,WAAQ,OAAO,MAAM,KAAK;;;EAG9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-render.js","names":["basename","parseHTML"],"sources":["../../src/commands/cloud-render.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport path, { basename, join } from \"node:path\";\nimport { PassThrough } from \"node:stream\";\nimport { inspect } from \"node:util\";\nimport { createRender, uploadRender } from \"@editframe/api\";\nimport { md5Directory, md5FilePath } from \"@editframe/assets\";\nimport { getRenderInfo, RenderInfoSchema } from \"@editframe/elements/node\";\nimport { Option, program } from \"commander\";\nimport debug from \"debug\";\nimport { parse as parseHTML } from \"node-html-parser\";\nimport * as tar from \"tar\";\nimport { processRenderInfo } from \"../operations/processRenderInfo.js\";\nimport { SyncStatus } from \"../operations/syncAssetsDirectory/SyncStatus.js\";\nimport { syncAssetDirectory } from \"../operations/syncAssetsDirectory.js\";\nimport { createReadableStreamFromReadable } from \"../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../utils/index.js\";\nimport { launchBrowserAndWaitForSDK } from \"../utils/launchBrowserAndWaitForSDK.js\";\nimport { PreviewServer } from \"../utils/startPreviewServer.js\";\nimport { validateVideoResolution } from \"../utils/validateVideoResolution.js\";\nimport { withSpinner } from \"../utils/withSpinner.js\";\n\nconst log = debug(\"ef:cli:render\");\n\nexport const buildAssetId = async (
|
|
1
|
+
{"version":3,"file":"cloud-render.js","names":["basename","parseHTML"],"sources":["../../src/commands/cloud-render.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport path, { basename, join } from \"node:path\";\nimport { PassThrough } from \"node:stream\";\nimport { inspect } from \"node:util\";\nimport { createRender, uploadRender } from \"@editframe/api\";\nimport { md5Directory, md5FilePath } from \"@editframe/assets\";\nimport { getRenderInfo, RenderInfoSchema } from \"@editframe/elements/node\";\nimport { Option, program } from \"commander\";\nimport debug from \"debug\";\nimport { parse as parseHTML } from \"node-html-parser\";\nimport * as tar from \"tar\";\nimport { processRenderInfo } from \"../operations/processRenderInfo.js\";\nimport { SyncStatus } from \"../operations/syncAssetsDirectory/SyncStatus.js\";\nimport { syncAssetDirectory } from \"../operations/syncAssetsDirectory.js\";\nimport { createReadableStreamFromReadable } from \"../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../utils/index.js\";\nimport { launchBrowserAndWaitForSDK } from \"../utils/launchBrowserAndWaitForSDK.js\";\nimport { PreviewServer } from \"../utils/startPreviewServer.js\";\nimport { validateVideoResolution } from \"../utils/validateVideoResolution.js\";\nimport { withSpinner } from \"../utils/withSpinner.js\";\n\nconst log = debug(\"ef:cli:render\");\n\nexport const buildAssetId = async (srcDir: string, src: string, basename: string) => {\n log(`Building image asset id for ${src}\\n`);\n const assetPath = path.join(srcDir, src);\n const assetMd5 = await md5FilePath(assetPath);\n const syncStatus = new SyncStatus(join(srcDir, \"assets\", \".cache\", assetMd5, basename));\n const info = await syncStatus.readInfo();\n if (!info) {\n throw new Error(`SyncStatus info is not found for ${syncStatus.infoPath}`);\n }\n\n return info.id;\n};\n\nprogram\n .command(\"cloud-render [directory]\")\n .description(\"Render a directory's index.html file as a video in the editframe cloud\")\n .addOption(\n new Option(\"-s, --strategy <strategy>\", \"Render strategy\").choices([\"v1\"]).default(\"v1\"),\n )\n .action(async (directory, options) => {\n directory ??= \".\";\n\n // If running from the dev script (via tsx), ORIGINAL_CWD contains the user's actual directory\n const baseCwd = process.env.ORIGINAL_CWD || process.cwd();\n const resolvedDirectory = path.resolve(baseCwd, directory);\n\n await syncAssetDirectory(join(resolvedDirectory, \"src\", \"assets\", \".cache\"));\n\n const srcDir = path.join(resolvedDirectory, \"src\");\n const distDir = path.join(resolvedDirectory, \"dist\");\n await withSpinner(\"Building\\n\", async () => {\n try {\n await withSpinner(\"Building\\n\", async () => {\n spawnSync(\n \"npx\",\n // biome-ignore format: Grouping CLI arguments\n [\"vite\", \"build\", resolvedDirectory, \"--clearScreen\", \"false\", \"--logLevel\", \"debug\"],\n {\n stdio: \"inherit\",\n },\n );\n });\n } catch (error) {\n console.error(\"Build failed:\", error);\n }\n });\n\n const previewServer = await PreviewServer.start(distDir);\n process.stderr.write(\"Preview server started at:\");\n process.stderr.write(previewServer.url);\n process.stderr.write(\"\\n\");\n await launchBrowserAndWaitForSDK(\n {\n url: previewServer.url,\n efInteractive: false,\n interactive: false,\n headless: true,\n },\n async (page) => {\n const renderInfo = RenderInfoSchema.parse(await page.evaluate(getRenderInfo));\n\n validateVideoResolution({\n width: renderInfo.width,\n height: renderInfo.height,\n });\n\n await processRenderInfo(renderInfo);\n\n const doc = parseHTML(await readFile(path.join(distDir, \"index.html\"), \"utf-8\"));\n\n log(\"Building file IDs\");\n for (const element of doc.querySelectorAll(\"ef-image, ef-audio, ef-video\")) {\n log(`Processing ${element.tagName}`);\n if (element.hasAttribute(\"file-id\") || element.hasAttribute(\"asset-id\")) {\n log(\n `File ID for ${element.tagName} ${element.getAttribute(\"src\")} is ${element.getAttribute(\"file-id\") || element.getAttribute(\"asset-id\")}`,\n );\n continue;\n }\n const src = element.getAttribute(\"src\");\n if (!src) {\n log(`No src attribute for ${element.tagName}`);\n continue;\n }\n\n switch (element.tagName) {\n case \"EF-IMAGE\":\n element.setAttribute(\"file-id\", await buildAssetId(srcDir, src, basename(src)));\n break;\n case \"EF-AUDIO\":\n case \"EF-VIDEO\":\n element.setAttribute(\"file-id\", await buildAssetId(srcDir, src, \"isobmff\"));\n break;\n default:\n log(`Unknown element type: ${element.tagName}`);\n }\n }\n\n await writeFile(path.join(distDir, \"index.html\"), doc.toString());\n\n const md5 = await md5Directory(distDir);\n const render = await createRender(getClient(), {\n md5,\n width: renderInfo.width,\n height: renderInfo.height,\n fps: renderInfo.fps,\n duration_ms: renderInfo.durationMs,\n work_slice_ms: 4_000,\n strategy: options.strategy,\n });\n if (render?.status !== \"created\") {\n process.stderr.write(\n `Render is in '${render?.status}' status. It cannot be recreated while in this status.\\n`,\n );\n return;\n }\n /**\n * This tar stream is created with the dist directory as the root.\n * This is acheived by setting the cwd option to the dist directory.\n * And the files to be included in the tar stream are all files in the dist directory.\n *\n * The renderer expects to find the index.html file at the root of the tar stream.\n */\n const tarStream = tar.create(\n {\n gzip: true,\n cwd: distDir,\n },\n [\".\"],\n );\n const readable = new PassThrough();\n tarStream.pipe(readable);\n\n await uploadRender(getClient(), render.id, createReadableStreamFromReadable(readable));\n process.stderr.write(\"Render assets uploaded\\n\");\n process.stderr.write(inspect(render));\n process.stderr.write(\"\\n\");\n },\n );\n });\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-file.js","names":[],"sources":["../../src/commands/process-file.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"process-file.js","names":[],"sources":["../../src/commands/process-file.ts"],"sourcesContent":["import { upload, getFileProcessingProgress, getFileDetail } from \"@editframe/api/node\";\nimport { program } from \"commander\";\nimport ora from \"ora\";\nimport { getClient } from \"../utils/index.js\";\n\nprogram\n .command(\"process-file <file>\")\n .description(\"Upload a audio/video to Editframe for processing.\")\n .action(async (path: string) => {\n const client = getClient();\n\n const uploadSpinner = ora(\"Creating file and uploading\").start();\n\n const { file, uploadIterator } = await upload(client, path);\n\n for await (const event of uploadIterator) {\n uploadSpinner.text = `Uploading file: ${(100 * event.progress).toFixed(2)}%`;\n }\n uploadSpinner.succeed(\"Upload complete\");\n\n if (file.type !== \"video\") {\n console.log(`File type \"${file.type}\" does not require processing.`);\n console.log(\"File ID:\", file.id);\n return;\n }\n\n const processSpinner = ora(\"Waiting for processing to complete\");\n processSpinner.start();\n const progress = await getFileProcessingProgress(client, file.id);\n\n for await (const event of progress) {\n if (event.type === \"progress\") {\n processSpinner.text = `Processing: ${(100 * event.data.progress).toFixed(2)}%`;\n } else if (event.type === \"complete\") {\n processSpinner.succeed(\"Processing complete\");\n }\n }\n\n const detail = await getFileDetail(client, file.id);\n\n console.log(\"Processed file info\");\n console.log(detail);\n });\n"],"mappings":";;;;;;AAKA,QACG,QAAQ,sBAAsB,CAC9B,YAAY,oDAAoD,CAChE,OAAO,OAAO,SAAiB;CAC9B,MAAM,SAAS,WAAW;CAE1B,MAAM,gBAAgB,IAAI,8BAA8B,CAAC,OAAO;CAEhE,MAAM,EAAE,MAAM,mBAAmB,MAAM,OAAO,QAAQ,KAAK;AAE3D,YAAW,MAAM,SAAS,eACxB,eAAc,OAAO,oBAAoB,MAAM,MAAM,UAAU,QAAQ,EAAE,CAAC;AAE5E,eAAc,QAAQ,kBAAkB;AAExC,KAAI,KAAK,SAAS,SAAS;AACzB,UAAQ,IAAI,cAAc,KAAK,KAAK,gCAAgC;AACpE,UAAQ,IAAI,YAAY,KAAK,GAAG;AAChC;;CAGF,MAAM,iBAAiB,IAAI,qCAAqC;AAChE,gBAAe,OAAO;CACtB,MAAM,WAAW,MAAM,0BAA0B,QAAQ,KAAK,GAAG;AAEjE,YAAW,MAAM,SAAS,SACxB,KAAI,MAAM,SAAS,WACjB,gBAAe,OAAO,gBAAgB,MAAM,MAAM,KAAK,UAAU,QAAQ,EAAE,CAAC;UACnE,MAAM,SAAS,WACxB,gBAAe,QAAQ,sBAAsB;CAIjD,MAAM,SAAS,MAAM,cAAc,QAAQ,KAAK,GAAG;AAEnD,SAAQ,IAAI,sBAAsB;AAClC,SAAQ,IAAI,OAAO;EACnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","names":["token: string | undefined","efRenderHost: string","renderData: Record<string, unknown> | undefined","renderUrl: string","viteServer: SpawnedViteServer | null","lastProgress: {\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n speedMultiplier: number;\n } | null","renderInfo: any","renderOptions: any"],"sources":["../../src/commands/render.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { createWriteStream, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { program } from \"commander\";\nimport debug from \"debug\";\nimport ora from \"ora\";\nimport { launchBrowserAndWaitForSDK } from \"../utils/launchBrowserAndWaitForSDK.js\";\nimport {\n spawnViteServer,\n type SpawnedViteServer,\n} from \"../utils/spawnViteServer.js\";\nimport { patchFragmentedMp4 } from \"../utils/patchFragmentedMp4.js\";\nimport { StreamTargetChunk } from \"mediabunny\";\nimport { withProfiling } from \"../utils/profileRender.js\";\nimport { VERSION } from \"../VERSION.js\";\n\ndeclare global {\n interface Window {\n EF_RENDER_DATA: any;\n EF_RENDER: any;\n }\n}\n\nconst log = debug(\"ef:cli:render\");\n\nexport async function sendTelemetry(\n efRenderHost: string,\n token: string | undefined,\n payload: Record<string, unknown>,\n): Promise<void> {\n if (process.env.EF_TELEMETRY_ENABLED !== \"true\") return;\n try {\n await fetch(`${efRenderHost}/api/v1/telemetry`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(token && { Authorization: `Bearer ${token}` }),\n },\n body: JSON.stringify(payload),\n });\n } catch {\n // Telemetry must never fail the render — swallow all errors.\n }\n}\n\n/**\n * Format milliseconds as MM:SS or HH:MM:SS\n */\nfunction formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}`;\n }\n return `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n}\n\nprogram\n .command(\"render [directory]\")\n .description(\"Render a video composition locally\")\n .option(\"-o, --output <path>\", \"Output file path\", \"output.mp4\")\n .option(\"--url <url>\", \"URL to render (bypasses directory/server startup)\")\n .option(\"-d, --data <json>\", \"Custom render data (JSON string)\")\n .option(\"--data-file <path>\", \"Custom render data from JSON file\")\n .option(\"--fps <number>\", \"Frame rate\", \"30\")\n .option(\"--scale <number>\", \"Resolution scale (0-1)\", \"1\")\n .option(\"--include-audio\", \"Include audio track\", true)\n .option(\"--no-include-audio\", \"Exclude audio track\")\n .option(\"--from-ms <number>\", \"Start time in milliseconds\")\n .option(\"--to-ms <number>\", \"End time in milliseconds\")\n .option(\n \"--experimental-native-render\",\n \"Use experimental canvas capture API (faster)\",\n )\n .option(\"--profile\", \"Enable CPU profiling\")\n .option(\n \"--profile-output <path>\",\n \"Profile output path\",\n \"./render-profile.cpuprofile\",\n )\n .action(async (directory = \".\", options) => {\n const programOpts = program.opts();\n const token: string | undefined = programOpts.token || process.env.EF_TOKEN;\n const efRenderHost: string =\n programOpts.efRenderHost ||\n process.env.EF_RENDER_HOST ||\n \"https://editframe.com\";\n\n // If running from the dev script (via tsx), ORIGINAL_CWD contains the user's actual directory\n const baseCwd = process.env.ORIGINAL_CWD || process.cwd();\n const outputPath = path.resolve(baseCwd, options.output);\n\n // Parse custom data if provided\n let renderData: Record<string, unknown> | undefined;\n if (options.dataFile) {\n const dataFileContent = await readFile(options.dataFile, \"utf-8\");\n renderData = JSON.parse(dataFileContent);\n log(\"Loaded render data from file:\", options.dataFile);\n } else if (options.data) {\n renderData = JSON.parse(options.data);\n log(\"Using render data from --data option\");\n }\n\n // Parse numeric options\n const fps = parseInt(options.fps, 10);\n const scale = parseFloat(options.scale);\n const fromMs = options.fromMs ? parseInt(options.fromMs, 10) : undefined;\n const toMs = options.toMs ? parseInt(options.toMs, 10) : undefined;\n\n // Single initialization spinner for all setup steps\n const initSpinner = ora(\"Initializing...\").start();\n\n let renderUrl: string;\n let viteServer: SpawnedViteServer | null = null;\n let renderStarted = false;\n\n try {\n // Determine URL to render\n if (options.url) {\n // Use provided URL directly\n renderUrl = options.url;\n log(\"Using provided URL:\", renderUrl);\n } else {\n // Spawn Vite dev server as subprocess\n // This allows Vite to run with full config resolution (including Tailwind)\n // while we maintain Playwright control for rendering\n const srcDir = path.resolve(baseCwd, directory);\n viteServer = await spawnViteServer(srcDir);\n renderUrl = viteServer.url;\n log(\"Vite server spawned at:\", renderUrl);\n }\n\n // Launch browser and load SDK (all within initialization)\n await launchBrowserAndWaitForSDK(\n {\n url: renderUrl,\n headless: true,\n interactive: false,\n efInteractive: false,\n nativeRender: options.experimentalNativeRender === true,\n profile: options.profile === true,\n profileOutput: options.profileOutput,\n silent: true, // Suppress individual spinners since we show unified \"Initializing...\"\n },\n async (page) => {\n initSpinner.succeed(\"Ready\");\n\n // Now handle the render\n await withProfiling(\n page,\n {\n enabled: options.profile === true,\n outputPath: options.profileOutput,\n },\n async () => {\n // Ensure output directory exists\n mkdirSync(path.dirname(outputPath), { recursive: true });\n\n // Open output file for streaming writes\n const outputStream = createWriteStream(outputPath);\n let chunkCount = 0;\n let totalBytes = 0;\n\n // Expose chunk handler - writes through the stream in order\n await page.exposeFunction(\n \"onRenderChunk\",\n (chunk: StreamTargetChunk) => {\n outputStream.write(Buffer.from(chunk.data));\n chunkCount++;\n totalBytes += chunk.data.length;\n log(\n `Received chunk ${chunkCount}: ${chunk.data.length} bytes (total: ${totalBytes} bytes)`,\n );\n },\n );\n\n // Set custom render data if provided\n if (renderData) {\n await page.evaluate((data) => {\n window.EF_RENDER_DATA = data;\n }, renderData);\n log(\"Set EF_RENDER_DATA:\", renderData);\n }\n\n // Wait for EF_RENDER API to be available\n await page.waitForFunction(\n () => typeof window.EF_RENDER !== \"undefined\",\n { timeout: 10_000 },\n );\n\n // Check if ready\n const isReady = await page.evaluate(() =>\n window.EF_RENDER?.isReady(),\n );\n if (!isReady) {\n throw new Error(\n \"Render API is not ready. No ef-timegroup found.\",\n );\n }\n\n // Create progress spinner\n const progressSpinner = ora(\"Rendering video...\").start();\n\n // Track last progress for completion message\n let lastProgress: {\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n speedMultiplier: number;\n } | null = null;\n\n // Expose progress callback\n await page.exposeFunction(\n \"onRenderProgress\",\n (progress: {\n progress: number;\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n estimatedRemainingMs: number;\n speedMultiplier: number;\n }) => {\n const percent = (progress.progress * 100).toFixed(1);\n const renderedTime = formatTime(progress.renderedMs);\n const totalTime = formatTime(progress.totalDurationMs);\n const remainingTime = formatTime(\n progress.estimatedRemainingMs,\n );\n const speed = progress.speedMultiplier.toFixed(2);\n\n progressSpinner.text = `Rendering: ${progress.currentFrame}/${progress.totalFrames} frames (${percent}%) | ${renderedTime}/${totalTime} | ${remainingTime} remaining | ${speed}x speed`;\n\n // Store last progress for completion message\n lastProgress = {\n currentFrame: progress.currentFrame,\n totalFrames: progress.totalFrames,\n renderedMs: progress.renderedMs,\n totalDurationMs: progress.totalDurationMs,\n elapsedMs: progress.elapsedMs,\n speedMultiplier: progress.speedMultiplier,\n };\n },\n );\n\n // Render with streaming\n renderStarted = true;\n let renderInfo: any = null;\n try {\n const renderOptions: any = {\n fps,\n scale,\n includeAudio: options.includeAudio !== false,\n };\n\n if (fromMs !== undefined) {\n renderOptions.fromMs = fromMs;\n }\n if (toMs !== undefined) {\n renderOptions.toMs = toMs;\n }\n\n const renderStartTime = Date.now();\n await page.evaluate(async (opts) => {\n await window.EF_RENDER!.renderStreaming(opts);\n }, renderOptions);\n const renderDurationMs = Date.now() - renderStartTime;\n\n // Collect render info for telemetry and moov patching\n renderInfo = await page.evaluate(async () => {\n try {\n return await window.EF_RENDER!.getRenderInfo();\n } catch {\n return null;\n }\n });\n\n // Build completion message with performance stats\n if (lastProgress) {\n const p = lastProgress as {\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n speedMultiplier: number;\n };\n const renderedTime = formatTime(p.renderedMs);\n const totalTime = formatTime(p.totalDurationMs);\n const elapsedTime = formatTime(p.elapsedMs);\n const speed = p.speedMultiplier.toFixed(2);\n progressSpinner.succeed(\n `Render complete: ${p.currentFrame}/${p.totalFrames} frames | ${renderedTime}/${totalTime} | ${elapsedTime} elapsed | ${speed}x speed`,\n );\n } else {\n progressSpinner.succeed(\"Render complete\");\n }\n\n // Send telemetry (fire-and-forget)\n void sendTelemetry(efRenderHost, token, {\n render_path: \"cli\",\n duration_ms: renderDurationMs,\n ...(renderInfo?.width != null && { width: renderInfo.width }),\n ...(renderInfo?.height != null && {\n height: renderInfo.height,\n }),\n ...(renderInfo?.fps != null && { fps: renderInfo.fps }),\n feature_usage: {\n efMediaCount: renderInfo\n ? Object.keys(renderInfo.assets.efMedia).length\n : 0,\n efImageCount: renderInfo?.assets.efImage.length ?? 0,\n efCaptionsCount: renderInfo?.assets.efCaptions.length ?? 0,\n efTextCount: 0,\n },\n cli_version: VERSION,\n });\n } catch (error) {\n progressSpinner.fail(\"Render failed\");\n throw error;\n }\n\n // Close the output stream and wait for all chunks to flush\n outputStream.end();\n await new Promise<void>((resolve, reject) => {\n outputStream.on(\"finish\", () => {\n log(\n `Render complete: ${chunkCount} chunks, ${totalBytes} bytes written to ${outputPath}`,\n );\n resolve();\n });\n outputStream.on(\"error\", reject);\n });\n\n if (renderInfo?.durationMs) {\n await patchFragmentedMp4(outputPath, renderInfo.durationMs);\n }\n },\n );\n },\n );\n } catch (error) {\n if (!renderStarted) {\n initSpinner.fail(\"Initialization failed\");\n }\n throw error;\n }\n\n // Clean up spawned Vite process\n if (viteServer) {\n viteServer.kill();\n log(\"Vite server stopped\");\n }\n\n process.stderr.write(`\\nRender complete: ${outputPath}\\n`);\n });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"render.js","names":["token: string | undefined","efRenderHost: string","renderData: Record<string, unknown> | undefined","renderUrl: string","viteServer: SpawnedViteServer | null","lastProgress: {\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n speedMultiplier: number;\n } | null","renderInfo: any","renderOptions: any"],"sources":["../../src/commands/render.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { createWriteStream, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { program } from \"commander\";\nimport debug from \"debug\";\nimport ora from \"ora\";\nimport { launchBrowserAndWaitForSDK } from \"../utils/launchBrowserAndWaitForSDK.js\";\nimport { spawnViteServer, type SpawnedViteServer } from \"../utils/spawnViteServer.js\";\nimport { patchFragmentedMp4 } from \"../utils/patchFragmentedMp4.js\";\nimport { StreamTargetChunk } from \"mediabunny\";\nimport { withProfiling } from \"../utils/profileRender.js\";\nimport { VERSION } from \"../VERSION.js\";\n\ndeclare global {\n interface Window {\n EF_RENDER_DATA: any;\n EF_RENDER: any;\n }\n}\n\nconst log = debug(\"ef:cli:render\");\n\nexport async function sendTelemetry(\n efRenderHost: string,\n token: string | undefined,\n payload: Record<string, unknown>,\n): Promise<void> {\n if (process.env.EF_TELEMETRY_ENABLED !== \"true\") return;\n try {\n await fetch(`${efRenderHost}/api/v1/telemetry`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(token && { Authorization: `Bearer ${token}` }),\n },\n body: JSON.stringify(payload),\n });\n } catch {\n // Telemetry must never fail the render — swallow all errors.\n }\n}\n\n/**\n * Format milliseconds as MM:SS or HH:MM:SS\n */\nfunction formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}`;\n }\n return `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n}\n\nprogram\n .command(\"render [directory]\")\n .description(\"Render a video composition locally\")\n .option(\"-o, --output <path>\", \"Output file path\", \"output.mp4\")\n .option(\"--url <url>\", \"URL to render (bypasses directory/server startup)\")\n .option(\"-d, --data <json>\", \"Custom render data (JSON string)\")\n .option(\"--data-file <path>\", \"Custom render data from JSON file\")\n .option(\"--fps <number>\", \"Frame rate\", \"30\")\n .option(\"--scale <number>\", \"Resolution scale (0-1)\", \"1\")\n .option(\"--include-audio\", \"Include audio track\", true)\n .option(\"--no-include-audio\", \"Exclude audio track\")\n .option(\"--from-ms <number>\", \"Start time in milliseconds\")\n .option(\"--to-ms <number>\", \"End time in milliseconds\")\n .option(\"--experimental-native-render\", \"Use experimental canvas capture API (faster)\")\n .option(\"--profile\", \"Enable CPU profiling\")\n .option(\"--profile-output <path>\", \"Profile output path\", \"./render-profile.cpuprofile\")\n .action(async (directory = \".\", options) => {\n const programOpts = program.opts();\n const token: string | undefined = programOpts.token || process.env.EF_TOKEN;\n const efRenderHost: string =\n programOpts.efRenderHost || process.env.EF_RENDER_HOST || \"https://editframe.com\";\n\n // If running from the dev script (via tsx), ORIGINAL_CWD contains the user's actual directory\n const baseCwd = process.env.ORIGINAL_CWD || process.cwd();\n const outputPath = path.resolve(baseCwd, options.output);\n\n // Parse custom data if provided\n let renderData: Record<string, unknown> | undefined;\n if (options.dataFile) {\n const dataFileContent = await readFile(options.dataFile, \"utf-8\");\n renderData = JSON.parse(dataFileContent);\n log(\"Loaded render data from file:\", options.dataFile);\n } else if (options.data) {\n renderData = JSON.parse(options.data);\n log(\"Using render data from --data option\");\n }\n\n // Parse numeric options\n const fps = parseInt(options.fps, 10);\n const scale = parseFloat(options.scale);\n const fromMs = options.fromMs ? parseInt(options.fromMs, 10) : undefined;\n const toMs = options.toMs ? parseInt(options.toMs, 10) : undefined;\n\n // Single initialization spinner for all setup steps\n const initSpinner = ora(\"Initializing...\").start();\n\n let renderUrl: string;\n let viteServer: SpawnedViteServer | null = null;\n let renderStarted = false;\n\n try {\n // Determine URL to render\n if (options.url) {\n // Use provided URL directly\n renderUrl = options.url;\n log(\"Using provided URL:\", renderUrl);\n } else {\n // Spawn Vite dev server as subprocess\n // This allows Vite to run with full config resolution (including Tailwind)\n // while we maintain Playwright control for rendering\n const srcDir = path.resolve(baseCwd, directory);\n viteServer = await spawnViteServer(srcDir);\n renderUrl = viteServer.url;\n log(\"Vite server spawned at:\", renderUrl);\n }\n\n // Launch browser and load SDK (all within initialization)\n await launchBrowserAndWaitForSDK(\n {\n url: renderUrl,\n headless: true,\n interactive: false,\n efInteractive: false,\n nativeRender: options.experimentalNativeRender === true,\n profile: options.profile === true,\n profileOutput: options.profileOutput,\n silent: true, // Suppress individual spinners since we show unified \"Initializing...\"\n },\n async (page) => {\n initSpinner.succeed(\"Ready\");\n\n // Now handle the render\n await withProfiling(\n page,\n {\n enabled: options.profile === true,\n outputPath: options.profileOutput,\n },\n async () => {\n // Ensure output directory exists\n mkdirSync(path.dirname(outputPath), { recursive: true });\n\n // Open output file for streaming writes\n const outputStream = createWriteStream(outputPath);\n let chunkCount = 0;\n let totalBytes = 0;\n\n // Expose chunk handler - writes through the stream in order\n await page.exposeFunction(\"onRenderChunk\", (chunk: StreamTargetChunk) => {\n outputStream.write(Buffer.from(chunk.data));\n chunkCount++;\n totalBytes += chunk.data.length;\n log(\n `Received chunk ${chunkCount}: ${chunk.data.length} bytes (total: ${totalBytes} bytes)`,\n );\n });\n\n // Set custom render data if provided\n if (renderData) {\n await page.evaluate((data) => {\n window.EF_RENDER_DATA = data;\n }, renderData);\n log(\"Set EF_RENDER_DATA:\", renderData);\n }\n\n // Wait for EF_RENDER API to be available\n await page.waitForFunction(() => typeof window.EF_RENDER !== \"undefined\", {\n timeout: 10_000,\n });\n\n // Check if ready\n const isReady = await page.evaluate(() => window.EF_RENDER?.isReady());\n if (!isReady) {\n throw new Error(\"Render API is not ready. No ef-timegroup found.\");\n }\n\n // Create progress spinner\n const progressSpinner = ora(\"Rendering video...\").start();\n\n // Track last progress for completion message\n let lastProgress: {\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n speedMultiplier: number;\n } | null = null;\n\n // Expose progress callback\n await page.exposeFunction(\n \"onRenderProgress\",\n (progress: {\n progress: number;\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n estimatedRemainingMs: number;\n speedMultiplier: number;\n }) => {\n const percent = (progress.progress * 100).toFixed(1);\n const renderedTime = formatTime(progress.renderedMs);\n const totalTime = formatTime(progress.totalDurationMs);\n const remainingTime = formatTime(progress.estimatedRemainingMs);\n const speed = progress.speedMultiplier.toFixed(2);\n\n progressSpinner.text = `Rendering: ${progress.currentFrame}/${progress.totalFrames} frames (${percent}%) | ${renderedTime}/${totalTime} | ${remainingTime} remaining | ${speed}x speed`;\n\n // Store last progress for completion message\n lastProgress = {\n currentFrame: progress.currentFrame,\n totalFrames: progress.totalFrames,\n renderedMs: progress.renderedMs,\n totalDurationMs: progress.totalDurationMs,\n elapsedMs: progress.elapsedMs,\n speedMultiplier: progress.speedMultiplier,\n };\n },\n );\n\n // Render with streaming\n renderStarted = true;\n let renderInfo: any = null;\n try {\n const renderOptions: any = {\n fps,\n scale,\n includeAudio: options.includeAudio !== false,\n };\n\n if (fromMs !== undefined) {\n renderOptions.fromMs = fromMs;\n }\n if (toMs !== undefined) {\n renderOptions.toMs = toMs;\n }\n\n const renderStartTime = Date.now();\n await page.evaluate(async (opts) => {\n await window.EF_RENDER!.renderStreaming(opts);\n }, renderOptions);\n const renderDurationMs = Date.now() - renderStartTime;\n\n // Collect render info for telemetry and moov patching\n renderInfo = await page.evaluate(async () => {\n try {\n return await window.EF_RENDER!.getRenderInfo();\n } catch {\n return null;\n }\n });\n\n // Build completion message with performance stats\n if (lastProgress) {\n const p = lastProgress as {\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n speedMultiplier: number;\n };\n const renderedTime = formatTime(p.renderedMs);\n const totalTime = formatTime(p.totalDurationMs);\n const elapsedTime = formatTime(p.elapsedMs);\n const speed = p.speedMultiplier.toFixed(2);\n progressSpinner.succeed(\n `Render complete: ${p.currentFrame}/${p.totalFrames} frames | ${renderedTime}/${totalTime} | ${elapsedTime} elapsed | ${speed}x speed`,\n );\n } else {\n progressSpinner.succeed(\"Render complete\");\n }\n\n // Send telemetry (fire-and-forget)\n void sendTelemetry(efRenderHost, token, {\n render_path: \"cli\",\n duration_ms: renderDurationMs,\n ...(renderInfo?.width != null && { width: renderInfo.width }),\n ...(renderInfo?.height != null && {\n height: renderInfo.height,\n }),\n ...(renderInfo?.fps != null && { fps: renderInfo.fps }),\n feature_usage: {\n efMediaCount: renderInfo ? Object.keys(renderInfo.assets.efMedia).length : 0,\n efImageCount: renderInfo?.assets.efImage.length ?? 0,\n efCaptionsCount: renderInfo?.assets.efCaptions.length ?? 0,\n efTextCount: 0,\n },\n cli_version: VERSION,\n });\n } catch (error) {\n progressSpinner.fail(\"Render failed\");\n throw error;\n }\n\n // Close the output stream and wait for all chunks to flush\n outputStream.end();\n await new Promise<void>((resolve, reject) => {\n outputStream.on(\"finish\", () => {\n log(\n `Render complete: ${chunkCount} chunks, ${totalBytes} bytes written to ${outputPath}`,\n );\n resolve();\n });\n outputStream.on(\"error\", reject);\n });\n\n if (renderInfo?.durationMs) {\n await patchFragmentedMp4(outputPath, renderInfo.durationMs);\n }\n },\n );\n },\n );\n } catch (error) {\n if (!renderStarted) {\n initSpinner.fail(\"Initialization failed\");\n }\n throw error;\n }\n\n // Clean up spawned Vite process\n if (viteServer) {\n viteServer.kill();\n log(\"Vite server stopped\");\n }\n\n process.stderr.write(`\\nRender complete: ${outputPath}\\n`);\n });\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","names":[],"sources":["../../src/commands/sync.ts"],"sourcesContent":["import path, { join } from \"node:path\";\nimport { program } from \"commander\";\nimport { syncAssetDirectory } from \"../operations/syncAssetsDirectory.js\";\n\nprogram\n .command(\"sync\")\n .description(\"Sync assets to Editframe servers for rendering\")\n .argument(\"[directory]\", \"Path to project directory to sync.\")\n .action(async (directory = \".\") => {\n // If running from the dev script (via tsx), ORIGINAL_CWD contains the user's actual directory\n const baseCwd = process.env.ORIGINAL_CWD || process.cwd();\n const resolvedDirectory = path.resolve(baseCwd, directory);\n\n await syncAssetDirectory(
|
|
1
|
+
{"version":3,"file":"sync.js","names":[],"sources":["../../src/commands/sync.ts"],"sourcesContent":["import path, { join } from \"node:path\";\nimport { program } from \"commander\";\nimport { syncAssetDirectory } from \"../operations/syncAssetsDirectory.js\";\n\nprogram\n .command(\"sync\")\n .description(\"Sync assets to Editframe servers for rendering\")\n .argument(\"[directory]\", \"Path to project directory to sync.\")\n .action(async (directory = \".\") => {\n // If running from the dev script (via tsx), ORIGINAL_CWD contains the user's actual directory\n const baseCwd = process.env.ORIGINAL_CWD || process.cwd();\n const resolvedDirectory = path.resolve(baseCwd, directory);\n\n await syncAssetDirectory(join(resolvedDirectory, \"src\", \"assets\", \".cache\"));\n });\n"],"mappings":";;;;;AAIA,QACG,QAAQ,OAAO,CACf,YAAY,iDAAiD,CAC7D,SAAS,eAAe,qCAAqC,CAC7D,OAAO,OAAO,YAAY,QAAQ;CAEjC,MAAM,UAAU,QAAQ,IAAI,gBAAgB,QAAQ,KAAK;AAGzD,OAAM,mBAAmB,KAFC,KAAK,QAAQ,SAAS,UAAU,EAET,OAAO,UAAU,SAAS,CAAC;EAC5E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcribe.js","names":[],"sources":["../../src/commands/transcribe.ts"],"sourcesContent":["import { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { program } from \"commander\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { generateCaptionDataFromPath } from \"@editframe/assets\";\n\nprogram\n .command(\"transcribe <input>\")\n .description(\
|
|
1
|
+
{"version":3,"file":"transcribe.js","names":[],"sources":["../../src/commands/transcribe.ts"],"sourcesContent":["import { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { program } from \"commander\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { generateCaptionDataFromPath } from \"@editframe/assets\";\n\nprogram\n .command(\"transcribe <input>\")\n .description(\"Generate captions from audio/video file using whisper_timestamped\")\n .option(\"-o, --output <file>\", \"Output JSON file\", \"captions.json\")\n .option(\"-l, --language <lang>\", \"Language code (e.g., en, es, fr)\", \"en\")\n .action(async (input: string, options: { output: string; language: string }) => {\n const spinner = ora(\"Generating captions...\").start();\n\n try {\n const absoluteInput = resolve(input);\n const absoluteOutput = resolve(options.output);\n\n spinner.text = `Transcribing ${input}...`;\n\n // Generate captions using the same function as the vite plugin\n const captionData = await generateCaptionDataFromPath(absoluteInput);\n\n spinner.text = `Writing captions to ${options.output}...`;\n await writeFile(absoluteOutput, captionData, \"utf-8\");\n\n spinner.succeed(chalk.green(`✓ Captions generated successfully: ${options.output}`));\n\n // Parse to show stats\n const parsed = JSON.parse(captionData);\n console.log(chalk.dim(` ${parsed.segments.length} segments`));\n console.log(chalk.dim(` ${parsed.word_segments.length} words`));\n } catch (error) {\n spinner.fail(chalk.red(\"Failed to generate captions\"));\n\n if ((error as Error).message.includes(\"whisper_timestamped\")) {\n console.error(chalk.red(\"\\nwhisper_timestamped is not installed or not in PATH\"));\n console.error(chalk.yellow(\"\\nInstall it with:\"));\n console.error(chalk.white(\" pip3 install whisper-timestamped\"));\n console.error(chalk.dim(\"\\nOr check installation instructions at:\"));\n console.error(chalk.dim(\" https://github.com/linto-ai/whisper-timestamped#installation\"));\n } else {\n console.error(chalk.red(`\\n${(error as Error).message}`));\n }\n\n process.exit(1);\n }\n });\n"],"mappings":";;;;;;;;AAOA,QACG,QAAQ,qBAAqB,CAC7B,YAAY,oEAAoE,CAChF,OAAO,uBAAuB,oBAAoB,gBAAgB,CAClE,OAAO,yBAAyB,oCAAoC,KAAK,CACzE,OAAO,OAAO,OAAe,YAAkD;CAC9E,MAAM,UAAU,IAAI,yBAAyB,CAAC,OAAO;AAErD,KAAI;EACF,MAAM,gBAAgB,QAAQ,MAAM;EACpC,MAAM,iBAAiB,QAAQ,QAAQ,OAAO;AAE9C,UAAQ,OAAO,gBAAgB,MAAM;EAGrC,MAAM,cAAc,MAAM,4BAA4B,cAAc;AAEpE,UAAQ,OAAO,uBAAuB,QAAQ,OAAO;AACrD,QAAM,UAAU,gBAAgB,aAAa,QAAQ;AAErD,UAAQ,QAAQ,MAAM,MAAM,sCAAsC,QAAQ,SAAS,CAAC;EAGpF,MAAM,SAAS,KAAK,MAAM,YAAY;AACtC,UAAQ,IAAI,MAAM,IAAI,KAAK,OAAO,SAAS,OAAO,WAAW,CAAC;AAC9D,UAAQ,IAAI,MAAM,IAAI,KAAK,OAAO,cAAc,OAAO,QAAQ,CAAC;UACzD,OAAO;AACd,UAAQ,KAAK,MAAM,IAAI,8BAA8B,CAAC;AAEtD,MAAK,MAAgB,QAAQ,SAAS,sBAAsB,EAAE;AAC5D,WAAQ,MAAM,MAAM,IAAI,wDAAwD,CAAC;AACjF,WAAQ,MAAM,MAAM,OAAO,qBAAqB,CAAC;AACjD,WAAQ,MAAM,MAAM,MAAM,qCAAqC,CAAC;AAChE,WAAQ,MAAM,MAAM,IAAI,2CAA2C,CAAC;AACpE,WAAQ,MAAM,MAAM,IAAI,iEAAiE,CAAC;QAE1F,SAAQ,MAAM,MAAM,IAAI,KAAM,MAAgB,UAAU,CAAC;AAG3D,UAAQ,KAAK,EAAE;;EAEjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.js","names":["topic","error: any"],"sources":["../../src/commands/webhook.ts"],"sourcesContent":["import { input, select } from \"@inquirer/prompts\";\nimport chalk from \"chalk\";\nimport { Option, program } from \"commander\";\nimport debug from \"debug\";\nimport ora from \"ora\";\n\nimport { getClient } from \"../utils/index.js\";\n\nconst log = debug(\"ef:cli:auth\");\n\nexport interface APITestWebhhokResult {\n message: string;\n}\nconst topics = [\n \"render.created\",\n \"render.rendering\",\n \"render.pending\",\n \"render.failed\",\n \"render.completed\",\n];\n\nexport const testWebhookURL = async ({\n webhookURL,\n topic,\n}: {\n webhookURL: string;\n topic: string;\n}) => {\n const response = await getClient().authenticatedFetch(\
|
|
1
|
+
{"version":3,"file":"webhook.js","names":["topic","error: any"],"sources":["../../src/commands/webhook.ts"],"sourcesContent":["import { input, select } from \"@inquirer/prompts\";\nimport chalk from \"chalk\";\nimport { Option, program } from \"commander\";\nimport debug from \"debug\";\nimport ora from \"ora\";\n\nimport { getClient } from \"../utils/index.js\";\n\nconst log = debug(\"ef:cli:auth\");\n\nexport interface APITestWebhhokResult {\n message: string;\n}\nconst topics = [\n \"render.created\",\n \"render.rendering\",\n \"render.pending\",\n \"render.failed\",\n \"render.completed\",\n];\n\nexport const testWebhookURL = async ({\n webhookURL,\n topic,\n}: {\n webhookURL: string;\n topic: string;\n}) => {\n const response = await getClient().authenticatedFetch(\"/api/v1/test_webhook\", {\n method: \"POST\",\n body: JSON.stringify({\n webhookURL,\n topic,\n }),\n });\n return response.json() as Promise<APITestWebhhokResult>;\n};\n\nconst webhookCommand = program\n .command(\"webhook\")\n .description(\"Test webhook URL with a topic\")\n .option(\"-u, --webhookURL <webhookURL>\", \"Webhook URL\")\n .addOption(new Option(\"-t, --topic <topic>\", \"Topic\").choices(topics))\n .action(async () => {\n const options = webhookCommand.opts();\n log(\"Options:\", options);\n let { webhookURL, topic } = options;\n\n if (!webhookURL) {\n const answer = await input({ message: \"Enter a webhook URL:\" });\n webhookURL = answer;\n }\n\n if (!topic) {\n const answer = await select({\n message: \"Select a topic:\",\n choices: topics.map((topic) => ({ title: topic, value: topic })),\n });\n topic = answer;\n }\n\n const spinner = ora(\"Testing...\").start();\n try {\n const apiData = await testWebhookURL({ webhookURL, topic });\n spinner.succeed(\"Webhook URL is working! 🎉\");\n process.stderr.write(chalk.green(`${apiData.message}\\n`));\n } catch (error: any) {\n spinner.fail(\"Webhook URL is not working!\");\n process.stderr.write(error?.message);\n process.stderr.write(\"\\n\");\n log(\"Error:\", error);\n }\n });\n"],"mappings":";;;;;;;;AAQA,MAAM,MAAM,MAAM,cAAc;AAKhC,MAAM,SAAS;CACb;CACA;CACA;CACA;CACA;CACD;AAED,MAAa,iBAAiB,OAAO,EACnC,YACA,YAII;AAQJ,SAPiB,MAAM,WAAW,CAAC,mBAAmB,wBAAwB;EAC5E,QAAQ;EACR,MAAM,KAAK,UAAU;GACnB;GACA;GACD,CAAC;EACH,CAAC,EACc,MAAM;;AAGxB,MAAM,iBAAiB,QACpB,QAAQ,UAAU,CAClB,YAAY,gCAAgC,CAC5C,OAAO,iCAAiC,cAAc,CACtD,UAAU,IAAI,OAAO,uBAAuB,QAAQ,CAAC,QAAQ,OAAO,CAAC,CACrE,OAAO,YAAY;CAClB,MAAM,UAAU,eAAe,MAAM;AACrC,KAAI,YAAY,QAAQ;CACxB,IAAI,EAAE,YAAY,UAAU;AAE5B,KAAI,CAAC,WAEH,cADe,MAAM,MAAM,EAAE,SAAS,wBAAwB,CAAC;AAIjE,KAAI,CAAC,MAKH,SAJe,MAAM,OAAO;EAC1B,SAAS;EACT,SAAS,OAAO,KAAK,aAAW;GAAE,OAAOA;GAAO,OAAOA;GAAO,EAAE;EACjE,CAAC;CAIJ,MAAM,UAAU,IAAI,aAAa,CAAC,OAAO;AACzC,KAAI;EACF,MAAM,UAAU,MAAM,eAAe;GAAE;GAAY;GAAO,CAAC;AAC3D,UAAQ,QAAQ,6BAA6B;AAC7C,UAAQ,OAAO,MAAM,MAAM,MAAM,GAAG,QAAQ,QAAQ,IAAI,CAAC;UAClDC,OAAY;AACnB,UAAQ,KAAK,8BAA8B;AAC3C,UAAQ,OAAO,MAAM,OAAO,QAAQ;AACpC,UAAQ,OAAO,MAAM,KAAK;AAC1B,MAAI,UAAU,MAAM;;EAEtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processRenderInfo.js","names":[],"sources":["../../src/operations/processRenderInfo.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"processRenderInfo.js","names":[],"sources":["../../src/operations/processRenderInfo.ts"],"sourcesContent":["import { cacheImage, findOrCreateCaptions, generateTrack } from \"@editframe/assets\";\nimport type { getRenderInfo } from \"@editframe/elements/node\";\n\nexport const processRenderInfo = async (renderInfo: Awaited<ReturnType<typeof getRenderInfo>>) => {\n for (const [src, tracks] of Object.entries(renderInfo.assets.efMedia)) {\n process.stderr.write(\"Processing media asset: \");\n process.stderr.write(src);\n process.stderr.write(\"\\n\");\n for (const trackId in tracks) {\n process.stderr.write(\"Generating track: \");\n process.stderr.write(trackId);\n process.stderr.write(\"\\n\");\n await generateTrack(\"./src/assets\", `./src${src}`, `src?trackId=${trackId}`);\n }\n }\n\n for (const imageAsset of renderInfo.assets.efImage) {\n process.stderr.write(\"Processing image asset: \");\n process.stderr.write(imageAsset);\n process.stderr.write(\"\\n\");\n await cacheImage(\"./src/assets\", `./src${imageAsset}`);\n }\n\n for (const captionsAsset of renderInfo.assets.efCaptions) {\n process.stderr.write(\"Processing captions asset: \");\n process.stderr.write(captionsAsset);\n process.stderr.write(\"\\n\");\n await findOrCreateCaptions(\"./src/assets\", `./src${captionsAsset}`);\n }\n};\n"],"mappings":";;;AAGA,MAAa,oBAAoB,OAAO,eAA0D;AAChG,MAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,WAAW,OAAO,QAAQ,EAAE;AACrE,UAAQ,OAAO,MAAM,2BAA2B;AAChD,UAAQ,OAAO,MAAM,IAAI;AACzB,UAAQ,OAAO,MAAM,KAAK;AAC1B,OAAK,MAAM,WAAW,QAAQ;AAC5B,WAAQ,OAAO,MAAM,qBAAqB;AAC1C,WAAQ,OAAO,MAAM,QAAQ;AAC7B,WAAQ,OAAO,MAAM,KAAK;AAC1B,SAAM,cAAc,gBAAgB,QAAQ,OAAO,eAAe,UAAU;;;AAIhF,MAAK,MAAM,cAAc,WAAW,OAAO,SAAS;AAClD,UAAQ,OAAO,MAAM,2BAA2B;AAChD,UAAQ,OAAO,MAAM,WAAW;AAChC,UAAQ,OAAO,MAAM,KAAK;AAC1B,QAAM,WAAW,gBAAgB,QAAQ,aAAa;;AAGxD,MAAK,MAAM,iBAAiB,WAAW,OAAO,YAAY;AACxD,UAAQ,OAAO,MAAM,8BAA8B;AACnD,UAAQ,OAAO,MAAM,cAAc;AACnC,UAAQ,OAAO,MAAM,KAAK;AAC1B,QAAM,qBAAqB,gBAAgB,QAAQ,gBAAgB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncCaption.js","names":["path: string","md5: string"],"sources":["../../../src/operations/syncAssetsDirectory/SyncCaption.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport { basename } from \"node:path\";\n\nimport { Readable } from \"node:stream\";\nimport {\n type CreateFileResult,\n createFile,\n type LookupFileByMd5Result,\n lookupFileByMd5,\n uploadFile,\n} from \"@editframe/api\";\nimport { createReadableStreamFromReadable } from \"../../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../../utils/index.js\";\nimport type { SubAssetSync } from \"./SubAssetSync.js\";\nimport { SyncStatus } from \"./SyncStatus.js\";\nexport class SyncCaption implements SubAssetSync<CreateFileResult> {\n icon = \"📝\";\n label = \"captions\";\n syncStatus: SyncStatus = new SyncStatus(this.path);\n created: CreateFileResult | LookupFileByMd5Result | null = null;\n constructor(\n public path: string,\n public md5: string,\n ) {}\n\n async byteSize() {\n return (await fs.stat(this.path)).size;\n }\n\n async prepare() {}\n\n async validate() {}\n\n async create() {\n const maybeFile = await lookupFileByMd5(getClient(), this.md5);\n if (maybeFile) {\n this.created = maybeFile;\n } else {\n this.created = await createFile(getClient(), {\n md5: this.md5,\n filename: basename(this.path).replace(/\\.captions.json$/, \"\"),\n type: \"caption\",\n byte_size: await this.byteSize(),\n });\n }\n }\n\n isComplete() {\n return this.created?.status === \"ready\";\n }\n\n async upload() {\n if (!this.created) {\n throw new Error(\
|
|
1
|
+
{"version":3,"file":"SyncCaption.js","names":["path: string","md5: string"],"sources":["../../../src/operations/syncAssetsDirectory/SyncCaption.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport { basename } from \"node:path\";\n\nimport { Readable } from \"node:stream\";\nimport {\n type CreateFileResult,\n createFile,\n type LookupFileByMd5Result,\n lookupFileByMd5,\n uploadFile,\n} from \"@editframe/api\";\nimport { createReadableStreamFromReadable } from \"../../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../../utils/index.js\";\nimport type { SubAssetSync } from \"./SubAssetSync.js\";\nimport { SyncStatus } from \"./SyncStatus.js\";\nexport class SyncCaption implements SubAssetSync<CreateFileResult> {\n icon = \"📝\";\n label = \"captions\";\n syncStatus: SyncStatus = new SyncStatus(this.path);\n created: CreateFileResult | LookupFileByMd5Result | null = null;\n constructor(\n public path: string,\n public md5: string,\n ) {}\n\n async byteSize() {\n return (await fs.stat(this.path)).size;\n }\n\n async prepare() {}\n\n async validate() {}\n\n async create() {\n const maybeFile = await lookupFileByMd5(getClient(), this.md5);\n if (maybeFile) {\n this.created = maybeFile;\n } else {\n this.created = await createFile(getClient(), {\n md5: this.md5,\n filename: basename(this.path).replace(/\\.captions.json$/, \"\"),\n type: \"caption\",\n byte_size: await this.byteSize(),\n });\n }\n }\n\n isComplete() {\n return this.created?.status === \"ready\";\n }\n\n async upload() {\n if (!this.created) {\n throw new Error(\"Caption not created. Should have been prevented by .isComplete()\");\n }\n await uploadFile(\n getClient(),\n {\n id: this.created.id,\n byte_size: await this.byteSize(),\n type: \"caption\",\n },\n // It's not clear why we need to use Readable.from here, but it seems\n // to fix an issue where the request is closed early in tests\n createReadableStreamFromReadable(Readable.from(await fs.readFile(this.path))),\n ).whenUploaded();\n }\n\n async markSynced() {\n if (!this.created) {\n throw new Error(\"Caption not created. Should have been prevented by .isComplete()\");\n }\n const byteSize = await this.byteSize();\n await this.syncStatus.markSynced({\n version: \"1\",\n complete: true,\n id: this.created.id,\n md5: this.md5,\n byte_size: byteSize,\n });\n }\n}\n"],"mappings":";;;;;;;;;AAeA,IAAa,cAAb,MAAmE;CAKjE,YACE,AAAOA,QACP,AAAOC,KACP;EAFO;EACA;cANF;eACC;oBACiB,IAAI,WAAW,KAAK,KAAK;iBACS;;CAM3D,MAAM,WAAW;AACf,UAAQ,MAAM,GAAG,KAAK,KAAK,KAAK,EAAE;;CAGpC,MAAM,UAAU;CAEhB,MAAM,WAAW;CAEjB,MAAM,SAAS;EACb,MAAM,YAAY,MAAM,gBAAgB,WAAW,EAAE,KAAK,IAAI;AAC9D,MAAI,UACF,MAAK,UAAU;MAEf,MAAK,UAAU,MAAM,WAAW,WAAW,EAAE;GAC3C,KAAK,KAAK;GACV,UAAU,SAAS,KAAK,KAAK,CAAC,QAAQ,oBAAoB,GAAG;GAC7D,MAAM;GACN,WAAW,MAAM,KAAK,UAAU;GACjC,CAAC;;CAIN,aAAa;AACX,SAAO,KAAK,SAAS,WAAW;;CAGlC,MAAM,SAAS;AACb,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,mEAAmE;AAErF,QAAM,WACJ,WAAW,EACX;GACE,IAAI,KAAK,QAAQ;GACjB,WAAW,MAAM,KAAK,UAAU;GAChC,MAAM;GACP,EAGD,iCAAiC,SAAS,KAAK,MAAM,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,CAC9E,CAAC,cAAc;;CAGlB,MAAM,aAAa;AACjB,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,mEAAmE;EAErF,MAAM,WAAW,MAAM,KAAK,UAAU;AACtC,QAAM,KAAK,WAAW,WAAW;GAC/B,SAAS;GACT,UAAU;GACV,IAAI,KAAK,QAAQ;GACjB,KAAK,KAAK;GACV,WAAW;GACZ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncFragmentIndex.js","names":["path: string","md5: string"],"sources":["../../../src/operations/syncAssetsDirectory/SyncFragmentIndex.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { Readable } from \"node:stream\";\n\nimport {\n type CreateFileResult,\n createFile,\n type LookupFileByMd5Result,\n lookupFileByMd5,\n uploadFileIndex,\n} from \"@editframe/api\";\n\nimport { createReadableStreamFromReadable } from \"../../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../../utils/index.js\";\nimport type { SubAssetSync } from \"./SubAssetSync.js\";\nimport { SyncStatus } from \"./SyncStatus.js\";\n\nexport class SyncFragmentIndex implements SubAssetSync<CreateFileResult> {\n icon = \"📋\";\n label = \"fragment index\";\n syncStatus = new SyncStatus(this.path);\n fileSyncStatus = new SyncStatus(join(dirname(this.path), \"isobmff\"));\n created: CreateFileResult | LookupFileByMd5Result | null = null;\n\n constructor(\n public path: string,\n public md5: string,\n ) {}\n\n async byteSize() {\n return (await fs.stat(this.path)).size;\n }\n\n async prepare() {}\n\n async validate() {}\n\n async create() {\n const maybeFile = await lookupFileByMd5(getClient(), this.md5);\n if (maybeFile) {\n this.created = maybeFile;\n } else {\n this.created = await createFile(getClient(), {\n md5: this.md5,\n filename: basename(this.path).replace(/\\.tracks.json$/, \"\"),\n type: \"video\",\n byte_size: await this.byteSize(),\n });\n }\n }\n\n isComplete() {\n return this.created?.status === \"ready\";\n }\n\n async upload() {\n if (!this.created) {\n throw new Error(\
|
|
1
|
+
{"version":3,"file":"SyncFragmentIndex.js","names":["path: string","md5: string"],"sources":["../../../src/operations/syncAssetsDirectory/SyncFragmentIndex.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { Readable } from \"node:stream\";\n\nimport {\n type CreateFileResult,\n createFile,\n type LookupFileByMd5Result,\n lookupFileByMd5,\n uploadFileIndex,\n} from \"@editframe/api\";\n\nimport { createReadableStreamFromReadable } from \"../../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../../utils/index.js\";\nimport type { SubAssetSync } from \"./SubAssetSync.js\";\nimport { SyncStatus } from \"./SyncStatus.js\";\n\nexport class SyncFragmentIndex implements SubAssetSync<CreateFileResult> {\n icon = \"📋\";\n label = \"fragment index\";\n syncStatus = new SyncStatus(this.path);\n fileSyncStatus = new SyncStatus(join(dirname(this.path), \"isobmff\"));\n created: CreateFileResult | LookupFileByMd5Result | null = null;\n\n constructor(\n public path: string,\n public md5: string,\n ) {}\n\n async byteSize() {\n return (await fs.stat(this.path)).size;\n }\n\n async prepare() {}\n\n async validate() {}\n\n async create() {\n const maybeFile = await lookupFileByMd5(getClient(), this.md5);\n if (maybeFile) {\n this.created = maybeFile;\n } else {\n this.created = await createFile(getClient(), {\n md5: this.md5,\n filename: basename(this.path).replace(/\\.tracks.json$/, \"\"),\n type: \"video\",\n byte_size: await this.byteSize(),\n });\n }\n }\n\n isComplete() {\n return this.created?.status === \"ready\";\n }\n\n async upload() {\n if (!this.created) {\n throw new Error(\"Fragment index not created. Should have been prevented by .isComplete()\");\n }\n await uploadFileIndex(\n getClient(),\n this.created.id,\n // It is unclear why we need to use Readable.from here\n // Tests fail when using createReadStream\n createReadableStreamFromReadable(Readable.from(await fs.readFile(this.path))),\n await this.byteSize(),\n );\n }\n\n async markSynced() {\n if (!this.created) {\n throw new Error(\"Fragment index not created. Should have been prevented by .isComplete()\");\n }\n const byteSize = await this.byteSize();\n await Promise.all([\n this.syncStatus.markSynced({\n version: \"1\",\n complete: true,\n id: this.created.id,\n md5: this.md5,\n byte_size: byteSize,\n }),\n this.fileSyncStatus.markSynced({\n version: \"1\",\n complete: true,\n id: this.created.id,\n md5: this.md5,\n byte_size: byteSize,\n }),\n ]);\n }\n}\n"],"mappings":";;;;;;;;;AAiBA,IAAa,oBAAb,MAAyE;CAOvE,YACE,AAAOA,QACP,AAAOC,KACP;EAFO;EACA;cARF;eACC;oBACK,IAAI,WAAW,KAAK,KAAK;wBACrB,IAAI,WAAW,KAAK,QAAQ,KAAK,KAAK,EAAE,UAAU,CAAC;iBACT;;CAO3D,MAAM,WAAW;AACf,UAAQ,MAAM,GAAG,KAAK,KAAK,KAAK,EAAE;;CAGpC,MAAM,UAAU;CAEhB,MAAM,WAAW;CAEjB,MAAM,SAAS;EACb,MAAM,YAAY,MAAM,gBAAgB,WAAW,EAAE,KAAK,IAAI;AAC9D,MAAI,UACF,MAAK,UAAU;MAEf,MAAK,UAAU,MAAM,WAAW,WAAW,EAAE;GAC3C,KAAK,KAAK;GACV,UAAU,SAAS,KAAK,KAAK,CAAC,QAAQ,kBAAkB,GAAG;GAC3D,MAAM;GACN,WAAW,MAAM,KAAK,UAAU;GACjC,CAAC;;CAIN,aAAa;AACX,SAAO,KAAK,SAAS,WAAW;;CAGlC,MAAM,SAAS;AACb,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,0EAA0E;AAE5F,QAAM,gBACJ,WAAW,EACX,KAAK,QAAQ,IAGb,iCAAiC,SAAS,KAAK,MAAM,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,EAC7E,MAAM,KAAK,UAAU,CACtB;;CAGH,MAAM,aAAa;AACjB,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,0EAA0E;EAE5F,MAAM,WAAW,MAAM,KAAK,UAAU;AACtC,QAAM,QAAQ,IAAI,CAChB,KAAK,WAAW,WAAW;GACzB,SAAS;GACT,UAAU;GACV,IAAI,KAAK,QAAQ;GACjB,KAAK,KAAK;GACV,WAAW;GACZ,CAAC,EACF,KAAK,eAAe,WAAW;GAC7B,SAAS;GACT,UAAU;GACV,IAAI,KAAK,QAAQ;GACjB,KAAK,KAAK;GACV,WAAW;GACZ,CAAC,CACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncImage.js","names":["path: string","md5: string"],"sources":["../../../src/operations/syncAssetsDirectory/SyncImage.ts"],"sourcesContent":["import { createReadStream } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path, { basename } from \"node:path\";\n\nimport {\n type CreateFileResult,\n createFile,\n type LookupFileByMd5Result,\n lookupFileByMd5,\n uploadFile,\n} from \"@editframe/api\";\n\nimport { Probe } from \"@editframe/assets\";\n\nimport { createReadableStreamFromReadable } from \"../../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../../utils/index.js\";\nimport type { SubAssetSync } from \"./SubAssetSync.js\";\nimport { SyncStatus } from \"./SyncStatus.js\";\n\nexport class SyncImage implements SubAssetSync<CreateFileResult> {\n icon = \"🖼️\";\n label = \"image\";\n syncStatus: SyncStatus = new SyncStatus(this.path);\n created: CreateFileResult | LookupFileByMd5Result | null = null;\n\n constructor(\n public path: string,\n public md5: string,\n ) {}\n\n private _probeResult: Probe | null = null;\n\n async prepare() {\n this._probeResult = await Probe.probePath(this.path);\n }\n\n get probeResult() {\n if (!this._probeResult) {\n throw new Error(\"Probe result not found. Call prepare() first.\");\n }\n return this._probeResult;\n }\n\n get extension() {\n return path.extname(this.path).slice(1);\n }\n\n async byteSize() {\n return (await fs.stat(this.path)).size;\n }\n\n async validate() {\n const [videoProbe] = this.probeResult.videoStreams;\n if (!videoProbe) {\n throw new Error(`No media info found in image: ${this.path}`);\n }\n const ext = this.extension;\n if (!(ext === \"jpg\" || ext === \"jpeg\" || ext === \"png\" || ext === \"webp\")) {\n throw new Error(`Invalid image format: ${this.path}`);\n }\n }\n async create() {\n const byteSize = (await fs.stat(this.path)).size;\n const [videoProbe] = this.probeResult.videoStreams;\n if (!videoProbe) {\n throw new Error(\
|
|
1
|
+
{"version":3,"file":"SyncImage.js","names":["path: string","md5: string"],"sources":["../../../src/operations/syncAssetsDirectory/SyncImage.ts"],"sourcesContent":["import { createReadStream } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path, { basename } from \"node:path\";\n\nimport {\n type CreateFileResult,\n createFile,\n type LookupFileByMd5Result,\n lookupFileByMd5,\n uploadFile,\n} from \"@editframe/api\";\n\nimport { Probe } from \"@editframe/assets\";\n\nimport { createReadableStreamFromReadable } from \"../../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../../utils/index.js\";\nimport type { SubAssetSync } from \"./SubAssetSync.js\";\nimport { SyncStatus } from \"./SyncStatus.js\";\n\nexport class SyncImage implements SubAssetSync<CreateFileResult> {\n icon = \"🖼️\";\n label = \"image\";\n syncStatus: SyncStatus = new SyncStatus(this.path);\n created: CreateFileResult | LookupFileByMd5Result | null = null;\n\n constructor(\n public path: string,\n public md5: string,\n ) {}\n\n private _probeResult: Probe | null = null;\n\n async prepare() {\n this._probeResult = await Probe.probePath(this.path);\n }\n\n get probeResult() {\n if (!this._probeResult) {\n throw new Error(\"Probe result not found. Call prepare() first.\");\n }\n return this._probeResult;\n }\n\n get extension() {\n return path.extname(this.path).slice(1);\n }\n\n async byteSize() {\n return (await fs.stat(this.path)).size;\n }\n\n async validate() {\n const [videoProbe] = this.probeResult.videoStreams;\n if (!videoProbe) {\n throw new Error(`No media info found in image: ${this.path}`);\n }\n const ext = this.extension;\n if (!(ext === \"jpg\" || ext === \"jpeg\" || ext === \"png\" || ext === \"webp\")) {\n throw new Error(`Invalid image format: ${this.path}`);\n }\n }\n async create() {\n const byteSize = (await fs.stat(this.path)).size;\n const [videoProbe] = this.probeResult.videoStreams;\n if (!videoProbe) {\n throw new Error(\"No video stream found in image. Should have been prevented by .validate()\");\n }\n\n const maybeFile = await lookupFileByMd5(getClient(), this.md5);\n if (maybeFile) {\n this.created = maybeFile;\n } else {\n this.created = await createFile(getClient(), {\n md5: this.md5,\n filename: basename(this.path),\n type: \"image\",\n mime_type: `image/${this.extension}`,\n byte_size: byteSize,\n });\n }\n }\n isComplete() {\n return this.created?.status === \"ready\";\n }\n async upload() {\n if (!this.created) {\n throw new Error(\"Image not created. Should have been prevented by .isComplete()\");\n }\n await uploadFile(\n getClient(),\n {\n id: this.created.id,\n byte_size: Number.parseInt(this.probeResult.format.size || \"0\", 10),\n type: \"image\",\n },\n createReadableStreamFromReadable(createReadStream(this.path)),\n ).whenUploaded();\n }\n async markSynced() {\n if (!this.created) {\n throw new Error(\"Image not created. Should have been prevented by .isComplete()\");\n }\n const byteSize = await this.byteSize();\n return this.syncStatus.markSynced({\n version: \"1\",\n complete: true,\n id: this.created.id,\n md5: this.md5,\n byte_size: byteSize,\n });\n }\n}\n"],"mappings":";;;;;;;;;;AAmBA,IAAa,YAAb,MAAiE;CAM/D,YACE,AAAOA,QACP,AAAOC,KACP;EAFO;EACA;cAPF;eACC;oBACiB,IAAI,WAAW,KAAK,KAAK;iBACS;sBAOtB;;CAErC,MAAM,UAAU;AACd,OAAK,eAAe,MAAM,MAAM,UAAU,KAAK,KAAK;;CAGtD,IAAI,cAAc;AAChB,MAAI,CAAC,KAAK,aACR,OAAM,IAAI,MAAM,gDAAgD;AAElE,SAAO,KAAK;;CAGd,IAAI,YAAY;AACd,SAAO,KAAK,QAAQ,KAAK,KAAK,CAAC,MAAM,EAAE;;CAGzC,MAAM,WAAW;AACf,UAAQ,MAAM,GAAG,KAAK,KAAK,KAAK,EAAE;;CAGpC,MAAM,WAAW;EACf,MAAM,CAAC,cAAc,KAAK,YAAY;AACtC,MAAI,CAAC,WACH,OAAM,IAAI,MAAM,iCAAiC,KAAK,OAAO;EAE/D,MAAM,MAAM,KAAK;AACjB,MAAI,EAAE,QAAQ,SAAS,QAAQ,UAAU,QAAQ,SAAS,QAAQ,QAChE,OAAM,IAAI,MAAM,yBAAyB,KAAK,OAAO;;CAGzD,MAAM,SAAS;EACb,MAAM,YAAY,MAAM,GAAG,KAAK,KAAK,KAAK,EAAE;EAC5C,MAAM,CAAC,cAAc,KAAK,YAAY;AACtC,MAAI,CAAC,WACH,OAAM,IAAI,MAAM,4EAA4E;EAG9F,MAAM,YAAY,MAAM,gBAAgB,WAAW,EAAE,KAAK,IAAI;AAC9D,MAAI,UACF,MAAK,UAAU;MAEf,MAAK,UAAU,MAAM,WAAW,WAAW,EAAE;GAC3C,KAAK,KAAK;GACV,UAAU,SAAS,KAAK,KAAK;GAC7B,MAAM;GACN,WAAW,SAAS,KAAK;GACzB,WAAW;GACZ,CAAC;;CAGN,aAAa;AACX,SAAO,KAAK,SAAS,WAAW;;CAElC,MAAM,SAAS;AACb,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,iEAAiE;AAEnF,QAAM,WACJ,WAAW,EACX;GACE,IAAI,KAAK,QAAQ;GACjB,WAAW,OAAO,SAAS,KAAK,YAAY,OAAO,QAAQ,KAAK,GAAG;GACnE,MAAM;GACP,EACD,iCAAiC,iBAAiB,KAAK,KAAK,CAAC,CAC9D,CAAC,cAAc;;CAElB,MAAM,aAAa;AACjB,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,iEAAiE;EAEnF,MAAM,WAAW,MAAM,KAAK,UAAU;AACtC,SAAO,KAAK,WAAW,WAAW;GAChC,SAAS;GACT,UAAU;GACV,IAAI,KAAK,QAAQ;GACjB,KAAK,KAAK;GACV,WAAW;GACZ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncStatus.js","names":["basePath: string"],"sources":["../../../src/operations/syncAssetsDirectory/SyncStatus.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\n\nimport { z } from \"zod\";\n\nconst SYNC_VERSION = \"1\";\n\nconst SyncStatusSchema = z.object({\n version: z.string(),\n complete: z.boolean(),\n id: z.string(),\n md5: z.string(),\n byte_size: z.number(),\n});\n\nexport interface SyncStatusInfo extends z.infer<typeof SyncStatusSchema> {}\n\nexport class SyncStatus {\n infoPath = `${this.basePath}.info`;\n\n constructor(private basePath: string) {}\n\n async isSynced() {\n const syncInfo = await this.readInfo();\n if (!syncInfo) {\n return false;\n }\n return syncInfo.version === SYNC_VERSION && syncInfo.complete;\n }\n\n async readInfo() {\n try {\n const info = await fs.readFile(this.infoPath, \"utf-8\");\n return SyncStatusSchema.parse(JSON.parse(info));\n } catch (error) {\n if (
|
|
1
|
+
{"version":3,"file":"SyncStatus.js","names":["basePath: string"],"sources":["../../../src/operations/syncAssetsDirectory/SyncStatus.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\n\nimport { z } from \"zod\";\n\nconst SYNC_VERSION = \"1\";\n\nconst SyncStatusSchema = z.object({\n version: z.string(),\n complete: z.boolean(),\n id: z.string(),\n md5: z.string(),\n byte_size: z.number(),\n});\n\nexport interface SyncStatusInfo extends z.infer<typeof SyncStatusSchema> {}\n\nexport class SyncStatus {\n infoPath = `${this.basePath}.info`;\n\n constructor(private basePath: string) {}\n\n async isSynced() {\n const syncInfo = await this.readInfo();\n if (!syncInfo) {\n return false;\n }\n return syncInfo.version === SYNC_VERSION && syncInfo.complete;\n }\n\n async readInfo() {\n try {\n const info = await fs.readFile(this.infoPath, \"utf-8\");\n return SyncStatusSchema.parse(JSON.parse(info));\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return null;\n }\n throw error;\n }\n }\n\n async markSynced(info: SyncStatusInfo) {\n await fs.writeFile(this.infoPath, JSON.stringify(info, null, 2), \"utf-8\");\n }\n}\n"],"mappings":";;;;AAIA,MAAM,eAAe;AAErB,MAAM,mBAAmB,EAAE,OAAO;CAChC,SAAS,EAAE,QAAQ;CACnB,UAAU,EAAE,SAAS;CACrB,IAAI,EAAE,QAAQ;CACd,KAAK,EAAE,QAAQ;CACf,WAAW,EAAE,QAAQ;CACtB,CAAC;AAIF,IAAa,aAAb,MAAwB;CAGtB,YAAY,AAAQA,UAAkB;EAAlB;kBAFT,GAAG,KAAK,SAAS;;CAI5B,MAAM,WAAW;EACf,MAAM,WAAW,MAAM,KAAK,UAAU;AACtC,MAAI,CAAC,SACH,QAAO;AAET,SAAO,SAAS,YAAY,gBAAgB,SAAS;;CAGvD,MAAM,WAAW;AACf,MAAI;GACF,MAAM,OAAO,MAAM,GAAG,SAAS,KAAK,UAAU,QAAQ;AACtD,UAAO,iBAAiB,MAAM,KAAK,MAAM,KAAK,CAAC;WACxC,OAAO;AACd,OAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,SAC9D,QAAO;AAET,SAAM;;;CAIV,MAAM,WAAW,MAAsB;AACrC,QAAM,GAAG,UAAU,KAAK,UAAU,KAAK,UAAU,MAAM,MAAM,EAAE,EAAE,QAAQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncTrack.js","names":["path: string","md5: string","createPayload: CreateISOBMFFTrackPayload"],"sources":["../../../src/operations/syncAssetsDirectory/SyncTrack.ts"],"sourcesContent":["import { createReadStream } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\n\nimport {\n type CreateFileResult,\n type CreateISOBMFFTrackPayload,\n type CreateISOBMFFTrackResult,\n createFile,\n createFileTrack,\n type LookupFileByMd5Result,\n lookupFileByMd5,\n uploadFileTrack,\n} from \"@editframe/api\";\nimport { Probe } from \"@editframe/assets\";\n\nimport { createReadableStreamFromReadable } from \"../../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../../utils/index.js\";\nimport type { SubAssetSync } from \"./SubAssetSync.js\";\nimport { SyncStatus } from \"./SyncStatus.js\";\n\nexport class SyncTrack implements SubAssetSync<CreateISOBMFFTrackResult> {\n icon = \"📼\";\n label = \"track\";\n syncStatus = new SyncStatus(this.path);\n fileSyncStatus = new SyncStatus(join(dirname(this.path), \"isobmff\"));\n created: CreateISOBMFFTrackResult | null = null;\n\n constructor(\n public path: string,\n public md5: string,\n ) {}\n\n private _videoFile: CreateFileResult | LookupFileByMd5Result | null = null;\n\n get videoFile() {\n if (this._videoFile) {\n return this._videoFile;\n }\n throw new Error(\"Video file not found. Call prepare() first.\");\n }\n\n async byteSize() {\n return (await fs.stat(this.path)).size;\n }\n\n private _probeResult: Probe | null = null;\n get probeResult() {\n if (this._probeResult) {\n return this._probeResult;\n }\n throw new Error(\"Probe result not found. Call prepare() first.\");\n }\n\n get track() {\n const [track] = this.probeResult.streams;\n if (track) {\n return track;\n }\n throw new Error(`No track found in track: ${this.path}`);\n }\n\n async prepare() {\n const maybeFile = await lookupFileByMd5(getClient(), this.md5);\n if (maybeFile) {\n this._videoFile = maybeFile;\n } else {\n this._videoFile = await createFile(getClient(), {\n md5: this.md5,\n filename: basename(this.path).replace(/\\.track-[\\d]+.mp4$/, \"\"),\n type: \"video\",\n byte_size: await this.byteSize(),\n });\n }\n this._probeResult = await Probe.probePath(this.path);\n }\n\n get trackId() {\n const trackId = this.path.match(/track-([\\d]+).mp4/)?.[1];\n if (!trackId) {\n throw new Error(`No track ID found for track: ${this.path}`);\n }\n return trackId;\n }\n\n get trackDuration() {\n const track = this.track;\n if (!track.duration) {\n throw new Error(`No duration found in track: ${this.path}`);\n }\n if (typeof track.duration === \"string\") {\n return Number.parseFloat(track.duration);\n }\n return track.duration;\n }\n\n async validate() {\n this.trackId;\n this.videoFile;\n this.trackDuration;\n }\n\n async create(): Promise<void> {\n const track = this.track;\n const videoFile = this.videoFile;\n\n if (track.codec_type === \"data\") {\n throw new Error(`Unsupported codec type: ${track.codec_type}`);\n }\n const createPayload: CreateISOBMFFTrackPayload =\n track.codec_type === \"audio\"\n ? {\n type: track.codec_type,\n file_id: videoFile.id,\n track_id: Number(this.trackId),\n probe_info: track,\n duration_ms: Math.round(this.trackDuration * 1000),\n codec_name: track.codec_name,\n byte_size: await this.byteSize(),\n }\n : {\n type: track.codec_type,\n file_id: videoFile.id,\n track_id: Number(this.trackId),\n probe_info: track,\n duration_ms: Math.round(this.trackDuration * 1000),\n codec_name: track.codec_name,\n byte_size: await this.byteSize(),\n };\n\n this.created = await createFileTrack(
|
|
1
|
+
{"version":3,"file":"SyncTrack.js","names":["path: string","md5: string","createPayload: CreateISOBMFFTrackPayload"],"sources":["../../../src/operations/syncAssetsDirectory/SyncTrack.ts"],"sourcesContent":["import { createReadStream } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\n\nimport {\n type CreateFileResult,\n type CreateISOBMFFTrackPayload,\n type CreateISOBMFFTrackResult,\n createFile,\n createFileTrack,\n type LookupFileByMd5Result,\n lookupFileByMd5,\n uploadFileTrack,\n} from \"@editframe/api\";\nimport { Probe } from \"@editframe/assets\";\n\nimport { createReadableStreamFromReadable } from \"../../utils/createReadableStreamFromReadable.js\";\nimport { getClient } from \"../../utils/index.js\";\nimport type { SubAssetSync } from \"./SubAssetSync.js\";\nimport { SyncStatus } from \"./SyncStatus.js\";\n\nexport class SyncTrack implements SubAssetSync<CreateISOBMFFTrackResult> {\n icon = \"📼\";\n label = \"track\";\n syncStatus = new SyncStatus(this.path);\n fileSyncStatus = new SyncStatus(join(dirname(this.path), \"isobmff\"));\n created: CreateISOBMFFTrackResult | null = null;\n\n constructor(\n public path: string,\n public md5: string,\n ) {}\n\n private _videoFile: CreateFileResult | LookupFileByMd5Result | null = null;\n\n get videoFile() {\n if (this._videoFile) {\n return this._videoFile;\n }\n throw new Error(\"Video file not found. Call prepare() first.\");\n }\n\n async byteSize() {\n return (await fs.stat(this.path)).size;\n }\n\n private _probeResult: Probe | null = null;\n get probeResult() {\n if (this._probeResult) {\n return this._probeResult;\n }\n throw new Error(\"Probe result not found. Call prepare() first.\");\n }\n\n get track() {\n const [track] = this.probeResult.streams;\n if (track) {\n return track;\n }\n throw new Error(`No track found in track: ${this.path}`);\n }\n\n async prepare() {\n const maybeFile = await lookupFileByMd5(getClient(), this.md5);\n if (maybeFile) {\n this._videoFile = maybeFile;\n } else {\n this._videoFile = await createFile(getClient(), {\n md5: this.md5,\n filename: basename(this.path).replace(/\\.track-[\\d]+.mp4$/, \"\"),\n type: \"video\",\n byte_size: await this.byteSize(),\n });\n }\n this._probeResult = await Probe.probePath(this.path);\n }\n\n get trackId() {\n const trackId = this.path.match(/track-([\\d]+).mp4/)?.[1];\n if (!trackId) {\n throw new Error(`No track ID found for track: ${this.path}`);\n }\n return trackId;\n }\n\n get trackDuration() {\n const track = this.track;\n if (!track.duration) {\n throw new Error(`No duration found in track: ${this.path}`);\n }\n if (typeof track.duration === \"string\") {\n return Number.parseFloat(track.duration);\n }\n return track.duration;\n }\n\n async validate() {\n void this.trackId;\n void this.videoFile;\n void this.trackDuration;\n }\n\n async create(): Promise<void> {\n const track = this.track;\n const videoFile = this.videoFile;\n\n if (track.codec_type === \"data\") {\n throw new Error(`Unsupported codec type: ${track.codec_type}`);\n }\n const createPayload: CreateISOBMFFTrackPayload =\n track.codec_type === \"audio\"\n ? {\n type: track.codec_type,\n file_id: videoFile.id,\n track_id: Number(this.trackId),\n probe_info: track,\n duration_ms: Math.round(this.trackDuration * 1000),\n codec_name: track.codec_name,\n byte_size: await this.byteSize(),\n }\n : {\n type: track.codec_type,\n file_id: videoFile.id,\n track_id: Number(this.trackId),\n probe_info: track,\n duration_ms: Math.round(this.trackDuration * 1000),\n codec_name: track.codec_name,\n byte_size: await this.byteSize(),\n };\n\n this.created = await createFileTrack(getClient(), videoFile.id, createPayload);\n }\n isComplete() {\n return !!this.created?.complete;\n }\n async upload() {\n if (!this.created) {\n throw new Error(\"Track not created. Should have been prevented by .isComplete()\");\n }\n await uploadFileTrack(\n getClient(),\n this.videoFile.id,\n Number(this.trackId),\n this.created.byte_size,\n createReadableStreamFromReadable(createReadStream(this.path)),\n ).whenUploaded();\n }\n async markSynced() {\n if (!this.created) {\n throw new Error(\"Track not created. Should have been prevented by .isComplete()\");\n }\n const byteSize = await this.byteSize();\n await Promise.all([\n this.syncStatus.markSynced({\n version: \"1\",\n complete: true,\n id: `${this.created.file_id}:${this.created.track_id}`,\n md5: this.md5,\n byte_size: byteSize,\n }),\n this.fileSyncStatus.markSynced({\n version: \"1\",\n complete: true,\n id: this.created.file_id,\n md5: this.md5,\n byte_size: byteSize,\n }),\n ]);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doAssetSync.js","names":[],"sources":["../../../src/operations/syncAssetsDirectory/doAssetSync.ts"],"sourcesContent":["import type { SubAssetSync } from \"./SubAssetSync.js\";\n\nexport const doAssetSync = async function* (
|
|
1
|
+
{"version":3,"file":"doAssetSync.js","names":[],"sources":["../../../src/operations/syncAssetsDirectory/doAssetSync.ts"],"sourcesContent":["import type { SubAssetSync } from \"./SubAssetSync.js\";\n\nexport const doAssetSync = async function* (assetSync: SubAssetSync<unknown>): AsyncGenerator<{\n status: \"info\" | \"success\";\n message: string;\n}> {\n if (await assetSync.syncStatus.isSynced()) {\n yield {\n status: \"info\",\n message: `Sub-asset has already been synced: ${assetSync.path}`,\n };\n return;\n }\n\n try {\n await assetSync.prepare();\n await assetSync.validate();\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n\n throw new Error(`Error validating ${assetSync.label}: ${message}`);\n }\n\n yield {\n status: \"info\",\n message: `${assetSync.icon} Syncing ${assetSync.label}: ${assetSync.path}`,\n };\n\n try {\n await assetSync.create();\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n\n throw new Error(`Error creating ${assetSync.label}: ${message}`);\n }\n\n if (!assetSync.isComplete()) {\n try {\n await assetSync.upload();\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n\n throw new Error(`Error uploading ${assetSync.label}: ${message}`);\n }\n }\n\n try {\n await assetSync.markSynced();\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n\n throw new Error(`Error marking ${assetSync.label} as synced: ${message}`);\n }\n\n yield {\n status: \"success\",\n message: `Synced ${assetSync.label}: ${assetSync.path}`,\n };\n return;\n};\n"],"mappings":";AAEA,MAAa,cAAc,iBAAiB,WAGzC;AACD,KAAI,MAAM,UAAU,WAAW,UAAU,EAAE;AACzC,QAAM;GACJ,QAAQ;GACR,SAAS,sCAAsC,UAAU;GAC1D;AACD;;AAGF,KAAI;AACF,QAAM,UAAU,SAAS;AACzB,QAAM,UAAU,UAAU;UACnB,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AAEzD,QAAM,IAAI,MAAM,oBAAoB,UAAU,MAAM,IAAI,UAAU;;AAGpE,OAAM;EACJ,QAAQ;EACR,SAAS,GAAG,UAAU,KAAK,YAAY,UAAU,MAAM,IAAI,UAAU;EACtE;AAED,KAAI;AACF,QAAM,UAAU,QAAQ;UACjB,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AAEzD,QAAM,IAAI,MAAM,kBAAkB,UAAU,MAAM,IAAI,UAAU;;AAGlE,KAAI,CAAC,UAAU,YAAY,CACzB,KAAI;AACF,QAAM,UAAU,QAAQ;UACjB,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AAEzD,QAAM,IAAI,MAAM,mBAAmB,UAAU,MAAM,IAAI,UAAU;;AAIrE,KAAI;AACF,QAAM,UAAU,YAAY;UACrB,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AAEzD,QAAM,IAAI,MAAM,iBAAiB,UAAU,MAAM,cAAc,UAAU;;AAG3E,OAAM;EACJ,QAAQ;EACR,SAAS,UAAU,UAAU,MAAM,IAAI,UAAU;EAClD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createReadableStreamFromReadable.js","names":["_error: any"],"sources":["../../src/utils/createReadableStreamFromReadable.ts"],"sourcesContent":["import { type Readable, Stream } from \"node:stream\";\n\nexport const createReadableStreamFromReadable = (\n source: Readable & { readableHighWaterMark?: number },\n) => {\n const pump = new StreamPump(source);\n const stream = new ReadableStream(pump, pump);\n return stream;\n};\n\nclass StreamPump {\n public highWaterMark: number;\n public accumalatedSize: number;\n private stream: Stream & {\n readableHighWaterMark?: number;\n readable?: boolean;\n resume?: () => void;\n pause?: () => void;\n destroy?: (error?: Error) => void;\n };\n private controller?: ReadableStreamController<Uint8Array>;\n\n constructor(\n stream: Stream & {\n readableHighWaterMark?: number;\n readable?: boolean;\n resume?: () => void;\n pause?: () => void;\n destroy?: (error?: Error) => void;\n },\n ) {\n this.highWaterMark =\n stream.readableHighWaterMark
|
|
1
|
+
{"version":3,"file":"createReadableStreamFromReadable.js","names":["_error: any"],"sources":["../../src/utils/createReadableStreamFromReadable.ts"],"sourcesContent":["import { type Readable, Stream } from \"node:stream\";\n\nexport const createReadableStreamFromReadable = (\n source: Readable & { readableHighWaterMark?: number },\n) => {\n const pump = new StreamPump(source);\n const stream = new ReadableStream(pump, pump);\n return stream;\n};\n\nclass StreamPump {\n public highWaterMark: number;\n public accumalatedSize: number;\n private stream: Stream & {\n readableHighWaterMark?: number;\n readable?: boolean;\n resume?: () => void;\n pause?: () => void;\n destroy?: (error?: Error) => void;\n };\n private controller?: ReadableStreamController<Uint8Array>;\n\n constructor(\n stream: Stream & {\n readableHighWaterMark?: number;\n readable?: boolean;\n resume?: () => void;\n pause?: () => void;\n destroy?: (error?: Error) => void;\n },\n ) {\n this.highWaterMark =\n stream.readableHighWaterMark || new Stream.Readable().readableHighWaterMark;\n this.accumalatedSize = 0;\n this.stream = stream;\n this.enqueue = this.enqueue.bind(this);\n this.error = this.error.bind(this);\n this.close = this.close.bind(this);\n }\n\n size(chunk: Uint8Array) {\n return chunk?.byteLength || 0;\n }\n\n start(controller: ReadableStreamController<Uint8Array>) {\n this.controller = controller;\n this.stream.on(\"data\", this.enqueue);\n this.stream.once(\"error\", this.error);\n this.stream.once(\"end\", this.close);\n this.stream.once(\"close\", this.close);\n }\n\n pull() {\n this.resume();\n }\n\n cancel(reason?: Error) {\n if (this.stream.destroy) {\n this.stream.destroy(reason);\n }\n\n this.stream.off(\"data\", this.enqueue);\n this.stream.off(\"error\", this.error);\n this.stream.off(\"end\", this.close);\n this.stream.off(\"close\", this.close);\n }\n\n enqueue(chunk: Uint8Array | string) {\n if (this.controller) {\n try {\n const available = (this.controller.desiredSize || 0) - chunk.length;\n this.controller.enqueue(chunk as Uint8Array<ArrayBuffer>);\n if (available <= 0) {\n this.pause();\n }\n } catch (_error: any) {\n this.controller.error(\n new Error(\n \"Could not create Buffer, chunk must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object\",\n ),\n );\n this.cancel();\n }\n }\n }\n\n pause() {\n if (this.stream.pause) {\n this.stream.pause();\n }\n }\n\n resume() {\n if (this.stream.readable && this.stream.resume) {\n this.stream.resume();\n }\n }\n\n close() {\n if (this.controller) {\n this.controller.close();\n delete this.controller;\n }\n }\n\n error(error: Error) {\n if (this.controller) {\n this.controller.error(error);\n delete this.controller;\n }\n }\n}\n"],"mappings":";;;AAEA,MAAa,oCACX,WACG;CACH,MAAM,OAAO,IAAI,WAAW,OAAO;AAEnC,QADe,IAAI,eAAe,MAAM,KAAK;;AAI/C,IAAM,aAAN,MAAiB;CAYf,YACE,QAOA;AACA,OAAK,gBACH,OAAO,yBAAyB,IAAI,OAAO,UAAU,CAAC;AACxD,OAAK,kBAAkB;AACvB,OAAK,SAAS;AACd,OAAK,UAAU,KAAK,QAAQ,KAAK,KAAK;AACtC,OAAK,QAAQ,KAAK,MAAM,KAAK,KAAK;AAClC,OAAK,QAAQ,KAAK,MAAM,KAAK,KAAK;;CAGpC,KAAK,OAAmB;AACtB,SAAO,OAAO,cAAc;;CAG9B,MAAM,YAAkD;AACtD,OAAK,aAAa;AAClB,OAAK,OAAO,GAAG,QAAQ,KAAK,QAAQ;AACpC,OAAK,OAAO,KAAK,SAAS,KAAK,MAAM;AACrC,OAAK,OAAO,KAAK,OAAO,KAAK,MAAM;AACnC,OAAK,OAAO,KAAK,SAAS,KAAK,MAAM;;CAGvC,OAAO;AACL,OAAK,QAAQ;;CAGf,OAAO,QAAgB;AACrB,MAAI,KAAK,OAAO,QACd,MAAK,OAAO,QAAQ,OAAO;AAG7B,OAAK,OAAO,IAAI,QAAQ,KAAK,QAAQ;AACrC,OAAK,OAAO,IAAI,SAAS,KAAK,MAAM;AACpC,OAAK,OAAO,IAAI,OAAO,KAAK,MAAM;AAClC,OAAK,OAAO,IAAI,SAAS,KAAK,MAAM;;CAGtC,QAAQ,OAA4B;AAClC,MAAI,KAAK,WACP,KAAI;GACF,MAAM,aAAa,KAAK,WAAW,eAAe,KAAK,MAAM;AAC7D,QAAK,WAAW,QAAQ,MAAiC;AACzD,OAAI,aAAa,EACf,MAAK,OAAO;WAEPA,QAAa;AACpB,QAAK,WAAW,sBACd,IAAI,MACF,gIACD,CACF;AACD,QAAK,QAAQ;;;CAKnB,QAAQ;AACN,MAAI,KAAK,OAAO,MACd,MAAK,OAAO,OAAO;;CAIvB,SAAS;AACP,MAAI,KAAK,OAAO,YAAY,KAAK,OAAO,OACtC,MAAK,OAAO,QAAQ;;CAIxB,QAAQ;AACN,MAAI,KAAK,YAAY;AACnB,QAAK,WAAW,OAAO;AACvB,UAAO,KAAK;;;CAIhB,MAAM,OAAc;AAClB,MAAI,KAAK,YAAY;AACnB,QAAK,WAAW,MAAM,MAAM;AAC5B,UAAO,KAAK"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detectChrome.js","names":[],"sources":["../../src/utils/detectChrome.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { platform } from \"node:os\";\nimport path from \"node:path\";\n\nexport interface ChromeDetectionResult {\n found: boolean;\n path?: string;\n error?: string;\n}\n\n/**\n * Detects if Google Chrome is installed on the system.\n * Returns the path to Chrome executable if found.\n */\nexport function detectChrome(): ChromeDetectionResult {\n const osPlatform = platform();\n\n if (osPlatform === \"darwin\") {\n // macOS: Check for Google Chrome.app\n const chromePath
|
|
1
|
+
{"version":3,"file":"detectChrome.js","names":[],"sources":["../../src/utils/detectChrome.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { platform } from \"node:os\";\nimport path from \"node:path\";\n\nexport interface ChromeDetectionResult {\n found: boolean;\n path?: string;\n error?: string;\n}\n\n/**\n * Detects if Google Chrome is installed on the system.\n * Returns the path to Chrome executable if found.\n */\nexport function detectChrome(): ChromeDetectionResult {\n const osPlatform = platform();\n\n if (osPlatform === \"darwin\") {\n // macOS: Check for Google Chrome.app\n const chromePath = \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\";\n if (existsSync(chromePath)) {\n return { found: true, path: chromePath };\n }\n\n // Also check for Chromium\n const chromiumPath = \"/Applications/Chromium.app/Contents/MacOS/Chromium\";\n if (existsSync(chromiumPath)) {\n return { found: true, path: chromiumPath };\n }\n\n return {\n found: false,\n error: \"Chrome not found in /Applications/Google Chrome.app\",\n };\n }\n\n if (osPlatform === \"linux\") {\n // Linux: Try to find chrome/chromium via which\n try {\n const chromePath = execSync(\"which google-chrome\", {\n encoding: \"utf-8\",\n }).trim();\n if (chromePath && existsSync(chromePath)) {\n return { found: true, path: chromePath };\n }\n } catch {\n // google-chrome not found, try chromium\n }\n\n try {\n const chromiumPath = execSync(\"which chromium\", {\n encoding: \"utf-8\",\n }).trim();\n if (chromiumPath && existsSync(chromiumPath)) {\n return { found: true, path: chromiumPath };\n }\n } catch {\n // chromium not found\n }\n\n try {\n const chromiumBrowserPath = execSync(\"which chromium-browser\", {\n encoding: \"utf-8\",\n }).trim();\n if (chromiumBrowserPath && existsSync(chromiumBrowserPath)) {\n return { found: true, path: chromiumBrowserPath };\n }\n } catch {\n // chromium-browser not found\n }\n\n return {\n found: false,\n error: \"Chrome not found. Install with: apt-get install google-chrome-stable\",\n };\n }\n\n if (osPlatform === \"win32\") {\n // Windows: Check common installation paths\n const possiblePaths = [\n path.join(process.env.LOCALAPPDATA || \"\", \"Google\", \"Chrome\", \"Application\", \"chrome.exe\"),\n path.join(process.env.PROGRAMFILES || \"\", \"Google\", \"Chrome\", \"Application\", \"chrome.exe\"),\n path.join(\n process.env[\"PROGRAMFILES(X86)\"] || \"\",\n \"Google\",\n \"Chrome\",\n \"Application\",\n \"chrome.exe\",\n ),\n ];\n\n for (const chromePath of possiblePaths) {\n if (existsSync(chromePath)) {\n return { found: true, path: chromePath };\n }\n }\n\n return {\n found: false,\n error: \"Chrome not found in common Windows installation paths\",\n };\n }\n\n return {\n found: false,\n error: `Unsupported platform: ${osPlatform}`,\n };\n}\n\n/**\n * Throws a user-friendly error if Chrome is not found.\n */\nexport function requireChrome(): string {\n const result = detectChrome();\n if (!result.found) {\n throw new Error(\n `Chrome browser not found.\\n\\n` +\n `The render command requires Google Chrome to be installed on your system.\\n\\n` +\n `Install Chrome from: https://www.google.com/chrome/\\n` +\n (result.error ? `\\nDetails: ${result.error}` : \"\"),\n );\n }\n return result.path!;\n}\n"],"mappings":";;;;;;;;;;AAeA,SAAgB,eAAsC;CACpD,MAAM,aAAa,UAAU;AAE7B,KAAI,eAAe,UAAU;EAE3B,MAAM,aAAa;AACnB,MAAI,WAAW,WAAW,CACxB,QAAO;GAAE,OAAO;GAAM,MAAM;GAAY;EAI1C,MAAM,eAAe;AACrB,MAAI,WAAW,aAAa,CAC1B,QAAO;GAAE,OAAO;GAAM,MAAM;GAAc;AAG5C,SAAO;GACL,OAAO;GACP,OAAO;GACR;;AAGH,KAAI,eAAe,SAAS;AAE1B,MAAI;GACF,MAAM,aAAa,SAAS,uBAAuB,EACjD,UAAU,SACX,CAAC,CAAC,MAAM;AACT,OAAI,cAAc,WAAW,WAAW,CACtC,QAAO;IAAE,OAAO;IAAM,MAAM;IAAY;UAEpC;AAIR,MAAI;GACF,MAAM,eAAe,SAAS,kBAAkB,EAC9C,UAAU,SACX,CAAC,CAAC,MAAM;AACT,OAAI,gBAAgB,WAAW,aAAa,CAC1C,QAAO;IAAE,OAAO;IAAM,MAAM;IAAc;UAEtC;AAIR,MAAI;GACF,MAAM,sBAAsB,SAAS,0BAA0B,EAC7D,UAAU,SACX,CAAC,CAAC,MAAM;AACT,OAAI,uBAAuB,WAAW,oBAAoB,CACxD,QAAO;IAAE,OAAO;IAAM,MAAM;IAAqB;UAE7C;AAIR,SAAO;GACL,OAAO;GACP,OAAO;GACR;;AAGH,KAAI,eAAe,SAAS;EAE1B,MAAM,gBAAgB;GACpB,KAAK,KAAK,QAAQ,IAAI,gBAAgB,IAAI,UAAU,UAAU,eAAe,aAAa;GAC1F,KAAK,KAAK,QAAQ,IAAI,gBAAgB,IAAI,UAAU,UAAU,eAAe,aAAa;GAC1F,KAAK,KACH,QAAQ,IAAI,wBAAwB,IACpC,UACA,UACA,eACA,aACD;GACF;AAED,OAAK,MAAM,cAAc,cACvB,KAAI,WAAW,WAAW,CACxB,QAAO;GAAE,OAAO;GAAM,MAAM;GAAY;AAI5C,SAAO;GACL,OAAO;GACP,OAAO;GACR;;AAGH,QAAO;EACL,OAAO;EACP,OAAO,yBAAyB;EACjC;;;;;AAMH,SAAgB,gBAAwB;CACtC,MAAM,SAAS,cAAc;AAC7B,KAAI,CAAC,OAAO,MACV,OAAM,IAAI,MACR,qKAGG,OAAO,QAAQ,cAAc,OAAO,UAAU,IAClD;AAEH,QAAO,OAAO"}
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["client: Client"],"sources":["../../src/utils/index.ts"],"sourcesContent":["import { program } from \"commander\";\nimport \"dotenv/config\";\nimport { Client } from \"@editframe/api\";\n\nlet client: Client;\n\nexport const getClient = () => {\n if (!client) {\n const programOpts = program.opts();\n const token = programOpts.token || process.env.EF_TOKEN;\n const efHost = programOpts.efHost || process.env.EF_HOST;\n if (!token) {\n throw new Error(\
|
|
1
|
+
{"version":3,"file":"index.js","names":["client: Client"],"sources":["../../src/utils/index.ts"],"sourcesContent":["import { program } from \"commander\";\nimport \"dotenv/config\";\nimport { Client } from \"@editframe/api\";\n\nlet client: Client;\n\nexport const getClient = () => {\n if (!client) {\n const programOpts = program.opts();\n const token = programOpts.token || process.env.EF_TOKEN;\n const efHost = programOpts.efHost || process.env.EF_HOST;\n if (!token) {\n throw new Error(\"EF_TOKEN must be set or supplied as command line argument\");\n }\n client = new Client(token, efHost);\n }\n return client;\n};\n"],"mappings":";;;;;AAIA,IAAIA;AAEJ,MAAa,kBAAkB;AAC7B,KAAI,CAAC,QAAQ;EACX,MAAM,cAAc,QAAQ,MAAM;EAClC,MAAM,QAAQ,YAAY,SAAS,QAAQ,IAAI;EAC/C,MAAM,SAAS,YAAY,UAAU,QAAQ,IAAI;AACjD,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,4DAA4D;AAE9E,WAAS,IAAI,OAAO,OAAO,OAAO;;AAEpC,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launchBrowserAndWaitForSDK.js","names":["launchOptions: Parameters<typeof chromium.launch>[0]","pageOptions: Parameters<Browser[\"newPage\"]>[0]"],"sources":["../../src/utils/launchBrowserAndWaitForSDK.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport debug from \"debug\";\nimport { type Browser, chromium, type Page } from \"playwright\";\n\nimport { requireChrome } from \"./detectChrome.js\";\nimport { withSpinner } from \"./withSpinner.js\";\n\nconst browserLog = debug(\"ef:cli::browser\");\n\ninterface LaunchOptions {\n url: string;\n headless?: boolean;\n interactive?: boolean;\n efInteractive?: boolean;\n nativeRender?: boolean;\n chromePath?: string;\n profile?: boolean;\n profileOutput?: string;\n silent?: boolean; // Suppress spinner output\n}\n\nexport async function launchBrowserAndWaitForSDK(\n options: LaunchOptions,\n fn: (page: Page) => Promise<void>,\n) {\n // Detect Chrome before launching (only for non-interactive renders)\n if (options.interactive !== true && !options.chromePath) {\n requireChrome();\n }\n\n const launchBrowser = async () => {\n const launchOptions: Parameters<typeof chromium.launch>[0] = {\n channel: \"chrome\",\n headless: options.headless ?? true,\n devtools: options.interactive === true,\n };\n\n // Use custom Chrome path if provided\n if (options.chromePath) {\n launchOptions.executablePath = options.chromePath;\n // Don't use channel when providing explicit path\n delete launchOptions.channel;\n }\n\n // Enable GPU acceleration for headless rendering\n // This significantly improves canvas/WebGL performance for video rendering\n // Set EF_DISABLE_GPU=1 to disable GPU acceleration (for debugging/compatibility)\n const disableGpu = process.env.EF_DISABLE_GPU === \"1\";\n\n if (options.headless && options.interactive !== true && !disableGpu) {\n launchOptions.args = [\n // Core GPU acceleration flags\n \"--enable-gpu\", // Enable GPU hardware acceleration\n \"--use-angle=default\", // Use ANGLE for OpenGL (better compatibility)\n \"--enable-accelerated-2d-canvas\", // Hardware-accelerate canvas operations\n \"--enable-webgl\", // Enable WebGL\n \"--enable-features=VaapiVideoDecoder\", // Hardware video decoding (Linux)\n\n // Prevent fallback to software rendering\n \"--disable-software-rasterizer\",\n\n // System resource optimizations\n \"--disable-dev-shm-usage\", // Avoid /dev/shm issues (especially on Linux/Docker)\n\n // Optional: Uncomment if running in Docker or restricted environments\n // \"--no-sandbox\",\n // \"--disable-setuid-sandbox\",\n ];\n\n browserLog(\"Launching Chrome with GPU acceleration enabled\");\n } else if (disableGpu) {\n browserLog(\"GPU acceleration disabled via EF_DISABLE_GPU\");\n }\n\n return chromium.launch(launchOptions);\n };\n\n const browser = options.silent\n ? await launchBrowser()\n : await withSpinner(\"Launching chrome\", launchBrowser);\n\n const loadSDK = async () => {\n const pageOptions: Parameters<Browser[\"newPage\"]>[0] = {};\n if (options.interactive === true) {\n // By default, playwright uses its own viewport, so resizing the browser window\n // doesn't actually change the viewport. And the gui doesn't scale to fit.\n // This is not desirable for interactive mode, so we disable the viewport feature.\n pageOptions.viewport = null;\n }\n const page = await browser.newPage(pageOptions);\n page.on(\"console\", (msg) => {\n browserLog(chalk.blue(`browser (${msg.type()}) |`), msg.text());\n });\n\n // Build URL with query parameters\n const urlParams = new URLSearchParams();\n if (!options.efInteractive) {\n urlParams.set(\"EF_NONINTERACTIVE\", \"1\");\n }\n if (options.nativeRender) {\n urlParams.set(\"EF_NATIVE_RENDER\", \"1\");\n }\n const url
|
|
1
|
+
{"version":3,"file":"launchBrowserAndWaitForSDK.js","names":["launchOptions: Parameters<typeof chromium.launch>[0]","pageOptions: Parameters<Browser[\"newPage\"]>[0]"],"sources":["../../src/utils/launchBrowserAndWaitForSDK.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport debug from \"debug\";\nimport { type Browser, chromium, type Page } from \"playwright\";\n\nimport { requireChrome } from \"./detectChrome.js\";\nimport { withSpinner } from \"./withSpinner.js\";\n\nconst browserLog = debug(\"ef:cli::browser\");\n\ninterface LaunchOptions {\n url: string;\n headless?: boolean;\n interactive?: boolean;\n efInteractive?: boolean;\n nativeRender?: boolean;\n chromePath?: string;\n profile?: boolean;\n profileOutput?: string;\n silent?: boolean; // Suppress spinner output\n}\n\nexport async function launchBrowserAndWaitForSDK(\n options: LaunchOptions,\n fn: (page: Page) => Promise<void>,\n) {\n // Detect Chrome before launching (only for non-interactive renders)\n if (options.interactive !== true && !options.chromePath) {\n requireChrome();\n }\n\n const launchBrowser = async () => {\n const launchOptions: Parameters<typeof chromium.launch>[0] = {\n channel: \"chrome\",\n headless: options.headless ?? true,\n devtools: options.interactive === true,\n };\n\n // Use custom Chrome path if provided\n if (options.chromePath) {\n launchOptions.executablePath = options.chromePath;\n // Don't use channel when providing explicit path\n delete launchOptions.channel;\n }\n\n // Enable GPU acceleration for headless rendering\n // This significantly improves canvas/WebGL performance for video rendering\n // Set EF_DISABLE_GPU=1 to disable GPU acceleration (for debugging/compatibility)\n const disableGpu = process.env.EF_DISABLE_GPU === \"1\";\n\n if (options.headless && options.interactive !== true && !disableGpu) {\n launchOptions.args = [\n // Core GPU acceleration flags\n \"--enable-gpu\", // Enable GPU hardware acceleration\n \"--use-angle=default\", // Use ANGLE for OpenGL (better compatibility)\n \"--enable-accelerated-2d-canvas\", // Hardware-accelerate canvas operations\n \"--enable-webgl\", // Enable WebGL\n \"--enable-features=VaapiVideoDecoder\", // Hardware video decoding (Linux)\n\n // Prevent fallback to software rendering\n \"--disable-software-rasterizer\",\n\n // System resource optimizations\n \"--disable-dev-shm-usage\", // Avoid /dev/shm issues (especially on Linux/Docker)\n\n // Optional: Uncomment if running in Docker or restricted environments\n // \"--no-sandbox\",\n // \"--disable-setuid-sandbox\",\n ];\n\n browserLog(\"Launching Chrome with GPU acceleration enabled\");\n } else if (disableGpu) {\n browserLog(\"GPU acceleration disabled via EF_DISABLE_GPU\");\n }\n\n return chromium.launch(launchOptions);\n };\n\n const browser = options.silent\n ? await launchBrowser()\n : await withSpinner(\"Launching chrome\", launchBrowser);\n\n const loadSDK = async () => {\n const pageOptions: Parameters<Browser[\"newPage\"]>[0] = {};\n if (options.interactive === true) {\n // By default, playwright uses its own viewport, so resizing the browser window\n // doesn't actually change the viewport. And the gui doesn't scale to fit.\n // This is not desirable for interactive mode, so we disable the viewport feature.\n pageOptions.viewport = null;\n }\n const page = await browser.newPage(pageOptions);\n page.on(\"console\", (msg) => {\n browserLog(chalk.blue(`browser (${msg.type()}) |`), msg.text());\n });\n\n // Build URL with query parameters\n const urlParams = new URLSearchParams();\n if (!options.efInteractive) {\n urlParams.set(\"EF_NONINTERACTIVE\", \"1\");\n }\n if (options.nativeRender) {\n urlParams.set(\"EF_NATIVE_RENDER\", \"1\");\n }\n const url = options.url + (urlParams.toString() ? `?${urlParams.toString()}` : \"\");\n\n browserLog(\"Loading url:\", url);\n await page.goto(url);\n await page.waitForFunction(\n () => {\n return (\n // @ts-expect-error\n window.EF_REGISTERED\n );\n },\n [],\n { timeout: 10_000 },\n );\n return page;\n };\n\n const page = options.silent\n ? await loadSDK()\n : await withSpinner(\"Loading Editframe SDK\", loadSDK);\n\n await fn(page);\n if (options.interactive !== true) {\n await browser.close();\n process.exit(0);\n }\n}\n"],"mappings":";;;;;;;AAOA,MAAM,aAAa,MAAM,kBAAkB;AAc3C,eAAsB,2BACpB,SACA,IACA;AAEA,KAAI,QAAQ,gBAAgB,QAAQ,CAAC,QAAQ,WAC3C,gBAAe;CAGjB,MAAM,gBAAgB,YAAY;EAChC,MAAMA,gBAAuD;GAC3D,SAAS;GACT,UAAU,QAAQ,YAAY;GAC9B,UAAU,QAAQ,gBAAgB;GACnC;AAGD,MAAI,QAAQ,YAAY;AACtB,iBAAc,iBAAiB,QAAQ;AAEvC,UAAO,cAAc;;EAMvB,MAAM,aAAa,QAAQ,IAAI,mBAAmB;AAElD,MAAI,QAAQ,YAAY,QAAQ,gBAAgB,QAAQ,CAAC,YAAY;AACnE,iBAAc,OAAO;IAEnB;IACA;IACA;IACA;IACA;IAGA;IAGA;IAKD;AAED,cAAW,iDAAiD;aACnD,WACT,YAAW,+CAA+C;AAG5D,SAAO,SAAS,OAAO,cAAc;;CAGvC,MAAM,UAAU,QAAQ,SACpB,MAAM,eAAe,GACrB,MAAM,YAAY,oBAAoB,cAAc;CAExD,MAAM,UAAU,YAAY;EAC1B,MAAMC,cAAiD,EAAE;AACzD,MAAI,QAAQ,gBAAgB,KAI1B,aAAY,WAAW;EAEzB,MAAM,OAAO,MAAM,QAAQ,QAAQ,YAAY;AAC/C,OAAK,GAAG,YAAY,QAAQ;AAC1B,cAAW,MAAM,KAAK,YAAY,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,MAAM,CAAC;IAC/D;EAGF,MAAM,YAAY,IAAI,iBAAiB;AACvC,MAAI,CAAC,QAAQ,cACX,WAAU,IAAI,qBAAqB,IAAI;AAEzC,MAAI,QAAQ,aACV,WAAU,IAAI,oBAAoB,IAAI;EAExC,MAAM,MAAM,QAAQ,OAAO,UAAU,UAAU,GAAG,IAAI,UAAU,UAAU,KAAK;AAE/E,aAAW,gBAAgB,IAAI;AAC/B,QAAM,KAAK,KAAK,IAAI;AACpB,QAAM,KAAK,sBACH;AACJ,UAEE,OAAO;KAGX,EAAE,EACF,EAAE,SAAS,KAAQ,CACpB;AACD,SAAO;;AAOT,OAAM,GAJO,QAAQ,SACjB,MAAM,SAAS,GACf,MAAM,YAAY,yBAAyB,QAAQ,CAEzC;AACd,KAAI,QAAQ,gBAAgB,MAAM;AAChC,QAAM,QAAQ,OAAO;AACrB,UAAQ,KAAK,EAAE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patchFragmentedMp4.js","names":["timescale: number"],"sources":["../../src/utils/patchFragmentedMp4.ts"],"sourcesContent":["import { readFile, writeFile, rename, unlink } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport debug from \"debug\";\nimport ISOBoxer from \"codem-isoboxer\";\n\nconst log = debug(\"ef:cli:patch-mp4\");\n\n/**\n * Patches mvhd.duration, zeroes tkhd/mdhd durations, and removes edts boxes\n * from a fragmented MP4 buffer. This fixes mvhd.duration=0 that mediabunny\n * writes in fragmented mode, enabling duration display in VLC, QuickTime, and WMP.\n */\nexport function patchMoovDuration(
|
|
1
|
+
{"version":3,"file":"patchFragmentedMp4.js","names":["timescale: number"],"sources":["../../src/utils/patchFragmentedMp4.ts"],"sourcesContent":["import { readFile, writeFile, rename, unlink } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport debug from \"debug\";\nimport ISOBoxer from \"codem-isoboxer\";\n\nconst log = debug(\"ef:cli:patch-mp4\");\n\n/**\n * Patches mvhd.duration, zeroes tkhd/mdhd durations, and removes edts boxes\n * from a fragmented MP4 buffer. This fixes mvhd.duration=0 that mediabunny\n * writes in fragmented mode, enabling duration display in VLC, QuickTime, and WMP.\n */\nexport function patchMoovDuration(arrayBuffer: ArrayBuffer, durationMs: number): ArrayBuffer {\n const iso = ISOBoxer.parseBuffer(arrayBuffer);\n\n const mvhd = iso.fetch(\"mvhd\");\n if (mvhd) {\n const timescale: number = mvhd.timescale || 1000;\n mvhd.duration = Math.ceil((durationMs / 1000) * timescale);\n }\n\n for (const tkhd of iso.fetchAll(\"tkhd\")) {\n if (tkhd) tkhd.duration = 0;\n }\n\n for (const mdhd of iso.fetchAll(\"mdhd\")) {\n if (mdhd) mdhd.duration = 0;\n }\n\n const moov = iso.fetch(\"moov\") as any;\n if (moov?.boxes) {\n for (const trak of moov.boxes.filter((b: any) => b.type === \"trak\")) {\n if (trak.boxes) {\n trak.boxes = trak.boxes.filter((b: any) => b.type !== \"edts\");\n }\n }\n }\n\n return iso.write();\n}\n\n/**\n * Post-processes a rendered fragmented MP4:\n *\n * Layer 1 (always): patches mvhd.duration so VLC, QuickTime, and WMP can\n * display duration and support seeking.\n *\n * Layer 2 (if ffmpeg on PATH): remuxes to a non-fragmented progressive MP4\n * with moov at the front, for full compatibility including Windows Explorer\n * thumbnails and social upload pipelines.\n */\nexport async function patchFragmentedMp4(outputPath: string, durationMs: number): Promise<void> {\n // Layer 1: always patch the moov\n const buf = await readFile(outputPath);\n const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);\n const patched = patchMoovDuration(arrayBuffer, durationMs);\n await writeFile(outputPath, Buffer.from(patched));\n log(\"Patched mvhd.duration to %dms in %s\", durationMs, outputPath);\n\n // Layer 2: remux to non-fragmented progressive MP4 if ffmpeg is available\n const tmp = outputPath + \".tmp.mp4\";\n const result = spawnSync(\n \"ffmpeg\",\n [\"-y\", \"-i\", outputPath, \"-c\", \"copy\", \"-movflags\", \"+faststart\", tmp],\n { shell: true, stdio: \"pipe\" },\n );\n\n if (result.status === 0 && existsSync(tmp)) {\n await rename(tmp, outputPath);\n log(\"Remuxed to non-fragmented MP4 via ffmpeg: %s\", outputPath);\n } else {\n if (existsSync(tmp)) {\n await unlink(tmp).catch(() => {});\n }\n process.stderr.write(\n \"Note: install ffmpeg for better MP4 compatibility (Windows Explorer thumbnails, social uploads).\\n\",\n );\n log(\n \"ffmpeg not available or remux failed (status %d), keeping patched fragmented output\",\n result.status,\n );\n }\n}\n"],"mappings":";;;;;;;AAMA,MAAM,MAAM,MAAM,mBAAmB;;;;;;AAOrC,SAAgB,kBAAkB,aAA0B,YAAiC;CAC3F,MAAM,MAAM,SAAS,YAAY,YAAY;CAE7C,MAAM,OAAO,IAAI,MAAM,OAAO;AAC9B,KAAI,MAAM;EACR,MAAMA,YAAoB,KAAK,aAAa;AAC5C,OAAK,WAAW,KAAK,KAAM,aAAa,MAAQ,UAAU;;AAG5D,MAAK,MAAM,QAAQ,IAAI,SAAS,OAAO,CACrC,KAAI,KAAM,MAAK,WAAW;AAG5B,MAAK,MAAM,QAAQ,IAAI,SAAS,OAAO,CACrC,KAAI,KAAM,MAAK,WAAW;CAG5B,MAAM,OAAO,IAAI,MAAM,OAAO;AAC9B,KAAI,MAAM,OACR;OAAK,MAAM,QAAQ,KAAK,MAAM,QAAQ,MAAW,EAAE,SAAS,OAAO,CACjE,KAAI,KAAK,MACP,MAAK,QAAQ,KAAK,MAAM,QAAQ,MAAW,EAAE,SAAS,OAAO;;AAKnE,QAAO,IAAI,OAAO;;;;;;;;;;;;AAapB,eAAsB,mBAAmB,YAAoB,YAAmC;CAE9F,MAAM,MAAM,MAAM,SAAS,WAAW;CAEtC,MAAM,UAAU,kBADI,IAAI,OAAO,MAAM,IAAI,YAAY,IAAI,aAAa,IAAI,WAAW,EACtC,WAAW;AAC1D,OAAM,UAAU,YAAY,OAAO,KAAK,QAAQ,CAAC;AACjD,KAAI,uCAAuC,YAAY,WAAW;CAGlE,MAAM,MAAM,aAAa;CACzB,MAAM,SAAS,UACb,UACA;EAAC;EAAM;EAAM;EAAY;EAAM;EAAQ;EAAa;EAAc;EAAI,EACtE;EAAE,OAAO;EAAM,OAAO;EAAQ,CAC/B;AAED,KAAI,OAAO,WAAW,KAAK,WAAW,IAAI,EAAE;AAC1C,QAAM,OAAO,KAAK,WAAW;AAC7B,MAAI,gDAAgD,WAAW;QAC1D;AACL,MAAI,WAAW,IAAI,CACjB,OAAM,OAAO,IAAI,CAAC,YAAY,GAAG;AAEnC,UAAQ,OAAO,MACb,qGACD;AACD,MACE,uFACA,OAAO,OACR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profileRender.js","names":[],"sources":["../../src/utils/profileRender.ts"],"sourcesContent":["import { Page } from \"playwright\";\nimport { writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport interface ProfileOptions {\n enabled: boolean;\n outputPath?: string;\n}\n\nexport async function withProfiling<T>(\n page: Page,\n options: ProfileOptions,\n fn: () => Promise<T>,\n): Promise<T> {\n if (!options.enabled) {\n return await fn();\n }\n\n // Get CDP session\n const client = await page.context().newCDPSession(page);\n\n // Start profiling\n await client.send(\"Profiler.enable\");\n await client.send(\"Profiler.setSamplingInterval\", { interval: 100 }); // 100μs\n await client.send(\"Profiler.start\");\n\n console.error(\"🔬 CPU profiling started...\");\n const startTime = Date.now();\n\n // Run the render\n const result = await fn();\n\n // Stop profiling\n const { profile } = await client.send(\"Profiler.stop\");\n await client.send(\"Profiler.disable\");\n\n const duration = Date.now() - startTime;\n console.error(`✅ CPU profiling complete (${(duration / 1000).toFixed(1)}s)`);\n\n // Save profile\n const outputPath = options.outputPath || \"./render-profile.cpuprofile\";\n const absolutePath = path.resolve(process.cwd(), outputPath);\n await writeFile(absolutePath, JSON.stringify(profile, null, 2));\n console.error(`💾 Profile saved to: ${absolutePath}`);\n\n // Basic analysis\n const samples = profile.samples?.length || 0;\n console.error(`📊 Captured ${samples.toLocaleString()} samples`);\n\n // Calculate hotspots\n const hitCounts = new Map<number, number>();\n if (profile.samples) {\n for (const sample of profile.samples) {\n hitCounts.set(sample, (hitCounts.get(sample) || 0) + 1);\n }\n }\n\n // Find top functions\n const hotspots = profile.nodes\n .map((node: any) => ({\n name: node.callFrame.functionName || \"(anonymous)\",\n url: node.callFrame.url,\n line: node.callFrame.lineNumber,\n hits: hitCounts.get(node.id) || 0,\n }))\n .filter((h) => h.hits > 0)\n .sort((a, b) => b.hits - a.hits)\n .slice(0, 20);\n\n if (hotspots.length > 0) {\n console.error(\"\\n📊 Top 20 Hotspots:\");\n for (const h of hotspots) {\n const file = h.url.split(\"/\").pop() || h.url;\n console.error(
|
|
1
|
+
{"version":3,"file":"profileRender.js","names":[],"sources":["../../src/utils/profileRender.ts"],"sourcesContent":["import { Page } from \"playwright\";\nimport { writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport interface ProfileOptions {\n enabled: boolean;\n outputPath?: string;\n}\n\nexport async function withProfiling<T>(\n page: Page,\n options: ProfileOptions,\n fn: () => Promise<T>,\n): Promise<T> {\n if (!options.enabled) {\n return await fn();\n }\n\n // Get CDP session\n const client = await page.context().newCDPSession(page);\n\n // Start profiling\n await client.send(\"Profiler.enable\");\n await client.send(\"Profiler.setSamplingInterval\", { interval: 100 }); // 100μs\n await client.send(\"Profiler.start\");\n\n console.error(\"🔬 CPU profiling started...\");\n const startTime = Date.now();\n\n // Run the render\n const result = await fn();\n\n // Stop profiling\n const { profile } = await client.send(\"Profiler.stop\");\n await client.send(\"Profiler.disable\");\n\n const duration = Date.now() - startTime;\n console.error(`✅ CPU profiling complete (${(duration / 1000).toFixed(1)}s)`);\n\n // Save profile\n const outputPath = options.outputPath || \"./render-profile.cpuprofile\";\n const absolutePath = path.resolve(process.cwd(), outputPath);\n await writeFile(absolutePath, JSON.stringify(profile, null, 2));\n console.error(`💾 Profile saved to: ${absolutePath}`);\n\n // Basic analysis\n const samples = profile.samples?.length || 0;\n console.error(`📊 Captured ${samples.toLocaleString()} samples`);\n\n // Calculate hotspots\n const hitCounts = new Map<number, number>();\n if (profile.samples) {\n for (const sample of profile.samples) {\n hitCounts.set(sample, (hitCounts.get(sample) || 0) + 1);\n }\n }\n\n // Find top functions\n const hotspots = profile.nodes\n .map((node: any) => ({\n name: node.callFrame.functionName || \"(anonymous)\",\n url: node.callFrame.url,\n line: node.callFrame.lineNumber,\n hits: hitCounts.get(node.id) || 0,\n }))\n .filter((h) => h.hits > 0)\n .sort((a, b) => b.hits - a.hits)\n .slice(0, 20);\n\n if (hotspots.length > 0) {\n console.error(\"\\n📊 Top 20 Hotspots:\");\n for (const h of hotspots) {\n const file = h.url.split(\"/\").pop() || h.url;\n console.error(` ${h.hits.toString().padStart(6)} samples | ${h.name} @ ${file}:${h.line}`);\n }\n }\n\n return result;\n}\n"],"mappings":";;;;AASA,eAAsB,cACpB,MACA,SACA,IACY;AACZ,KAAI,CAAC,QAAQ,QACX,QAAO,MAAM,IAAI;CAInB,MAAM,SAAS,MAAM,KAAK,SAAS,CAAC,cAAc,KAAK;AAGvD,OAAM,OAAO,KAAK,kBAAkB;AACpC,OAAM,OAAO,KAAK,gCAAgC,EAAE,UAAU,KAAK,CAAC;AACpE,OAAM,OAAO,KAAK,iBAAiB;AAEnC,SAAQ,MAAM,8BAA8B;CAC5C,MAAM,YAAY,KAAK,KAAK;CAG5B,MAAM,SAAS,MAAM,IAAI;CAGzB,MAAM,EAAE,YAAY,MAAM,OAAO,KAAK,gBAAgB;AACtD,OAAM,OAAO,KAAK,mBAAmB;CAErC,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,SAAQ,MAAM,8BAA8B,WAAW,KAAM,QAAQ,EAAE,CAAC,IAAI;CAG5E,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,eAAe,KAAK,QAAQ,QAAQ,KAAK,EAAE,WAAW;AAC5D,OAAM,UAAU,cAAc,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;AAC/D,SAAQ,MAAM,wBAAwB,eAAe;CAGrD,MAAM,UAAU,QAAQ,SAAS,UAAU;AAC3C,SAAQ,MAAM,eAAe,QAAQ,gBAAgB,CAAC,UAAU;CAGhE,MAAM,4BAAY,IAAI,KAAqB;AAC3C,KAAI,QAAQ,QACV,MAAK,MAAM,UAAU,QAAQ,QAC3B,WAAU,IAAI,SAAS,UAAU,IAAI,OAAO,IAAI,KAAK,EAAE;CAK3D,MAAM,WAAW,QAAQ,MACtB,KAAK,UAAe;EACnB,MAAM,KAAK,UAAU,gBAAgB;EACrC,KAAK,KAAK,UAAU;EACpB,MAAM,KAAK,UAAU;EACrB,MAAM,UAAU,IAAI,KAAK,GAAG,IAAI;EACjC,EAAE,CACF,QAAQ,MAAM,EAAE,OAAO,EAAE,CACzB,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,CAC/B,MAAM,GAAG,GAAG;AAEf,KAAI,SAAS,SAAS,GAAG;AACvB,UAAQ,MAAM,wBAAwB;AACtC,OAAK,MAAM,KAAK,UAAU;GACxB,MAAM,OAAO,EAAE,IAAI,MAAM,IAAI,CAAC,KAAK,IAAI,EAAE;AACzC,WAAQ,MAAM,KAAK,EAAE,KAAK,UAAU,CAAC,SAAS,EAAE,CAAC,aAAa,EAAE,KAAK,KAAK,KAAK,GAAG,EAAE,OAAO;;;AAI/F,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawnViteServer.js","names":["url: string | null"],"sources":["../../src/utils/spawnViteServer.ts"],"sourcesContent":["import { spawn, type ChildProcess } from \"node:child_process\";\nimport debug from \"debug\";\n\nconst log = debug(\"ef:cli::spawn-vite\");\n\nexport interface SpawnedViteServer {\n url: string;\n process: ChildProcess;\n kill: () => void;\n}\n\n/**\n * Spawn a Vite dev server as a subprocess and wait for it to be ready.\n * This allows Vite to run in its own process with full config resolution,\n * while the CLI maintains control via Playwright for rendering.\n */\nexport async function spawnViteServer(
|
|
1
|
+
{"version":3,"file":"spawnViteServer.js","names":["url: string | null"],"sources":["../../src/utils/spawnViteServer.ts"],"sourcesContent":["import { spawn, type ChildProcess } from \"node:child_process\";\nimport debug from \"debug\";\n\nconst log = debug(\"ef:cli::spawn-vite\");\n\nexport interface SpawnedViteServer {\n url: string;\n process: ChildProcess;\n kill: () => void;\n}\n\n/**\n * Spawn a Vite dev server as a subprocess and wait for it to be ready.\n * This allows Vite to run in its own process with full config resolution,\n * while the CLI maintains control via Playwright for rendering.\n */\nexport async function spawnViteServer(directory: string): Promise<SpawnedViteServer> {\n return new Promise((resolve, reject) => {\n log(\"Spawning vite dev server in\", directory);\n\n const viteProcess = spawn(\"npx\", [\"vite\", \"dev\"], {\n cwd: directory,\n shell: true,\n stdio: \"pipe\", // Capture output to detect when ready\n env: {\n ...process.env,\n // Disable Vite's automatic browser opening\n BROWSER: \"none\",\n },\n });\n\n let url: string | null = null;\n let stderr = \"\";\n let resolved = false;\n\n // Parse stdout to detect when Vite is ready\n viteProcess.stdout?.on(\"data\", (data: Buffer) => {\n const output = data.toString();\n log(\"vite stdout:\", output);\n\n // Look for the Local URL in Vite's output\n // Format: \" ➜ Local: http://localhost:5173/\"\n const match = output.match(/Local:\\s+(https?:\\/\\/[^\\s]+)/);\n if (match && !resolved) {\n url = match[1]!.trim();\n resolved = true;\n log(\"Vite server ready at:\", url);\n resolve({\n url,\n process: viteProcess,\n kill: () => {\n log(\"Killing vite process\");\n viteProcess.kill();\n },\n });\n }\n });\n\n // Capture stderr for error reporting\n viteProcess.stderr?.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n log(\"vite stderr:\", data.toString());\n });\n\n // Handle process errors\n viteProcess.on(\"error\", (error) => {\n if (!resolved) {\n resolved = true;\n reject(new Error(`Failed to spawn vite: ${error.message}\\n${stderr}`));\n }\n });\n\n // Handle unexpected process exit\n viteProcess.on(\"exit\", (code, signal) => {\n if (!resolved) {\n resolved = true;\n reject(\n new Error(`Vite exited unexpectedly with code ${code} and signal ${signal}\\n${stderr}`),\n );\n }\n });\n\n // Timeout after 30 seconds\n setTimeout(() => {\n if (!resolved) {\n resolved = true;\n viteProcess.kill();\n reject(new Error(`Vite server did not start within 30 seconds\\nStderr: ${stderr}`));\n }\n }, 30000);\n });\n}\n"],"mappings":";;;;AAGA,MAAM,MAAM,MAAM,qBAAqB;;;;;;AAavC,eAAsB,gBAAgB,WAA+C;AACnF,QAAO,IAAI,SAAS,SAAS,WAAW;AACtC,MAAI,+BAA+B,UAAU;EAE7C,MAAM,cAAc,MAAM,OAAO,CAAC,QAAQ,MAAM,EAAE;GAChD,KAAK;GACL,OAAO;GACP,OAAO;GACP,KAAK;IACH,GAAG,QAAQ;IAEX,SAAS;IACV;GACF,CAAC;EAEF,IAAIA,MAAqB;EACzB,IAAI,SAAS;EACb,IAAI,WAAW;AAGf,cAAY,QAAQ,GAAG,SAAS,SAAiB;GAC/C,MAAM,SAAS,KAAK,UAAU;AAC9B,OAAI,gBAAgB,OAAO;GAI3B,MAAM,QAAQ,OAAO,MAAM,+BAA+B;AAC1D,OAAI,SAAS,CAAC,UAAU;AACtB,UAAM,MAAM,GAAI,MAAM;AACtB,eAAW;AACX,QAAI,yBAAyB,IAAI;AACjC,YAAQ;KACN;KACA,SAAS;KACT,YAAY;AACV,UAAI,uBAAuB;AAC3B,kBAAY,MAAM;;KAErB,CAAC;;IAEJ;AAGF,cAAY,QAAQ,GAAG,SAAS,SAAiB;AAC/C,aAAU,KAAK,UAAU;AACzB,OAAI,gBAAgB,KAAK,UAAU,CAAC;IACpC;AAGF,cAAY,GAAG,UAAU,UAAU;AACjC,OAAI,CAAC,UAAU;AACb,eAAW;AACX,2BAAO,IAAI,MAAM,yBAAyB,MAAM,QAAQ,IAAI,SAAS,CAAC;;IAExE;AAGF,cAAY,GAAG,SAAS,MAAM,WAAW;AACvC,OAAI,CAAC,UAAU;AACb,eAAW;AACX,2BACE,IAAI,MAAM,sCAAsC,KAAK,cAAc,OAAO,IAAI,SAAS,CACxF;;IAEH;AAGF,mBAAiB;AACf,OAAI,CAAC,UAAU;AACb,eAAW;AACX,gBAAY,MAAM;AAClB,2BAAO,IAAI,MAAM,wDAAwD,SAAS,CAAC;;KAEpF,IAAM;GACT"}
|
package/package.json
CHANGED
package/test-fixtures/fixture.ts
CHANGED
|
@@ -7,10 +7,7 @@ import { expect } from "vitest";
|
|
|
7
7
|
import type { SyncStatusInfo } from "../src/operations/syncAssetsDirectory/SyncStatus.js";
|
|
8
8
|
import { syncAssetDirectory } from "../src/operations/syncAssetsDirectory.js";
|
|
9
9
|
|
|
10
|
-
export const fixture = async (
|
|
11
|
-
fixture: string,
|
|
12
|
-
name: string,
|
|
13
|
-
): Promise<Fixture> => {
|
|
10
|
+
export const fixture = async (fixture: string, name: string): Promise<Fixture> => {
|
|
14
11
|
const originalPath = join(__dirname, fixture);
|
|
15
12
|
const content = await readFile(originalPath);
|
|
16
13
|
return {
|
|
@@ -94,21 +91,13 @@ export const withFixtures = async (
|
|
|
94
91
|
},
|
|
95
92
|
generateTrack: async (fixture, trackId: number) => {
|
|
96
93
|
await mkdir(join(cacheDir, fixture.md5), { recursive: true });
|
|
97
|
-
const filePath = join(
|
|
98
|
-
cacheDir,
|
|
99
|
-
fixture.md5,
|
|
100
|
-
`${fixture.name}.track-${trackId}.mp4`,
|
|
101
|
-
);
|
|
94
|
+
const filePath = join(cacheDir, fixture.md5, `${fixture.name}.track-${trackId}.mp4`);
|
|
102
95
|
await writeFile(filePath, fixture.content);
|
|
103
96
|
return filePath;
|
|
104
97
|
},
|
|
105
98
|
generateTrackFragmentIndex: async (fixture) => {
|
|
106
99
|
await mkdir(join(cacheDir, fixture.md5), { recursive: true });
|
|
107
|
-
const filePath = join(
|
|
108
|
-
cacheDir,
|
|
109
|
-
fixture.md5,
|
|
110
|
-
`${fixture.name}.tracks.json`,
|
|
111
|
-
);
|
|
100
|
+
const filePath = join(cacheDir, fixture.md5, `${fixture.name}.tracks.json`);
|
|
112
101
|
await writeFile(
|
|
113
102
|
filePath,
|
|
114
103
|
JSON.stringify({
|
|
@@ -119,11 +108,7 @@ export const withFixtures = async (
|
|
|
119
108
|
},
|
|
120
109
|
generateCaptions: async (fixture) => {
|
|
121
110
|
await mkdir(join(cacheDir, fixture.md5), { recursive: true });
|
|
122
|
-
const filePath = join(
|
|
123
|
-
cacheDir,
|
|
124
|
-
fixture.md5,
|
|
125
|
-
`${fixture.name}.captions.json`,
|
|
126
|
-
);
|
|
111
|
+
const filePath = join(cacheDir, fixture.md5, `${fixture.name}.captions.json`);
|
|
127
112
|
await writeFile(
|
|
128
113
|
filePath,
|
|
129
114
|
JSON.stringify({
|
package/test-fixtures/network.ts
CHANGED
|
@@ -52,11 +52,7 @@ export const mockLookupFileByMd5 = ({
|
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
export const mockLookupFileByMd5NotFound = ({
|
|
56
|
-
md5 = "test-md5",
|
|
57
|
-
}: {
|
|
58
|
-
md5?: string;
|
|
59
|
-
}) =>
|
|
55
|
+
export const mockLookupFileByMd5NotFound = ({ md5 = "test-md5" }: { md5?: string }) =>
|
|
60
56
|
http.get(`http://localhost:3000/api/v1/files/md5/${md5}`, async () => {
|
|
61
57
|
return HttpResponse.json({}, { status: 404 });
|
|
62
58
|
});
|
|
@@ -114,15 +110,12 @@ export const mockGetFileTrackUpload = ({
|
|
|
114
110
|
fileId?: string;
|
|
115
111
|
trackId?: number;
|
|
116
112
|
}) =>
|
|
117
|
-
http.get(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
},
|
|
125
|
-
);
|
|
113
|
+
http.get(`http://localhost:3000/api/v1/files/${fileId}/tracks/${trackId}/upload`, async () => {
|
|
114
|
+
return HttpResponse.json({
|
|
115
|
+
id,
|
|
116
|
+
complete,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
126
119
|
|
|
127
120
|
export const mockUploadFileIndex = ({ id = "123" }: { id?: string }) =>
|
|
128
121
|
http.post(
|
|
@@ -173,11 +166,7 @@ export const mockLookupImageFileByMd5 = ({
|
|
|
173
166
|
});
|
|
174
167
|
});
|
|
175
168
|
|
|
176
|
-
export const mockLookupImageFileByMd5NotFound = ({
|
|
177
|
-
md5 = "test-md5",
|
|
178
|
-
}: {
|
|
179
|
-
md5?: string;
|
|
180
|
-
}) =>
|
|
169
|
+
export const mockLookupImageFileByMd5NotFound = ({ md5 = "test-md5" }: { md5?: string }) =>
|
|
181
170
|
http.get(`http://localhost:3000/api/v1/image_files/md5/${md5}`, async () => {
|
|
182
171
|
return HttpResponse.json({}, { status: 404 });
|
|
183
172
|
});
|
|
@@ -192,16 +181,13 @@ export const mockGetUploadImageFile = ({
|
|
|
192
181
|
filename?: string;
|
|
193
182
|
fixture: Fixture;
|
|
194
183
|
}) =>
|
|
195
|
-
http.get(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
});
|
|
203
|
-
},
|
|
204
|
-
);
|
|
184
|
+
http.get(`http://localhost:3000/api/v1/image_files/${id}/upload`, async () => {
|
|
185
|
+
return HttpResponse.json({
|
|
186
|
+
id,
|
|
187
|
+
complete,
|
|
188
|
+
filename,
|
|
189
|
+
});
|
|
190
|
+
});
|
|
205
191
|
|
|
206
192
|
export const mockCreateIsobmffFile = ({
|
|
207
193
|
complete = true,
|
|
@@ -233,27 +219,17 @@ export const mockLookupISOBMFFFileByMd5 = ({
|
|
|
233
219
|
md5?: string;
|
|
234
220
|
fixture: Fixture;
|
|
235
221
|
}) =>
|
|
236
|
-
http.get(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
});
|
|
243
|
-
},
|
|
244
|
-
);
|
|
222
|
+
http.get(`http://localhost:3000/api/v1/isobmff_files/md5/${md5}`, async () => {
|
|
223
|
+
return HttpResponse.json({
|
|
224
|
+
id,
|
|
225
|
+
complete,
|
|
226
|
+
});
|
|
227
|
+
});
|
|
245
228
|
|
|
246
|
-
export const mockLookupISOBMFFFileByMd5NotFound = ({
|
|
247
|
-
md5
|
|
248
|
-
}:
|
|
249
|
-
|
|
250
|
-
}) =>
|
|
251
|
-
http.get(
|
|
252
|
-
`http://localhost:3000/api/v1/isobmff_files/md5/${md5}`,
|
|
253
|
-
async () => {
|
|
254
|
-
return HttpResponse.json({}, { status: 404 });
|
|
255
|
-
},
|
|
256
|
-
);
|
|
229
|
+
export const mockLookupISOBMFFFileByMd5NotFound = ({ md5 = "test-md5" }: { md5?: string }) =>
|
|
230
|
+
http.get(`http://localhost:3000/api/v1/isobmff_files/md5/${md5}`, async () => {
|
|
231
|
+
return HttpResponse.json({}, { status: 404 });
|
|
232
|
+
});
|
|
257
233
|
|
|
258
234
|
export const mockCreateIsobmffTrack = ({
|
|
259
235
|
complete = true,
|
|
@@ -290,15 +266,12 @@ export const mockGetIsobmffTrackUpload = ({
|
|
|
290
266
|
fileId?: string;
|
|
291
267
|
trackId?: number;
|
|
292
268
|
}) =>
|
|
293
|
-
http.get(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
});
|
|
300
|
-
},
|
|
301
|
-
);
|
|
269
|
+
http.get(`http://localhost:3000/api/v1/isobmff_tracks/${fileId}/${trackId}/upload`, async () => {
|
|
270
|
+
return HttpResponse.json({
|
|
271
|
+
id,
|
|
272
|
+
complete,
|
|
273
|
+
});
|
|
274
|
+
});
|
|
302
275
|
export const mockUploadIsobmffFileIndex = ({
|
|
303
276
|
complete = true,
|
|
304
277
|
id = "123",
|
|
@@ -327,27 +300,17 @@ export const mockLookupCaptionFileByMd5 = ({
|
|
|
327
300
|
md5?: string;
|
|
328
301
|
fixture: Fixture;
|
|
329
302
|
}) =>
|
|
330
|
-
http.get(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
});
|
|
337
|
-
},
|
|
338
|
-
);
|
|
303
|
+
http.get(`http://localhost:3000/api/v1/caption_files/md5/${md5}`, async () => {
|
|
304
|
+
return HttpResponse.json({
|
|
305
|
+
id,
|
|
306
|
+
complete,
|
|
307
|
+
});
|
|
308
|
+
});
|
|
339
309
|
|
|
340
|
-
export const mockLookupCaptionFileByMd5NotFound = ({
|
|
341
|
-
md5
|
|
342
|
-
}:
|
|
343
|
-
|
|
344
|
-
}) =>
|
|
345
|
-
http.get(
|
|
346
|
-
`http://localhost:3000/api/v1/caption_files/md5/${md5}`,
|
|
347
|
-
async () => {
|
|
348
|
-
return HttpResponse.json({}, { status: 404 });
|
|
349
|
-
},
|
|
350
|
-
);
|
|
310
|
+
export const mockLookupCaptionFileByMd5NotFound = ({ md5 = "test-md5" }: { md5?: string }) =>
|
|
311
|
+
http.get(`http://localhost:3000/api/v1/caption_files/md5/${md5}`, async () => {
|
|
312
|
+
return HttpResponse.json({}, { status: 404 });
|
|
313
|
+
});
|
|
351
314
|
|
|
352
315
|
export const mockCreateCaptionFile = ({
|
|
353
316
|
complete = true,
|