hyperframes 0.6.64 → 0.6.65

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/cli.js CHANGED
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.6.64" : "0.0.0-dev";
53
+ VERSION = true ? "0.6.65" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -71433,7 +71433,7 @@ var init_urlDownloader2 = __esm({
71433
71433
 
71434
71434
  // ../producer/src/services/htmlCompiler.ts
71435
71435
  import { readFileSync as readFileSync27, existsSync as existsSync35, mkdirSync as mkdirSync20 } from "fs";
71436
- import { join as join39, dirname as dirname13, resolve as resolve19 } from "path";
71436
+ import { join as join39, dirname as dirname13, resolve as resolve19, basename as basename5 } from "path";
71437
71437
  function dedupeElementsById(elements) {
71438
71438
  const deduped = /* @__PURE__ */ new Map();
71439
71439
  for (const element of elements) {
@@ -71566,6 +71566,7 @@ async function compileHtmlFile(html, baseDir, downloadDir) {
71566
71566
  }
71567
71567
  compiledHtml = compiledHtml.replace(/(<video\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
71568
71568
  compiledHtml = compiledHtml.replace(/(<img\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
71569
+ compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
71569
71570
  return { html: compiledHtml, unresolvedCompositions };
71570
71571
  }
71571
71572
  async function parseSubCompositions(html, projectDir, downloadDir, parentOffset = 0, parentEnd = Infinity, visited = /* @__PURE__ */ new Set()) {
@@ -71978,6 +71979,46 @@ function collectExternalAssets(html, projectDir) {
71978
71979
  externalAssets
71979
71980
  };
71980
71981
  }
71982
+ async function localizeRemoteMediaSources(html, downloadDir) {
71983
+ const remoteDir = join39(downloadDir, REMOTE_MEDIA_SUBDIR);
71984
+ const urlSet = /* @__PURE__ */ new Set();
71985
+ const re2 = new RegExp(REMOTE_MEDIA_TAG_RE.source, REMOTE_MEDIA_TAG_RE.flags);
71986
+ let m2;
71987
+ while ((m2 = re2.exec(html)) !== null) {
71988
+ if (m2[1]) urlSet.add(m2[1]);
71989
+ }
71990
+ if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
71991
+ if (!existsSync35(remoteDir)) mkdirSync20(remoteDir, { recursive: true });
71992
+ const urlToLocal = /* @__PURE__ */ new Map();
71993
+ await Promise.all(
71994
+ [...urlSet].map(async (url) => {
71995
+ try {
71996
+ const localPath = await downloadToTemp(url, remoteDir);
71997
+ urlToLocal.set(url, localPath);
71998
+ } catch (err) {
71999
+ console.warn(
72000
+ `[Compiler] Remote media download failed for ${url} \u2014 using original URL as fallback. ${err instanceof Error ? err.message : String(err)}`
72001
+ );
72002
+ }
72003
+ })
72004
+ );
72005
+ if (urlToLocal.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
72006
+ const remoteMediaAssets = /* @__PURE__ */ new Map();
72007
+ const urlToRelPath = /* @__PURE__ */ new Map();
72008
+ for (const [url, absPath] of urlToLocal) {
72009
+ const relPath = `${REMOTE_MEDIA_SUBDIR}/${basename5(absPath)}`;
72010
+ remoteMediaAssets.set(relPath, absPath);
72011
+ urlToRelPath.set(url, relPath);
72012
+ }
72013
+ let result = html;
72014
+ for (const [url, relPath] of urlToRelPath) {
72015
+ result = result.replaceAll(`"${url}"`, `"${relPath}"`).replaceAll(`'${url}'`, `'${relPath}'`);
72016
+ }
72017
+ console.log(
72018
+ `[Compiler] Localized ${urlToLocal.size} remote media source(s) to ${REMOTE_MEDIA_SUBDIR}/`
72019
+ );
72020
+ return { html: result, remoteMediaAssets };
72021
+ }
71981
72022
  function rewriteUnresolvableGsapToCdn(html, projectDir) {
71982
72023
  return html.replace(
71983
72024
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
@@ -72028,10 +72069,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
72028
72069
  'data-hf-studio-motion="'
72029
72070
  ];
72030
72071
  const hasPositionEdits = HF_POSITION_ATTRS.some((attr) => htmlWithAssets.includes(attr));
72031
- const html = hasPositionEdits ? htmlWithAssets.replace(
72072
+ const htmlWithPositionScript = hasPositionEdits ? htmlWithAssets.replace(
72032
72073
  /<\/body>/i,
72033
72074
  `<script>${createStudioPositionSeekReapplyScript()}</script></body>`
72034
72075
  ) : htmlWithAssets;
72076
+ const { html, remoteMediaAssets } = await localizeRemoteMediaSources(
72077
+ htmlWithPositionScript,
72078
+ downloadDir
72079
+ );
72080
+ for (const [relPath, absPath] of remoteMediaAssets) {
72081
+ externalAssets.set(relPath, absPath);
72082
+ }
72035
72083
  const mainVideos = parseVideoElements(html);
72036
72084
  const mainAudios = parseAudioElements(html);
72037
72085
  const mainImages = parseImageElements(html);
@@ -72312,7 +72360,7 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
72312
72360
  hasShaderTransitions: compiled.hasShaderTransitions
72313
72361
  };
72314
72362
  }
72315
- var INLINE_SCRIPT_PATTERN, COMPILER_MOUNT_BLOCK_START, COMPILER_MOUNT_BLOCK_END, SHADER_TRANSITION_USAGE_PATTERN, GSAP_CDN_BASE;
72363
+ var INLINE_SCRIPT_PATTERN, COMPILER_MOUNT_BLOCK_START, COMPILER_MOUNT_BLOCK_END, SHADER_TRANSITION_USAGE_PATTERN, REMOTE_MEDIA_SUBDIR, REMOTE_MEDIA_TAG_RE, GSAP_CDN_BASE;
72316
72364
  var init_htmlCompiler2 = __esm({
72317
72365
  "../producer/src/services/htmlCompiler.ts"() {
72318
72366
  "use strict";
@@ -72329,6 +72377,8 @@ var init_htmlCompiler2 = __esm({
72329
72377
  COMPILER_MOUNT_BLOCK_START = "/* __HF_COMPILER_MOUNT_START__ */";
72330
72378
  COMPILER_MOUNT_BLOCK_END = "/* __HF_COMPILER_MOUNT_END__ */";
72331
72379
  SHADER_TRANSITION_USAGE_PATTERN = /\b(?:(?:window|globalThis)\s*\.\s*)?HyperShader\s*\.\s*init\s*\(|\b__hf\s*\.\s*transitions\s*=/;
72380
+ REMOTE_MEDIA_SUBDIR = "_remote_media";
72381
+ REMOTE_MEDIA_TAG_RE = /<(?:video|audio)\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
72332
72382
  GSAP_CDN_BASE = "https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/";
72333
72383
  }
72334
72384
  });
@@ -78343,7 +78393,7 @@ __export(studioServer_exports, {
78343
78393
  import { Hono as Hono5 } from "hono";
78344
78394
  import { streamSSE as streamSSE3 } from "hono/streaming";
78345
78395
  import { existsSync as existsSync48, readFileSync as readFileSync35, writeFileSync as writeFileSync25, statSync as statSync17 } from "fs";
78346
- import { resolve as resolve24, join as join58, basename as basename5 } from "path";
78396
+ import { resolve as resolve24, join as join58, basename as basename6 } from "path";
78347
78397
  function resolveDistDir() {
78348
78398
  return resolveStudioBundle().dir;
78349
78399
  }
@@ -78470,7 +78520,7 @@ async function loadPreviewServerBuildSignature() {
78470
78520
  }
78471
78521
  function createStudioServer(options) {
78472
78522
  const { projectDir, projectName } = options;
78473
- const projectId = projectName || basename5(projectDir);
78523
+ const projectId = projectName || basename6(projectDir);
78474
78524
  const studioDir = resolveDistDir();
78475
78525
  const runtimePath = resolveRuntimePath();
78476
78526
  const watcher = createProjectWatcher(projectDir);
@@ -78646,19 +78696,19 @@ function createStudioServer(options) {
78646
78696
  async installRegistryBlock(opts) {
78647
78697
  const { resolveItem: resolveItem2 } = await Promise.resolve().then(() => (init_resolver(), resolver_exports));
78648
78698
  const { installItem: installItem2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
78649
- const { readFileSync: readFileSync58, writeFileSync: writeFileSync40, existsSync: existsSync81 } = await import("fs");
78699
+ const { readFileSync: readFileSync59, writeFileSync: writeFileSync40, existsSync: existsSync81 } = await import("fs");
78650
78700
  const { join: join92 } = await import("path");
78651
78701
  const item = await resolveItem2(opts.blockName);
78652
78702
  const { written } = await installItem2(item, { destDir: opts.project.dir });
78653
78703
  const indexPath = join92(opts.project.dir, "index.html");
78654
78704
  if (existsSync81(indexPath)) {
78655
- const indexHtml = readFileSync58(indexPath, "utf-8");
78705
+ const indexHtml = readFileSync59(indexPath, "utf-8");
78656
78706
  const hostW = indexHtml.match(/data-width="(\d+)"/)?.[1];
78657
78707
  const hostH = indexHtml.match(/data-height="(\d+)"/)?.[1];
78658
78708
  if (hostW && hostH) {
78659
78709
  for (const absPath of written) {
78660
78710
  if (!absPath.endsWith(".html")) continue;
78661
- let content = readFileSync58(absPath, "utf-8");
78711
+ let content = readFileSync59(absPath, "utf-8");
78662
78712
  content = content.replace(
78663
78713
  /(<meta\s+name="viewport"\s+content="width=)\d+(,\s*height=)\d+/i,
78664
78714
  `$1${hostW}$2${hostH}`
@@ -78856,14 +78906,14 @@ __export(preview_exports, {
78856
78906
  });
78857
78907
  import { spawn as spawn11 } from "child_process";
78858
78908
  import { existsSync as existsSync49, lstatSync as lstatSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, readlinkSync, mkdirSync as mkdirSync30 } from "fs";
78859
- import { resolve as resolve25, dirname as dirname19, basename as basename6, join as join59 } from "path";
78909
+ import { resolve as resolve25, dirname as dirname19, basename as basename7, join as join59 } from "path";
78860
78910
  import { fileURLToPath as fileURLToPath6 } from "url";
78861
78911
  import { createRequire as createRequire2 } from "module";
78862
78912
  async function runDevMode(dir, options) {
78863
78913
  const thisFile = fileURLToPath6(import.meta.url);
78864
78914
  const repoRoot2 = resolve25(dirname19(thisFile), "..", "..", "..", "..");
78865
78915
  const projectsDir = join59(repoRoot2, "packages", "studio", "data", "projects");
78866
- const pName = options?.projectName ?? basename6(dir);
78916
+ const pName = options?.projectName ?? basename7(dir);
78867
78917
  const symlinkPath = join59(projectsDir, pName);
78868
78918
  mkdirSync30(projectsDir, { recursive: true });
78869
78919
  let createdSymlink = false;
@@ -78953,7 +79003,7 @@ function hasLocalStudio(dir) {
78953
79003
  async function runLocalStudioMode(dir, options) {
78954
79004
  const req = createRequire2(join59(dir, "package.json"));
78955
79005
  const studioPkgPath = dirname19(req.resolve("@hyperframes/studio/package.json"));
78956
- const pName = options?.projectName ?? basename6(dir);
79006
+ const pName = options?.projectName ?? basename7(dir);
78957
79007
  const projectsDir = join59(studioPkgPath, "data", "projects");
78958
79008
  const symlinkPath = join59(projectsDir, pName);
78959
79009
  mkdirSync30(projectsDir, { recursive: true });
@@ -79024,7 +79074,7 @@ async function runLocalStudioMode(dir, options) {
79024
79074
  }
79025
79075
  async function runEmbeddedMode(dir, startPort, options) {
79026
79076
  const { createStudioServer: createStudioServer2, loadPreviewServerBuildSignature: loadPreviewServerBuildSignature2, resolveStudioBundle: resolveStudioBundle2 } = await Promise.resolve().then(() => (init_studioServer(), studioServer_exports));
79027
- const pName = options?.projectName ?? basename6(dir);
79077
+ const pName = options?.projectName ?? basename7(dir);
79028
79078
  const studioBundle = resolveStudioBundle2();
79029
79079
  ge(c2.bold("hyperframes preview"));
79030
79080
  const s2 = ft();
@@ -79254,7 +79304,7 @@ var init_preview2 = __esm({
79254
79304
  const rawArg = args.dir;
79255
79305
  const dir = resolve25(rawArg ?? ".");
79256
79306
  const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
79257
- const projectName = isImplicitCwd ? basename6(process.env.PWD ?? dir) : basename6(dir);
79307
+ const projectName = isImplicitCwd ? basename7(process.env.PWD ?? dir) : basename7(dir);
79258
79308
  const indexPath = join59(dir, "index.html");
79259
79309
  if (existsSync49(indexPath)) {
79260
79310
  const project = { dir, name: projectName, indexPath };
@@ -79342,7 +79392,7 @@ import {
79342
79392
  readFileSync as readFileSync36,
79343
79393
  readdirSync as readdirSync21
79344
79394
  } from "fs";
79345
- import { resolve as resolve26, basename as basename7, join as join60, dirname as dirname20 } from "path";
79395
+ import { resolve as resolve26, basename as basename8, join as join60, dirname as dirname20 } from "path";
79346
79396
  import { fileURLToPath as fileURLToPath7 } from "url";
79347
79397
  import { execFileSync as execFileSync5, spawn as spawn12 } from "child_process";
79348
79398
  function probeVideo(filePath) {
@@ -79423,7 +79473,7 @@ function getSharedTemplateDir() {
79423
79473
  return resolveAssetDir(["..", "templates", "_shared"], ["templates", "_shared"]);
79424
79474
  }
79425
79475
  function toPackageName(projectName) {
79426
- const normalized = basename7(projectName).trim().toLowerCase().replace(/^[._]+/, "").replace(/[^a-z0-9._~-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+|[-.]+$/g, "");
79476
+ const normalized = basename8(projectName).trim().toLowerCase().replace(/^[._]+/, "").replace(/[^a-z0-9._~-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+|[-.]+$/g, "");
79427
79477
  return normalized || "hyperframes-project";
79428
79478
  }
79429
79479
  function getHyperframesPackageSpecifier() {
@@ -79534,7 +79584,7 @@ async function patchTranscript(dir, transcriptPath) {
79534
79584
  async function handleVideoFile(videoPath, destDir, interactive) {
79535
79585
  const probed = probeVideo(videoPath);
79536
79586
  let meta = { ...DEFAULT_META };
79537
- let localVideoName = basename7(videoPath);
79587
+ let localVideoName = basename8(videoPath);
79538
79588
  if (probed) {
79539
79589
  meta = probed;
79540
79590
  if (interactive) {
@@ -79884,8 +79934,8 @@ var init_init = __esm({
79884
79934
  process.exit(1);
79885
79935
  }
79886
79936
  sourceFilePath2 = audioPath;
79887
- copyFileSync5(audioPath, resolve26(destDir2, basename7(audioPath)));
79888
- console.log(`Audio: ${basename7(audioPath)}`);
79937
+ copyFileSync5(audioPath, resolve26(destDir2, basename8(audioPath)));
79938
+ console.log(`Audio: ${basename8(audioPath)}`);
79889
79939
  }
79890
79940
  if (sourceFilePath2 && !skipTranscribe) {
79891
79941
  try {
@@ -79909,7 +79959,7 @@ var init_init = __esm({
79909
79959
  try {
79910
79960
  await scaffoldProject(
79911
79961
  destDir2,
79912
- basename7(destDir2),
79962
+ basename8(destDir2),
79913
79963
  templateId2,
79914
79964
  localVideoName2,
79915
79965
  videoDuration2,
@@ -80016,8 +80066,8 @@ var init_init = __esm({
80016
80066
  }
80017
80067
  mkdirSync31(destDir, { recursive: true });
80018
80068
  sourceFilePath = audioPath;
80019
- copyFileSync5(audioPath, resolve26(destDir, basename7(audioPath)));
80020
- R2.info(`Audio copied to ${c2.accent(basename7(audioPath))}`);
80069
+ copyFileSync5(audioPath, resolve26(destDir, basename8(audioPath)));
80070
+ R2.info(`Audio copied to ${c2.accent(basename8(audioPath))}`);
80021
80071
  }
80022
80072
  if (sourceFilePath) {
80023
80073
  const transcribeChoice = await ue({
@@ -80579,10 +80629,10 @@ var init_format = __esm({
80579
80629
 
80580
80630
  // src/utils/project.ts
80581
80631
  import { existsSync as existsSync52, statSync as statSync18 } from "fs";
80582
- import { resolve as resolve29, basename as basename8 } from "path";
80632
+ import { resolve as resolve29, basename as basename9 } from "path";
80583
80633
  function resolveProject(dirArg) {
80584
80634
  const dir = resolve29(dirArg ?? ".");
80585
- const name = basename8(dir);
80635
+ const name = basename9(dir);
80586
80636
  const indexPath = resolve29(dir, "index.html");
80587
80637
  if (!existsSync52(dir) || !statSync18(dir).isDirectory()) {
80588
80638
  errorBox("Not a directory: " + dir);
@@ -80874,7 +80924,7 @@ var init_play = __esm({
80874
80924
  });
80875
80925
 
80876
80926
  // src/utils/publishProject.ts
80877
- import { basename as basename9, join as join61, relative as relative9 } from "path";
80927
+ import { basename as basename10, join as join61, relative as relative9 } from "path";
80878
80928
  import { readdirSync as readdirSync22, readFileSync as readFileSync38, statSync as statSync19 } from "fs";
80879
80929
  import AdmZip from "adm-zip";
80880
80930
  function isRecord2(value) {
@@ -81088,7 +81138,7 @@ async function publishProjectArchiveStaged(apiBaseUrl2, title, archive) {
81088
81138
  return publishedProject;
81089
81139
  }
81090
81140
  async function publishProjectArchive(projectDir) {
81091
- const title = basename9(projectDir);
81141
+ const title = basename10(projectDir);
81092
81142
  const archive = createPublishArchive(projectDir);
81093
81143
  const apiBaseUrl2 = getPublishApiBaseUrl();
81094
81144
  const stagedResult = await publishProjectArchiveStaged(apiBaseUrl2, title, archive);
@@ -81114,7 +81164,7 @@ __export(publish_exports, {
81114
81164
  default: () => publish_default,
81115
81165
  examples: () => examples6
81116
81166
  });
81117
- import { basename as basename10, resolve as resolve31 } from "path";
81167
+ import { basename as basename11, resolve as resolve31 } from "path";
81118
81168
  import { existsSync as existsSync54 } from "fs";
81119
81169
  import { join as join62 } from "path";
81120
81170
  var examples6, publish_default;
@@ -81150,7 +81200,7 @@ var init_publish = __esm({
81150
81200
  const rawArg = args.dir;
81151
81201
  const dir = resolve31(rawArg ?? ".");
81152
81202
  const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
81153
- const projectName = isImplicitCwd ? basename10(process.env["PWD"] ?? dir) : basename10(dir);
81203
+ const projectName = isImplicitCwd ? basename11(process.env["PWD"] ?? dir) : basename11(dir);
81154
81204
  const indexPath = join62(dir, "index.html");
81155
81205
  if (existsSync54(indexPath)) {
81156
81206
  const lintResult = await lintProject({ dir, name: projectName, indexPath });
@@ -81573,7 +81623,7 @@ __export(render_exports, {
81573
81623
  });
81574
81624
  import { mkdirSync as mkdirSync32, readdirSync as readdirSync23, readFileSync as readFileSync40, statSync as statSync20, writeFileSync as writeFileSync27, rmSync as rmSync14 } from "fs";
81575
81625
  import { cpus as cpus4, freemem as freemem4, tmpdir as tmpdir5 } from "os";
81576
- import { resolve as resolve33, dirname as dirname22, join as join63, basename as basename11 } from "path";
81626
+ import { resolve as resolve33, dirname as dirname22, join as join63, basename as basename12 } from "path";
81577
81627
  import { execFileSync as execFileSync6, spawn as spawn13 } from "child_process";
81578
81628
  function formatFpsParseError(input2, reason) {
81579
81629
  switch (reason) {
@@ -81677,7 +81727,7 @@ async function renderDocker(projectDir, outputPath, options) {
81677
81727
  process.exit(1);
81678
81728
  }
81679
81729
  const outputDir = dirname22(outputPath);
81680
- const outputFilename = basename11(outputPath);
81730
+ const outputFilename = basename12(outputPath);
81681
81731
  const dockerArgs = buildDockerRunArgs({
81682
81732
  imageTag,
81683
81733
  projectDir: resolve33(projectDir),
@@ -84666,7 +84716,7 @@ __export(synthesize_exports, {
84666
84716
  });
84667
84717
  import { execFileSync as execFileSync7 } from "child_process";
84668
84718
  import { existsSync as existsSync63, writeFileSync as writeFileSync29, mkdirSync as mkdirSync35, readdirSync as readdirSync25, unlinkSync as unlinkSync6 } from "fs";
84669
- import { join as join70, dirname as dirname26, basename as basename12 } from "path";
84719
+ import { join as join70, dirname as dirname26, basename as basename13 } from "path";
84670
84720
  import { homedir as homedir11 } from "os";
84671
84721
  function findPython() {
84672
84722
  for (const name of ["python3", "python"]) {
@@ -84705,7 +84755,7 @@ function ensureSynthScript() {
84705
84755
  if (!existsSync63(SCRIPT_PATH)) {
84706
84756
  mkdirSync35(SCRIPT_DIR, { recursive: true });
84707
84757
  writeFileSync29(SCRIPT_PATH, SYNTH_SCRIPT);
84708
- const currentName = basename12(SCRIPT_PATH);
84758
+ const currentName = basename13(SCRIPT_PATH);
84709
84759
  try {
84710
84760
  for (const entry of readdirSync25(SCRIPT_DIR)) {
84711
84761
  if (entry !== currentName && /^synth(-v\d+)?\.py$/.test(entry)) {
@@ -85977,7 +86027,7 @@ __export(contactSheet_exports, {
85977
86027
  });
85978
86028
  import sharp from "sharp";
85979
86029
  import { readdirSync as readdirSync26, readFileSync as readFileSync48, writeFileSync as writeFileSync30, unlinkSync as unlinkSync7, existsSync as existsSync67 } from "fs";
85980
- import { join as join73, extname as extname13, basename as basename13, dirname as dirname29 } from "path";
86030
+ import { join as join73, extname as extname13, basename as basename14, dirname as dirname29 } from "path";
85981
86031
  async function createContactSheet(imagePaths, outputPath, opts = {}) {
85982
86032
  const {
85983
86033
  cols = 3,
@@ -86010,7 +86060,7 @@ async function createContactSheet(imagePaths, outputPath, opts = {}) {
86010
86060
  overlays.push({ input: resized, left: x3, top: y + labelH });
86011
86061
  let labelText = `${i2 + 1}`;
86012
86062
  if (labelMode === "filename") {
86013
- labelText = `${i2 + 1}. ${basename13(files[i2]).replace(extname13(files[i2]), "")}`;
86063
+ labelText = `${i2 + 1}. ${basename14(files[i2]).replace(extname13(files[i2]), "")}`;
86014
86064
  } else if (labelMode === "custom" && labels?.[i2]) {
86015
86065
  labelText = `${i2 + 1}. ${labels[i2]}`;
86016
86066
  }
@@ -93108,7 +93158,7 @@ var require_node_domexception = __commonJS({
93108
93158
 
93109
93159
  // ../../node_modules/.bun/fetch-blob@3.2.0/node_modules/fetch-blob/from.js
93110
93160
  import { statSync as statSync23, createReadStream as createReadStream2, promises as fs2 } from "fs";
93111
- import { basename as basename14 } from "path";
93161
+ import { basename as basename15 } from "path";
93112
93162
  var import_node_domexception, stat, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
93113
93163
  var init_from = __esm({
93114
93164
  "../../node_modules/.bun/fetch-blob@3.2.0/node_modules/fetch-blob/from.js"() {
@@ -93132,7 +93182,7 @@ var init_from = __esm({
93132
93182
  size: stat3.size,
93133
93183
  lastModified: stat3.mtimeMs,
93134
93184
  start: 0
93135
- })], basename14(path2), { type, lastModified: stat3.mtimeMs });
93185
+ })], basename15(path2), { type, lastModified: stat3.mtimeMs });
93136
93186
  BlobDataItem = class _BlobDataItem {
93137
93187
  #path;
93138
93188
  #start;
@@ -131866,6 +131916,57 @@ ${HELP}`);
131866
131916
  }
131867
131917
  });
131868
131918
 
131919
+ // src/cloud/detectAspectRatio.ts
131920
+ import { readFileSync as readFileSync58 } from "fs";
131921
+ function extractAttributeNumber(tag, re2) {
131922
+ const match = tag.match(re2);
131923
+ if (!match) return null;
131924
+ const raw = match[1] ?? match[2] ?? match[3];
131925
+ if (raw === void 0) return null;
131926
+ const value = Number(raw);
131927
+ return Number.isFinite(value) ? value : null;
131928
+ }
131929
+ function detectAspectRatioFromHtml(entryHtmlPath) {
131930
+ let html;
131931
+ try {
131932
+ html = readFileSync58(entryHtmlPath, "utf-8");
131933
+ } catch (err) {
131934
+ return { kind: "read-error", error: err instanceof Error ? err.message : String(err) };
131935
+ }
131936
+ return detectAspectRatioFromHtmlString(html);
131937
+ }
131938
+ function detectAspectRatioFromHtmlString(html) {
131939
+ const tagMatch = html.match(ROOT_COMPOSITION_DIV_RE);
131940
+ if (!tagMatch) return { kind: "no-root-div" };
131941
+ const openTag = tagMatch[0];
131942
+ const width = extractAttributeNumber(openTag, DATA_WIDTH_RE);
131943
+ const height = extractAttributeNumber(openTag, DATA_HEIGHT_RE);
131944
+ if (width === null || height === null) return { kind: "no-dims" };
131945
+ if (width <= 0 || height <= 0) return { kind: "invalid-dims", width, height };
131946
+ const ratio = width / height;
131947
+ for (const candidate of SUPPORTED_RATIOS) {
131948
+ if (Math.abs(ratio - candidate.ratio) <= RATIO_TOLERANCE) {
131949
+ return { kind: "matched", aspectRatio: candidate.value, width, height };
131950
+ }
131951
+ }
131952
+ return { kind: "no-match", width, height, ratio };
131953
+ }
131954
+ var RATIO_TOLERANCE, SUPPORTED_RATIOS, ROOT_COMPOSITION_DIV_RE, DATA_WIDTH_RE, DATA_HEIGHT_RE;
131955
+ var init_detectAspectRatio = __esm({
131956
+ "src/cloud/detectAspectRatio.ts"() {
131957
+ "use strict";
131958
+ RATIO_TOLERANCE = 0.05;
131959
+ SUPPORTED_RATIOS = [
131960
+ { value: "16:9", ratio: 16 / 9 },
131961
+ { value: "9:16", ratio: 9 / 16 },
131962
+ { value: "1:1", ratio: 1 }
131963
+ ];
131964
+ ROOT_COMPOSITION_DIV_RE = /<div\b[^>]*?\bdata-composition-id\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)[^>]*>/i;
131965
+ DATA_WIDTH_RE = /\bdata-width\s*=\s*(?:"(\d+(?:\.\d+)?)"|'(\d+(?:\.\d+)?)'|(\d+(?:\.\d+)?))(?=\s|>|\/)/i;
131966
+ DATA_HEIGHT_RE = /\bdata-height\s*=\s*(?:"(\d+(?:\.\d+)?)"|'(\d+(?:\.\d+)?)'|(\d+(?:\.\d+)?))(?=\s|>|\/)/i;
131967
+ }
131968
+ });
131969
+
131869
131970
  // src/cloud/poll.ts
131870
131971
  function isTerminal(status) {
131871
131972
  return TERMINAL_STATUSES.has(status);
@@ -133321,6 +133422,40 @@ function resolveProjectInput(opts) {
133321
133422
  if (explicit.url) return { kind: "url", url: opts.url };
133322
133423
  return { kind: "dir", dir: opts.dir ?? "." };
133323
133424
  }
133425
+ function maybeAutoDetectAspectRatio(project, compositionArg, asJson) {
133426
+ if (project.kind !== "dir") {
133427
+ const reason = project.kind === "asset_id" ? "--asset-id" : "--url";
133428
+ logDetection(asJson, `Auto-detect skipped (project is ${reason})`);
133429
+ return void 0;
133430
+ }
133431
+ const dir = project.dir ?? ".";
133432
+ const entryRelative = compositionArg ?? "index.html";
133433
+ const entryPath = resolvePath4(dir, entryRelative);
133434
+ const detection = detectAspectRatioFromHtml(entryPath);
133435
+ logDetection(asJson, summarizeDetection(detection, entryRelative));
133436
+ return detection.kind === "matched" ? detection.aspectRatio : void 0;
133437
+ }
133438
+ function logDetection(asJson, message) {
133439
+ if (asJson) return;
133440
+ const suffix = message.startsWith("Detected aspect ratio") ? "" : `; ${ASPECT_FALLBACK_HINT}`;
133441
+ console.log(c2.dim(` ${message}${suffix}`));
133442
+ }
133443
+ function summarizeDetection(detection, entryRelative) {
133444
+ switch (detection.kind) {
133445
+ case "matched":
133446
+ return `Detected aspect ratio: ${detection.aspectRatio} (from ${entryRelative} dims ${detection.width}\xD7${detection.height})`;
133447
+ case "no-root-div":
133448
+ return `No <div data-composition-id> found in ${entryRelative}`;
133449
+ case "no-dims":
133450
+ return `${entryRelative} root composition has no data-width / data-height`;
133451
+ case "invalid-dims":
133452
+ return `${entryRelative} root has invalid dims (${detection.width}\xD7${detection.height})`;
133453
+ case "no-match":
133454
+ return `${entryRelative} dims ${detection.width}\xD7${detection.height} (ratio ${detection.ratio.toFixed(2)}) don't match 16:9, 9:16, or 1:1`;
133455
+ case "read-error":
133456
+ return `Couldn't read ${entryRelative} for aspect-ratio detection (${detection.error})`;
133457
+ }
133458
+ }
133324
133459
  function resolveVariablesAndValidateIfLocal(inline, filePath, strict, source) {
133325
133460
  const variables = resolveVariablesArg(inline, filePath);
133326
133461
  if (!variables || Object.keys(variables).length === 0) return variables;
@@ -133398,6 +133533,7 @@ function buildRenderBody(opts) {
133398
133533
  if (opts.quality !== void 0) body.quality = opts.quality;
133399
133534
  if (opts.format !== void 0) body.format = opts.format;
133400
133535
  if (opts.resolution !== void 0) body.resolution = opts.resolution;
133536
+ if (opts.aspectRatio !== void 0) body.aspect_ratio = opts.aspectRatio;
133401
133537
  if (opts.composition !== void 0) body.composition = opts.composition;
133402
133538
  if (opts.variables !== void 0) body.variables = opts.variables;
133403
133539
  if (opts.title !== void 0) body.title = opts.title;
@@ -133490,11 +133626,12 @@ async function streamVideo(url, destPath, asJson) {
133490
133626
  process.exit(1);
133491
133627
  }
133492
133628
  }
133493
- var VALID_QUALITY2, VALID_FORMAT2, VALID_RESOLUTION, FORMAT_EXT2, examples26, render_default2, IDEMPOTENCY_KEY_RE;
133629
+ var VALID_QUALITY2, VALID_FORMAT2, VALID_RESOLUTION, VALID_ASPECT_RATIO, FORMAT_EXT2, examples26, render_default2, IDEMPOTENCY_KEY_RE, ASPECT_FALLBACK_HINT;
133494
133630
  var init_render4 = __esm({
133495
133631
  "src/commands/cloud/render.ts"() {
133496
133632
  "use strict";
133497
133633
  init_dist();
133634
+ init_detectAspectRatio();
133498
133635
  init_colors();
133499
133636
  init_format();
133500
133637
  init_project();
@@ -133507,14 +133644,8 @@ var init_render4 = __esm({
133507
133644
  init_statusColor();
133508
133645
  VALID_QUALITY2 = ["draft", "standard", "high"];
133509
133646
  VALID_FORMAT2 = ["mp4", "webm", "mov"];
133510
- VALID_RESOLUTION = [
133511
- "landscape",
133512
- "portrait",
133513
- "landscape-4k",
133514
- "portrait-4k",
133515
- "square",
133516
- "square-4k"
133517
- ];
133647
+ VALID_RESOLUTION = ["1080p", "4k"];
133648
+ VALID_ASPECT_RATIO = ["16:9", "9:16", "1:1"];
133518
133649
  FORMAT_EXT2 = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
133519
133650
  examples26 = [
133520
133651
  ["Render the current directory in the cloud", "hyperframes cloud render"],
@@ -133542,7 +133673,11 @@ var init_render4 = __esm({
133542
133673
  format: { type: "string", description: "mp4 | webm | mov (default: mp4)" },
133543
133674
  resolution: {
133544
133675
  type: "string",
133545
- description: "Resolution preset: landscape | portrait | landscape-4k | portrait-4k | square | square-4k"
133676
+ description: "Resolution tier: 1080p | 4k (default: 1080p; 4k is billed at 1.5x)"
133677
+ },
133678
+ "aspect-ratio": {
133679
+ type: "string",
133680
+ description: "Aspect ratio: 16:9 | 9:16 | 1:1 (default: 16:9)"
133546
133681
  },
133547
133682
  composition: {
133548
133683
  type: "string",
@@ -133625,6 +133760,9 @@ var init_render4 = __esm({
133625
133760
  const resolution = parseEnumFlag(args.resolution, VALID_RESOLUTION, {
133626
133761
  flag: "--resolution"
133627
133762
  });
133763
+ const explicitAspectRatio = parseEnumFlag(args["aspect-ratio"], VALID_ASPECT_RATIO, {
133764
+ flag: "--aspect-ratio"
133765
+ });
133628
133766
  const pollIntervalMs = parsePollIntervalMs(args["poll-interval"]);
133629
133767
  const maxWaitMs = parseMaxWaitMs(args["max-wait"]);
133630
133768
  validateIdempotencyKey(args["idempotency-key"]);
@@ -133633,6 +133771,7 @@ var init_render4 = __esm({
133633
133771
  assetId: args["asset-id"],
133634
133772
  url: args.url
133635
133773
  });
133774
+ const aspectRatio = explicitAspectRatio ?? maybeAutoDetectAspectRatio(project, args.composition, asJson);
133636
133775
  const variables = resolveVariablesAndValidateIfLocal(
133637
133776
  args.variables,
133638
133777
  args["variables-file"],
@@ -133647,6 +133786,7 @@ var init_render4 = __esm({
133647
133786
  quality,
133648
133787
  format,
133649
133788
  resolution,
133789
+ aspectRatio,
133650
133790
  composition: args.composition,
133651
133791
  variables,
133652
133792
  title: args.title,
@@ -133707,6 +133847,7 @@ var init_render4 = __esm({
133707
133847
  }
133708
133848
  });
133709
133849
  IDEMPOTENCY_KEY_RE = /^[A-Za-z0-9_:.-]{1,255}$/;
133850
+ ASPECT_FALLBACK_HINT = "server will default aspect_ratio to 16:9. Pass --aspect-ratio to override.";
133710
133851
  }
133711
133852
  });
133712
133853
 
@@ -135091,10 +135232,10 @@ if (rootVersionRequested) {
135091
135232
  process.exit(0);
135092
135233
  }
135093
135234
  try {
135094
- const { readFileSync: readFileSync58 } = await import("fs");
135235
+ const { readFileSync: readFileSync59 } = await import("fs");
135095
135236
  const { resolve: resolve46 } = await import("path");
135096
135237
  const envPath = resolve46(process.cwd(), ".env");
135097
- const envContent = readFileSync58(envPath, "utf-8");
135238
+ const envContent = readFileSync59(envPath, "utf-8");
135098
135239
  for (const rawLine of envContent.split("\n")) {
135099
135240
  let line = rawLine.trim();
135100
135241
  if (!line || line.startsWith("#")) continue;
@@ -181,38 +181,23 @@ The slide distance DECAYS per word (80→12px) — mimics a camera settling.
181
181
  Vector animations that play inside a composition. Use for logos, character animations, icons.
182
182
 
183
183
  ```html
184
- <!-- The web-component package is `@lottiefiles/dotlottie-wc` (the SDK
185
- `@lottiefiles/dotlottie-web` does NOT expose a custom element).
186
- The element tag is <dotlottie-wc>. -->
187
- <script
188
- src="https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-wc/dist/dotlottie-wc.js"
189
- type="module"
190
- ></script>
191
- <dotlottie-wc
192
- class="lottie"
193
- src="capture/assets/lottie/animation-0.json"
194
- autoplay
195
- loop
196
- speed="1.5"
197
- style="width:500px;height:500px;"
198
- >
199
- </dotlottie-wc>
184
+ <div id="anim" class="lottie"></div>
185
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.12.2/lottie.min.js"></script>
200
186
  <script>
201
- gsap.set(".lottie", { scale: 0.3, opacity: 0 });
202
- tl.to(".lottie", { scale: 1, opacity: 1, duration: 0.35, ease: "back.out(1.6)" }, 0.2);
203
- </script>
204
- ```
205
-
206
- Or use lottie-web for more control:
187
+ window.__hfLottie = window.__hfLottie || [];
188
+
189
+ const anim = lottie.loadAnimation({
190
+ container: document.getElementById("anim"),
191
+ renderer: "svg",
192
+ loop: false,
193
+ autoplay: false,
194
+ path: "capture/assets/lottie/animation-0.json",
195
+ });
196
+ window.__hfLottie.push(anim);
207
197
 
208
- ```javascript
209
- var anim = lottie.loadAnimation({
210
- container: document.getElementById("anim"),
211
- renderer: "svg",
212
- loop: false,
213
- autoplay: false,
214
- path: "capture/assets/lottie/animation-0.json",
215
- });
198
+ gsap.set("#anim", { scale: 0.3, opacity: 0 });
199
+ tl.to("#anim", { scale: 1, opacity: 1, duration: 0.35, ease: "back.out(1.6)" }, 0.2);
200
+ </script>
216
201
  ```
217
202
 
218
203
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperframes",
3
- "version": "0.6.64",
3
+ "version": "0.6.65",
4
4
  "description": "HyperFrames CLI — create, preview, and render HTML video compositions",
5
5
  "repository": {
6
6
  "type": "git",