hyperframes 0.8.43 → 0.8.44

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.8.43" : "0.0.0-dev";
53
+ VERSION = true ? "0.8.44" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -55088,9 +55088,9 @@ var init_dist2 = __esm({
55088
55088
  UHD_SQUARE_MIN = 2160;
55089
55089
  UHD_RECT_MIN = 3840;
55090
55090
  OK = { ok: true };
55091
- HTML_BODY_CSS_WIDTH_FIRST_RE = /html\s*,\s*body\s*\{[^}]*?width:\s*(\d+)px[^}]*?height:\s*(\d+)px/i;
55092
- HTML_BODY_CSS_HEIGHT_FIRST_RE = /html\s*,\s*body\s*\{[^}]*?height:\s*(\d+)px[^}]*?width:\s*(\d+)px/i;
55093
- VIEWPORT_META_SIZE_RE = /<meta[^>]*name=["']viewport["'][^>]*content=["']width=(\d+),\s*height=(\d+)/i;
55091
+ HTML_BODY_CSS_WIDTH_FIRST_RE = /(html\s*,\s*body\s*\{[^}]*?width:\s*)(\d+)px([^}]*?height:\s*)(\d+)px/i;
55092
+ HTML_BODY_CSS_HEIGHT_FIRST_RE = /(html\s*,\s*body\s*\{[^}]*?height:\s*)(\d+)px([^}]*?width:\s*)(\d+)px/i;
55093
+ VIEWPORT_META_SIZE_RE = /(<meta[^>]*name=["']viewport["'][^>]*content=["']width=)(\d+)(,\s*height=)(\d+)/i;
55094
55094
  FONT_ALIAS_MAP = {
55095
55095
  // ── Canonical bundled fonts (self-referencing) ────────────────────────
55096
55096
  inter: "inter",
@@ -71226,6 +71226,31 @@ function getEncoderPreset(quality, format = "mp4", hdr) {
71226
71226
  }
71227
71227
  return { ...base2, pixelFormat: "yuv420p" };
71228
71228
  }
71229
+ function resolveLockedGopSize(options) {
71230
+ if (options.lockGopForChunkConcat !== true) return null;
71231
+ if (typeof options.gopSize !== "number" || !Number.isFinite(options.gopSize) || options.gopSize <= 0) {
71232
+ throw new Error(
71233
+ `[chunkEncoder] lockGopForChunkConcat=true requires a positive integer gopSize (received ${String(options.gopSize)})`
71234
+ );
71235
+ }
71236
+ return Math.floor(options.gopSize);
71237
+ }
71238
+ function appendLockedGopArgs(args, gopSize) {
71239
+ args.push(
71240
+ "-g",
71241
+ String(gopSize),
71242
+ "-keyint_min",
71243
+ String(gopSize),
71244
+ "-sc_threshold",
71245
+ "0",
71246
+ "-force_key_frames",
71247
+ `expr:eq(mod(n,${gopSize}),0)`
71248
+ );
71249
+ }
71250
+ function lockedGopCodecParams(codec, gopSize) {
71251
+ const shared = "scenecut=0:open-gop=0:repeat-headers=1";
71252
+ return codec === "h264" ? shared : `keyint=${gopSize}:min-keyint=${gopSize}:${shared}`;
71253
+ }
71229
71254
  function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
71230
71255
  const {
71231
71256
  fps,
@@ -71293,36 +71318,15 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
71293
71318
  args.push("-c:v", encoderName, "-preset", preset2);
71294
71319
  if (bitrate) args.push("-b:v", bitrate);
71295
71320
  else args.push("-crf", String(quality));
71296
- const lockGop = options.lockGopForChunkConcat === true;
71297
- let gop = 0;
71298
- if (lockGop) {
71299
- if (typeof options.gopSize !== "number" || !Number.isFinite(options.gopSize) || options.gopSize <= 0) {
71300
- throw new Error(
71301
- `[chunkEncoder] lockGopForChunkConcat=true requires a positive integer gopSize (received ${String(options.gopSize)})`
71302
- );
71303
- }
71304
- gop = Math.floor(options.gopSize);
71305
- args.push(
71306
- "-g",
71307
- String(gop),
71308
- "-keyint_min",
71309
- String(gop),
71310
- "-sc_threshold",
71311
- "0",
71312
- "-force_key_frames",
71313
- `expr:eq(mod(n,${gop}),0)`
71314
- );
71315
- }
71321
+ const gop = resolveLockedGopSize(options);
71322
+ const lockGop = gop !== null;
71323
+ if (gop !== null) appendLockedGopArgs(args, gop);
71316
71324
  if (codec === "h264" || codec === "h265" && lockGop) {
71317
71325
  args.push("-bf", "0");
71318
71326
  }
71319
71327
  const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
71320
71328
  const colorParams = codec === "h265" && options.hdr ? getHdrEncoderColorParams(options.hdr.transfer).x265ColorParams : "colorprim=bt709:transfer=bt709:colormatrix=bt709";
71321
- let gopParams = "";
71322
- if (lockGop) {
71323
- const shared = "scenecut=0:open-gop=0:repeat-headers=1";
71324
- gopParams = codec === "h264" ? shared : `keyint=${gop}:min-keyint=${gop}:${shared}`;
71325
- }
71329
+ const gopParams = gop !== null ? lockedGopCodecParams(codec, gop) : "";
71326
71330
  const joinParams = (...parts) => parts.filter((p2) => p2.length > 0).join(":");
71327
71331
  if (preset2 === "ultrafast") {
71328
71332
  args.push(xParamsFlag, joinParams("aq-mode=3", colorParams, gopParams));
@@ -71341,15 +71345,10 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
71341
71345
  args.push("-deadline", preset2 === "ultrafast" ? "realtime" : "good");
71342
71346
  args.push("-row-mt", "1");
71343
71347
  appendVp9CpuUsedArg(args, vp9CpuUsed);
71344
- const lockGopVp9 = options.lockGopForChunkConcat === true;
71345
- if (lockGopVp9) {
71346
- if (typeof options.gopSize !== "number" || !Number.isFinite(options.gopSize) || options.gopSize <= 0) {
71347
- throw new Error(
71348
- `[chunkEncoder] lockGopForChunkConcat=true requires a positive integer gopSize (received ${String(options.gopSize)})`
71349
- );
71350
- }
71351
- const gop = Math.floor(options.gopSize);
71352
- args.push("-g", String(gop), "-keyint_min", String(gop), "-auto-alt-ref", "0");
71348
+ const vp9Gop = resolveLockedGopSize(options);
71349
+ const lockGopVp9 = vp9Gop !== null;
71350
+ if (vp9Gop !== null) {
71351
+ args.push("-g", String(vp9Gop), "-keyint_min", String(vp9Gop), "-auto-alt-ref", "0");
71353
71352
  }
71354
71353
  if (pixelFormat === "yuva420p") {
71355
71354
  if (!lockGopVp9) {
@@ -71896,15 +71895,18 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
71896
71895
  args.push("-c:v", encoderName, "-preset", preset2);
71897
71896
  if (bitrate) args.push("-b:v", bitrate);
71898
71897
  else args.push("-crf", String(quality));
71899
- if (codec === "h264") {
71898
+ const lockedGop = resolveLockedGopSize(options);
71899
+ if (lockedGop !== null) appendLockedGopArgs(args, lockedGop);
71900
+ if (codec === "h264" || lockedGop !== null) {
71900
71901
  args.push("-bf", "0");
71901
71902
  }
71902
71903
  const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
71903
71904
  const colorParams = options.rawInputFormat && options.hdr ? getHdrEncoderColorParams(options.hdr.transfer).x265ColorParams : "colorprim=bt709:transfer=bt709:colormatrix=bt709";
71905
+ const gopParams = lockedGop !== null ? `:${lockedGopCodecParams(codec, lockedGop)}` : "";
71904
71906
  if (preset2 === "ultrafast") {
71905
- args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
71907
+ args.push(xParamsFlag, `aq-mode=3:${colorParams}${gopParams}`);
71906
71908
  } else {
71907
- args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
71909
+ args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}${gopParams}`);
71908
71910
  }
71909
71911
  if (codec === "h265") {
71910
71912
  args.push("-tag:v", "hvc1");
@@ -72135,6 +72137,7 @@ var init_streamingEncoder = __esm({
72135
72137
  init_dist3();
72136
72138
  init_vp9Options();
72137
72139
  init_renderProvenance();
72140
+ init_chunkEncoder();
72138
72141
  }
72139
72142
  });
72140
72143
 
@@ -80437,12 +80440,12 @@ function findVisibleMarkupCommentLeak(source) {
80437
80440
  function readHtmlBodyCssSize(source) {
80438
80441
  const widthFirst = source.match(HTML_BODY_CSS_WIDTH_FIRST_RE);
80439
80442
  if (widthFirst) {
80440
- const [, width = "", height = ""] = widthFirst;
80443
+ const [, , width = "", , height = ""] = widthFirst;
80441
80444
  return { width, height };
80442
80445
  }
80443
80446
  const heightFirst = source.match(HTML_BODY_CSS_HEIGHT_FIRST_RE);
80444
80447
  if (heightFirst) {
80445
- const [, height = "", width = ""] = heightFirst;
80448
+ const [, , height = "", , width = ""] = heightFirst;
80446
80449
  return { width, height };
80447
80450
  }
80448
80451
  return null;
@@ -80450,18 +80453,39 @@ function readHtmlBodyCssSize(source) {
80450
80453
  function readViewportMetaSize(source) {
80451
80454
  const match = source.match(VIEWPORT_META_SIZE_RE);
80452
80455
  if (!match) return null;
80453
- const [, width = "", height = ""] = match;
80456
+ const [, , width = "", , height = ""] = match;
80454
80457
  return { width, height };
80455
80458
  }
80456
80459
  function describeSizeMismatch(label2, size, dataWidth, dataHeight) {
80457
80460
  if (!size || size.width === dataWidth && size.height === dataHeight) return null;
80458
80461
  return `${label2} is ${size.width}x${size.height}`;
80459
80462
  }
80460
- function findScaffoldSizeMismatches(source, dataWidth, dataHeight) {
80461
- return [
80462
- describeSizeMismatch("html/body CSS", readHtmlBodyCssSize(source), dataWidth, dataHeight),
80463
- describeSizeMismatch("the viewport meta", readViewportMetaSize(source), dataWidth, dataHeight)
80464
- ].filter((mismatch) => mismatch !== null);
80463
+ function describeRootDimensionsDrift(source, dataWidth, dataHeight) {
80464
+ const bodyCssMismatch = describeSizeMismatch(
80465
+ "html/body CSS",
80466
+ readHtmlBodyCssSize(source),
80467
+ dataWidth,
80468
+ dataHeight
80469
+ );
80470
+ const viewportMismatch = describeSizeMismatch(
80471
+ "the viewport meta",
80472
+ readViewportMetaSize(source),
80473
+ dataWidth,
80474
+ dataHeight
80475
+ );
80476
+ if (!bodyCssMismatch && !viewportMismatch) return null;
80477
+ const declared = `Root composition declares data-width="${dataWidth}" data-height="${dataHeight}"`;
80478
+ if (!bodyCssMismatch) {
80479
+ return {
80480
+ message: `${declared}, but ${viewportMismatch}. The viewport meta has no effect on capture \u2014 the renderer sizes the viewport from the root's own data-width/data-height \u2014 so this is stale metadata, not a clipping risk.`,
80481
+ fixHint: "update the meta viewport to match, or scaffold with `hyperframes init --resolution portrait`"
80482
+ };
80483
+ }
80484
+ const mismatches = viewportMismatch ? `${bodyCssMismatch} and ${viewportMismatch}` : bodyCssMismatch;
80485
+ return {
80486
+ message: `${declared}, but ${mismatches}. The scaffolded body clips the composition at its old size.`,
80487
+ fixHint: "update html/body CSS and the meta viewport to match, or scaffold with `hyperframes init --resolution portrait`"
80488
+ };
80465
80489
  }
80466
80490
  function tweensVolumeInSameCall(script, id) {
80467
80491
  const selector = new RegExp(`#${escapeRegExp4(id)}(?![\\w-])`, "g");
@@ -82803,22 +82827,24 @@ var init_dist4 = __esm({
82803
82827
  // root. `hyperframes check`'s layout audits can't see it: they measure
82804
82828
  // against the root's own (already-correct) rect, not the body's.
82805
82829
  //
82806
- // A sub-composition fragment has no html/body or viewport to compare
82807
- // against, which makes this top-level-only without a separate guard.
82808
- ({ rootTag, source }) => {
82809
- if (!rootTag) return [];
82830
+ // Sub-compositions are exempt: loadExternalCompositions (packages/core/src/
82831
+ // runtime/compositionLoader.ts) mounts only the matched <template>/<body>
82832
+ // subtree, so a sub-comp's own <html>/<head>/<meta viewport> never reach
82833
+ // the rendering document, even when it's a full standalone document.
82834
+ ({ rootTag, source, options }) => {
82835
+ if (!rootTag || options.isSubComposition) return [];
82810
82836
  const dataWidth = readAttr(rootTag.raw, "data-width");
82811
82837
  const dataHeight = readAttr(rootTag.raw, "data-height");
82812
82838
  if (!dataWidth || !dataHeight) return [];
82813
- const mismatches = findScaffoldSizeMismatches(source, dataWidth, dataHeight);
82814
- if (mismatches.length === 0) return [];
82839
+ const drift = describeRootDimensionsDrift(source, dataWidth, dataHeight);
82840
+ if (!drift) return [];
82815
82841
  return [
82816
82842
  {
82817
82843
  code: "root_dimensions_mismatch",
82818
82844
  severity: "warning",
82819
- message: `Root composition declares data-width="${dataWidth}" data-height="${dataHeight}", but ${mismatches.join(" and ")}. The scaffolded body clips the composition at its old size.`,
82845
+ message: drift.message,
82820
82846
  elementId: readAttr(rootTag.raw, "id") || void 0,
82821
- fixHint: "update html/body CSS and the meta viewport to match, or scaffold with `hyperframes init --resolution portrait`",
82847
+ fixHint: drift.fixHint,
82822
82848
  snippet: truncateSnippet(rootTag.raw)
82823
82849
  }
82824
82850
  ];
@@ -150156,19 +150182,16 @@ function applyResolutionPreset(destDir, resolution) {
150156
150182
  changed = true;
150157
150183
  }
150158
150184
  }
150159
- const bodyCssRe = /(html\s*,\s*body\s*\{[^}]*?width:\s*)\d+px([^}]*?height:\s*)\d+px/i;
150160
- if (bodyCssRe.test(html)) {
150161
- html = html.replace(bodyCssRe, `$1${width}px$2${height}px`);
150185
+ if (HTML_BODY_CSS_WIDTH_FIRST_RE.test(html)) {
150186
+ html = html.replace(HTML_BODY_CSS_WIDTH_FIRST_RE, `$1${width}px$3${height}px`);
150162
150187
  changed = true;
150163
150188
  }
150164
- const bodyCssReverseRe = /(html\s*,\s*body\s*\{[^}]*?height:\s*)\d+px([^}]*?width:\s*)\d+px/i;
150165
- if (bodyCssReverseRe.test(html)) {
150166
- html = html.replace(bodyCssReverseRe, `$1${height}px$2${width}px`);
150189
+ if (HTML_BODY_CSS_HEIGHT_FIRST_RE.test(html)) {
150190
+ html = html.replace(HTML_BODY_CSS_HEIGHT_FIRST_RE, `$1${height}px$3${width}px`);
150167
150191
  changed = true;
150168
150192
  }
150169
- const viewportRe = /(<meta[^>]*name=["']viewport["'][^>]*content=["'])width=\d+,\s*height=\d+/i;
150170
- if (viewportRe.test(html)) {
150171
- html = html.replace(viewportRe, `$1width=${width}, height=${height}`);
150193
+ if (VIEWPORT_META_SIZE_RE.test(html)) {
150194
+ html = html.replace(VIEWPORT_META_SIZE_RE, `$1${width}$3${height}`);
150172
150195
  changed = true;
150173
150196
  }
150174
150197
  if (changed) writeFileSync29(file, html, "utf-8");
@@ -150260,6 +150283,7 @@ var init_init = __esm({
150260
150283
  init_ffmpeg();
150261
150284
  init_version();
150262
150285
  init_dist3();
150286
+ init_dist2();
150263
150287
  examples3 = [
150264
150288
  ["Create a project with the interactive wizard", "hyperframes init my-video"],
150265
150289
  ["Pick a starter example", "hyperframes init my-video --example warm-grain"],
@@ -50355,9 +50355,9 @@ var init_dist = __esm({
50355
50355
  COMPOSITION_ATTRIBUTES.legacyTrack
50356
50356
  ]);
50357
50357
  OK = { ok: true };
50358
- HTML_BODY_CSS_WIDTH_FIRST_RE = /html\s*,\s*body\s*\{[^}]*?width:\s*(\d+)px[^}]*?height:\s*(\d+)px/i;
50359
- HTML_BODY_CSS_HEIGHT_FIRST_RE = /html\s*,\s*body\s*\{[^}]*?height:\s*(\d+)px[^}]*?width:\s*(\d+)px/i;
50360
- VIEWPORT_META_SIZE_RE = /<meta[^>]*name=["']viewport["'][^>]*content=["']width=(\d+),\s*height=(\d+)/i;
50358
+ HTML_BODY_CSS_WIDTH_FIRST_RE = /(html\s*,\s*body\s*\{[^}]*?width:\s*)(\d+)px([^}]*?height:\s*)(\d+)px/i;
50359
+ HTML_BODY_CSS_HEIGHT_FIRST_RE = /(html\s*,\s*body\s*\{[^}]*?height:\s*)(\d+)px([^}]*?width:\s*)(\d+)px/i;
50360
+ VIEWPORT_META_SIZE_RE = /(<meta[^>]*name=["']viewport["'][^>]*content=["']width=)(\d+)(,\s*height=)(\d+)/i;
50361
50361
  FONT_ALIAS_MAP = {
50362
50362
  // ── Canonical bundled fonts (self-referencing) ────────────────────────
50363
50363
  inter: "inter",
@@ -60405,6 +60405,31 @@ function getEncoderPreset(quality, format = "mp4", hdr) {
60405
60405
  }
60406
60406
  return { ...base2, pixelFormat: "yuv420p" };
60407
60407
  }
60408
+ function resolveLockedGopSize(options) {
60409
+ if (options.lockGopForChunkConcat !== true) return null;
60410
+ if (typeof options.gopSize !== "number" || !Number.isFinite(options.gopSize) || options.gopSize <= 0) {
60411
+ throw new Error(
60412
+ `[chunkEncoder] lockGopForChunkConcat=true requires a positive integer gopSize (received ${String(options.gopSize)})`
60413
+ );
60414
+ }
60415
+ return Math.floor(options.gopSize);
60416
+ }
60417
+ function appendLockedGopArgs(args, gopSize) {
60418
+ args.push(
60419
+ "-g",
60420
+ String(gopSize),
60421
+ "-keyint_min",
60422
+ String(gopSize),
60423
+ "-sc_threshold",
60424
+ "0",
60425
+ "-force_key_frames",
60426
+ `expr:eq(mod(n,${gopSize}),0)`
60427
+ );
60428
+ }
60429
+ function lockedGopCodecParams(codec, gopSize) {
60430
+ const shared = "scenecut=0:open-gop=0:repeat-headers=1";
60431
+ return codec === "h264" ? shared : `keyint=${gopSize}:min-keyint=${gopSize}:${shared}`;
60432
+ }
60408
60433
  function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
60409
60434
  const {
60410
60435
  fps,
@@ -60472,36 +60497,15 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
60472
60497
  args.push("-c:v", encoderName, "-preset", preset2);
60473
60498
  if (bitrate) args.push("-b:v", bitrate);
60474
60499
  else args.push("-crf", String(quality));
60475
- const lockGop = options.lockGopForChunkConcat === true;
60476
- let gop = 0;
60477
- if (lockGop) {
60478
- if (typeof options.gopSize !== "number" || !Number.isFinite(options.gopSize) || options.gopSize <= 0) {
60479
- throw new Error(
60480
- `[chunkEncoder] lockGopForChunkConcat=true requires a positive integer gopSize (received ${String(options.gopSize)})`
60481
- );
60482
- }
60483
- gop = Math.floor(options.gopSize);
60484
- args.push(
60485
- "-g",
60486
- String(gop),
60487
- "-keyint_min",
60488
- String(gop),
60489
- "-sc_threshold",
60490
- "0",
60491
- "-force_key_frames",
60492
- `expr:eq(mod(n,${gop}),0)`
60493
- );
60494
- }
60500
+ const gop = resolveLockedGopSize(options);
60501
+ const lockGop = gop !== null;
60502
+ if (gop !== null) appendLockedGopArgs(args, gop);
60495
60503
  if (codec === "h264" || codec === "h265" && lockGop) {
60496
60504
  args.push("-bf", "0");
60497
60505
  }
60498
60506
  const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
60499
60507
  const colorParams = codec === "h265" && options.hdr ? getHdrEncoderColorParams(options.hdr.transfer).x265ColorParams : "colorprim=bt709:transfer=bt709:colormatrix=bt709";
60500
- let gopParams = "";
60501
- if (lockGop) {
60502
- const shared = "scenecut=0:open-gop=0:repeat-headers=1";
60503
- gopParams = codec === "h264" ? shared : `keyint=${gop}:min-keyint=${gop}:${shared}`;
60504
- }
60508
+ const gopParams = gop !== null ? lockedGopCodecParams(codec, gop) : "";
60505
60509
  const joinParams = (...parts) => parts.filter((p) => p.length > 0).join(":");
60506
60510
  if (preset2 === "ultrafast") {
60507
60511
  args.push(xParamsFlag, joinParams("aq-mode=3", colorParams, gopParams));
@@ -60520,15 +60524,10 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
60520
60524
  args.push("-deadline", preset2 === "ultrafast" ? "realtime" : "good");
60521
60525
  args.push("-row-mt", "1");
60522
60526
  appendVp9CpuUsedArg(args, vp9CpuUsed);
60523
- const lockGopVp9 = options.lockGopForChunkConcat === true;
60524
- if (lockGopVp9) {
60525
- if (typeof options.gopSize !== "number" || !Number.isFinite(options.gopSize) || options.gopSize <= 0) {
60526
- throw new Error(
60527
- `[chunkEncoder] lockGopForChunkConcat=true requires a positive integer gopSize (received ${String(options.gopSize)})`
60528
- );
60529
- }
60530
- const gop = Math.floor(options.gopSize);
60531
- args.push("-g", String(gop), "-keyint_min", String(gop), "-auto-alt-ref", "0");
60527
+ const vp9Gop = resolveLockedGopSize(options);
60528
+ const lockGopVp9 = vp9Gop !== null;
60529
+ if (vp9Gop !== null) {
60530
+ args.push("-g", String(vp9Gop), "-keyint_min", String(vp9Gop), "-auto-alt-ref", "0");
60532
60531
  }
60533
60532
  if (pixelFormat === "yuva420p") {
60534
60533
  if (!lockGopVp9) {
@@ -61075,15 +61074,18 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
61075
61074
  args.push("-c:v", encoderName, "-preset", preset2);
61076
61075
  if (bitrate) args.push("-b:v", bitrate);
61077
61076
  else args.push("-crf", String(quality));
61078
- if (codec === "h264") {
61077
+ const lockedGop = resolveLockedGopSize(options);
61078
+ if (lockedGop !== null) appendLockedGopArgs(args, lockedGop);
61079
+ if (codec === "h264" || lockedGop !== null) {
61079
61080
  args.push("-bf", "0");
61080
61081
  }
61081
61082
  const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
61082
61083
  const colorParams = options.rawInputFormat && options.hdr ? getHdrEncoderColorParams(options.hdr.transfer).x265ColorParams : "colorprim=bt709:transfer=bt709:colormatrix=bt709";
61084
+ const gopParams = lockedGop !== null ? `:${lockedGopCodecParams(codec, lockedGop)}` : "";
61083
61085
  if (preset2 === "ultrafast") {
61084
- args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
61086
+ args.push(xParamsFlag, `aq-mode=3:${colorParams}${gopParams}`);
61085
61087
  } else {
61086
- args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
61088
+ args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}${gopParams}`);
61087
61089
  }
61088
61090
  if (codec === "h265") {
61089
61091
  args.push("-tag:v", "hvc1");
@@ -61314,6 +61316,7 @@ var init_streamingEncoder = __esm({
61314
61316
  init_dist2();
61315
61317
  init_vp9Options();
61316
61318
  init_renderProvenance();
61319
+ init_chunkEncoder();
61317
61320
  }
61318
61321
  });
61319
61322
 
@@ -69430,12 +69433,12 @@ function findVisibleMarkupCommentLeak(source) {
69430
69433
  function readHtmlBodyCssSize(source) {
69431
69434
  const widthFirst = source.match(HTML_BODY_CSS_WIDTH_FIRST_RE);
69432
69435
  if (widthFirst) {
69433
- const [, width = "", height = ""] = widthFirst;
69436
+ const [, , width = "", , height = ""] = widthFirst;
69434
69437
  return { width, height };
69435
69438
  }
69436
69439
  const heightFirst = source.match(HTML_BODY_CSS_HEIGHT_FIRST_RE);
69437
69440
  if (heightFirst) {
69438
- const [, height = "", width = ""] = heightFirst;
69441
+ const [, , height = "", , width = ""] = heightFirst;
69439
69442
  return { width, height };
69440
69443
  }
69441
69444
  return null;
@@ -69443,18 +69446,39 @@ function readHtmlBodyCssSize(source) {
69443
69446
  function readViewportMetaSize(source) {
69444
69447
  const match = source.match(VIEWPORT_META_SIZE_RE);
69445
69448
  if (!match) return null;
69446
- const [, width = "", height = ""] = match;
69449
+ const [, , width = "", , height = ""] = match;
69447
69450
  return { width, height };
69448
69451
  }
69449
69452
  function describeSizeMismatch(label, size, dataWidth, dataHeight) {
69450
69453
  if (!size || size.width === dataWidth && size.height === dataHeight) return null;
69451
69454
  return `${label} is ${size.width}x${size.height}`;
69452
69455
  }
69453
- function findScaffoldSizeMismatches(source, dataWidth, dataHeight) {
69454
- return [
69455
- describeSizeMismatch("html/body CSS", readHtmlBodyCssSize(source), dataWidth, dataHeight),
69456
- describeSizeMismatch("the viewport meta", readViewportMetaSize(source), dataWidth, dataHeight)
69457
- ].filter((mismatch) => mismatch !== null);
69456
+ function describeRootDimensionsDrift(source, dataWidth, dataHeight) {
69457
+ const bodyCssMismatch = describeSizeMismatch(
69458
+ "html/body CSS",
69459
+ readHtmlBodyCssSize(source),
69460
+ dataWidth,
69461
+ dataHeight
69462
+ );
69463
+ const viewportMismatch = describeSizeMismatch(
69464
+ "the viewport meta",
69465
+ readViewportMetaSize(source),
69466
+ dataWidth,
69467
+ dataHeight
69468
+ );
69469
+ if (!bodyCssMismatch && !viewportMismatch) return null;
69470
+ const declared = `Root composition declares data-width="${dataWidth}" data-height="${dataHeight}"`;
69471
+ if (!bodyCssMismatch) {
69472
+ return {
69473
+ message: `${declared}, but ${viewportMismatch}. The viewport meta has no effect on capture \u2014 the renderer sizes the viewport from the root's own data-width/data-height \u2014 so this is stale metadata, not a clipping risk.`,
69474
+ fixHint: "update the meta viewport to match, or scaffold with `hyperframes init --resolution portrait`"
69475
+ };
69476
+ }
69477
+ const mismatches = viewportMismatch ? `${bodyCssMismatch} and ${viewportMismatch}` : bodyCssMismatch;
69478
+ return {
69479
+ message: `${declared}, but ${mismatches}. The scaffolded body clips the composition at its old size.`,
69480
+ fixHint: "update html/body CSS and the meta viewport to match, or scaffold with `hyperframes init --resolution portrait`"
69481
+ };
69458
69482
  }
69459
69483
  function tweensVolumeInSameCall(script, id) {
69460
69484
  const selector = new RegExp(`#${escapeRegExp3(id)}(?![\\w-])`, "g");
@@ -71075,22 +71099,24 @@ var init_dist3 = __esm({
71075
71099
  // root. `hyperframes check`'s layout audits can't see it: they measure
71076
71100
  // against the root's own (already-correct) rect, not the body's.
71077
71101
  //
71078
- // A sub-composition fragment has no html/body or viewport to compare
71079
- // against, which makes this top-level-only without a separate guard.
71080
- ({ rootTag, source }) => {
71081
- if (!rootTag) return [];
71102
+ // Sub-compositions are exempt: loadExternalCompositions (packages/core/src/
71103
+ // runtime/compositionLoader.ts) mounts only the matched <template>/<body>
71104
+ // subtree, so a sub-comp's own <html>/<head>/<meta viewport> never reach
71105
+ // the rendering document, even when it's a full standalone document.
71106
+ ({ rootTag, source, options }) => {
71107
+ if (!rootTag || options.isSubComposition) return [];
71082
71108
  const dataWidth = readAttr(rootTag.raw, "data-width");
71083
71109
  const dataHeight = readAttr(rootTag.raw, "data-height");
71084
71110
  if (!dataWidth || !dataHeight) return [];
71085
- const mismatches = findScaffoldSizeMismatches(source, dataWidth, dataHeight);
71086
- if (mismatches.length === 0) return [];
71111
+ const drift = describeRootDimensionsDrift(source, dataWidth, dataHeight);
71112
+ if (!drift) return [];
71087
71113
  return [
71088
71114
  {
71089
71115
  code: "root_dimensions_mismatch",
71090
71116
  severity: "warning",
71091
- message: `Root composition declares data-width="${dataWidth}" data-height="${dataHeight}", but ${mismatches.join(" and ")}. The scaffolded body clips the composition at its old size.`,
71117
+ message: drift.message,
71092
71118
  elementId: readAttr(rootTag.raw, "id") || void 0,
71093
- fixHint: "update html/body CSS and the meta viewport to match, or scaffold with `hyperframes init --resolution portrait`",
71119
+ fixHint: drift.fixHint,
71094
71120
  snippet: truncateSnippet(rootTag.raw)
71095
71121
  }
71096
71122
  ];
@@ -99807,8 +99833,8 @@ async function runFontLocalize(io, localize) {
99807
99833
  }
99808
99834
 
99809
99835
  // src/version.ts
99810
- var VERSION = true ? "0.8.43" : "0.0.0-dev";
99811
- var PRODUCER_VERSION = true ? "0.8.43" : "0.0.0-dev";
99836
+ var VERSION = true ? "0.8.44" : "0.0.0-dev";
99837
+ var PRODUCER_VERSION = true ? "0.8.44" : "0.0.0-dev";
99812
99838
 
99813
99839
  // src/fontLocalizeCli.ts
99814
99840
  async function readStdin() {
@@ -1,4 +1,4 @@
1
- "use strict";var HyperframesPlayer=(()=>{var ee=Object.defineProperty;var et=Object.getOwnPropertyDescriptor;var tt=Object.getOwnPropertyNames;var it=Object.prototype.hasOwnProperty;var rt=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of tt(e))!it.call(r,n)&&n!==t&&ee(r,n,{get:()=>e[n],enumerable:!(i=et(e,n))||i.enumerable});return r};var ot=r=>nt(ee({},"__esModule",{value:!0}),r);var Ht={};rt(Ht,{HyperframesPlayer:()=>K,SPEED_PRESETS:()=>re,formatSpeed:()=>O,formatTime:()=>q});function we(r){return r.hasRuntime||r.runtimeInjected?!1:!!(r.hasNestedCompositions||r.hasTimelines&&r.attempts>=5)}function P(r){return typeof r=="object"&&r!==null}function Ae(r){return P(r)&&typeof r.getDuration=="function"}function Ce(r){return P(r)&&typeof r.duration=="function"&&typeof r.time=="function"&&typeof r.seek=="function"&&typeof r.play=="function"&&typeof r.pause=="function"}var k="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.43/dist/hyperframe.runtime.iife.js";function N(r){if(r===null)return null;let e=Number.parseInt(r,10);return Number.isFinite(e)&&e>0?e:null}function at(r){let e=r?.querySelector("[data-composition-id][data-width][data-height]")??r?.querySelector("[data-width][data-height]");if(!e)return null;let t=N(e.getAttribute("data-width")),i=N(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var W=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(we({hasRuntime:i,hasTimelines:n,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();let s=at(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||P(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=k,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!P(t))return null;let i=Object.keys(t);if(i.length===0)return null;let n=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=n&&n in t?n:i[i.length-1],d=t[o];return Ce(d)?d:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(Ae(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var Re=`
1
+ "use strict";var HyperframesPlayer=(()=>{var ee=Object.defineProperty;var et=Object.getOwnPropertyDescriptor;var tt=Object.getOwnPropertyNames;var it=Object.prototype.hasOwnProperty;var rt=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of tt(e))!it.call(r,n)&&n!==t&&ee(r,n,{get:()=>e[n],enumerable:!(i=et(e,n))||i.enumerable});return r};var ot=r=>nt(ee({},"__esModule",{value:!0}),r);var Ht={};rt(Ht,{HyperframesPlayer:()=>K,SPEED_PRESETS:()=>re,formatSpeed:()=>O,formatTime:()=>q});function we(r){return r.hasRuntime||r.runtimeInjected?!1:!!(r.hasNestedCompositions||r.hasTimelines&&r.attempts>=5)}function P(r){return typeof r=="object"&&r!==null}function Ae(r){return P(r)&&typeof r.getDuration=="function"}function Ce(r){return P(r)&&typeof r.duration=="function"&&typeof r.time=="function"&&typeof r.seek=="function"&&typeof r.play=="function"&&typeof r.pause=="function"}var k="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.44/dist/hyperframe.runtime.iife.js";function N(r){if(r===null)return null;let e=Number.parseInt(r,10);return Number.isFinite(e)&&e>0?e:null}function at(r){let e=r?.querySelector("[data-composition-id][data-width][data-height]")??r?.querySelector("[data-width][data-height]");if(!e)return null;let t=N(e.getAttribute("data-width")),i=N(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var W=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(we({hasRuntime:i,hasTimelines:n,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();let s=at(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||P(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=k,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!P(t))return null;let i=Object.keys(t);if(i.length===0)return null;let n=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=n&&n in t?n:i[i.length-1],d=t[o];return Ce(d)?d:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(Ae(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var Re=`
2
2
  :host {
3
3
  display: block;
4
4
  position: relative;
@@ -54938,9 +54938,9 @@ var LEGACY_TIMING_ATTRIBUTES = Object.freeze([
54938
54938
  COMPOSITION_ATTRIBUTES.derivedEnd,
54939
54939
  COMPOSITION_ATTRIBUTES.legacyTrack
54940
54940
  ]);
54941
- var HTML_BODY_CSS_WIDTH_FIRST_RE = /html\s*,\s*body\s*\{[^}]*?width:\s*(\d+)px[^}]*?height:\s*(\d+)px/i;
54942
- var HTML_BODY_CSS_HEIGHT_FIRST_RE = /html\s*,\s*body\s*\{[^}]*?height:\s*(\d+)px[^}]*?width:\s*(\d+)px/i;
54943
- var VIEWPORT_META_SIZE_RE = /<meta[^>]*name=["']viewport["'][^>]*content=["']width=(\d+),\s*height=(\d+)/i;
54941
+ var HTML_BODY_CSS_WIDTH_FIRST_RE = /(html\s*,\s*body\s*\{[^}]*?width:\s*)(\d+)px([^}]*?height:\s*)(\d+)px/i;
54942
+ var HTML_BODY_CSS_HEIGHT_FIRST_RE = /(html\s*,\s*body\s*\{[^}]*?height:\s*)(\d+)px([^}]*?width:\s*)(\d+)px/i;
54943
+ var VIEWPORT_META_SIZE_RE = /(<meta[^>]*name=["']viewport["'][^>]*content=["']width=)(\d+)(,\s*height=)(\d+)/i;
54944
54944
  function parseProgram2(script) {
54945
54945
  try {
54946
54946
  return parse3(script, { ecmaVersion: "latest", sourceType: "script" });
@@ -57143,12 +57143,12 @@ function findVisibleMarkupCommentLeak(source) {
57143
57143
  function readHtmlBodyCssSize(source) {
57144
57144
  const widthFirst = source.match(HTML_BODY_CSS_WIDTH_FIRST_RE);
57145
57145
  if (widthFirst) {
57146
- const [, width = "", height = ""] = widthFirst;
57146
+ const [, , width = "", , height = ""] = widthFirst;
57147
57147
  return { width, height };
57148
57148
  }
57149
57149
  const heightFirst = source.match(HTML_BODY_CSS_HEIGHT_FIRST_RE);
57150
57150
  if (heightFirst) {
57151
- const [, height = "", width = ""] = heightFirst;
57151
+ const [, , height = "", , width = ""] = heightFirst;
57152
57152
  return { width, height };
57153
57153
  }
57154
57154
  return null;
@@ -57156,18 +57156,39 @@ function readHtmlBodyCssSize(source) {
57156
57156
  function readViewportMetaSize(source) {
57157
57157
  const match = source.match(VIEWPORT_META_SIZE_RE);
57158
57158
  if (!match) return null;
57159
- const [, width = "", height = ""] = match;
57159
+ const [, , width = "", , height = ""] = match;
57160
57160
  return { width, height };
57161
57161
  }
57162
57162
  function describeSizeMismatch(label, size, dataWidth, dataHeight) {
57163
57163
  if (!size || size.width === dataWidth && size.height === dataHeight) return null;
57164
57164
  return `${label} is ${size.width}x${size.height}`;
57165
57165
  }
57166
- function findScaffoldSizeMismatches(source, dataWidth, dataHeight) {
57167
- return [
57168
- describeSizeMismatch("html/body CSS", readHtmlBodyCssSize(source), dataWidth, dataHeight),
57169
- describeSizeMismatch("the viewport meta", readViewportMetaSize(source), dataWidth, dataHeight)
57170
- ].filter((mismatch) => mismatch !== null);
57166
+ function describeRootDimensionsDrift(source, dataWidth, dataHeight) {
57167
+ const bodyCssMismatch = describeSizeMismatch(
57168
+ "html/body CSS",
57169
+ readHtmlBodyCssSize(source),
57170
+ dataWidth,
57171
+ dataHeight
57172
+ );
57173
+ const viewportMismatch = describeSizeMismatch(
57174
+ "the viewport meta",
57175
+ readViewportMetaSize(source),
57176
+ dataWidth,
57177
+ dataHeight
57178
+ );
57179
+ if (!bodyCssMismatch && !viewportMismatch) return null;
57180
+ const declared = `Root composition declares data-width="${dataWidth}" data-height="${dataHeight}"`;
57181
+ if (!bodyCssMismatch) {
57182
+ return {
57183
+ message: `${declared}, but ${viewportMismatch}. The viewport meta has no effect on capture \u2014 the renderer sizes the viewport from the root's own data-width/data-height \u2014 so this is stale metadata, not a clipping risk.`,
57184
+ fixHint: "update the meta viewport to match, or scaffold with `hyperframes init --resolution portrait`"
57185
+ };
57186
+ }
57187
+ const mismatches = viewportMismatch ? `${bodyCssMismatch} and ${viewportMismatch}` : bodyCssMismatch;
57188
+ return {
57189
+ message: `${declared}, but ${mismatches}. The scaffolded body clips the composition at its old size.`,
57190
+ fixHint: "update html/body CSS and the meta viewport to match, or scaffold with `hyperframes init --resolution portrait`"
57191
+ };
57171
57192
  }
57172
57193
  var coreRules = [
57173
57194
  // id_requires_css_escape
@@ -57224,22 +57245,24 @@ var coreRules = [
57224
57245
  // root. `hyperframes check`'s layout audits can't see it: they measure
57225
57246
  // against the root's own (already-correct) rect, not the body's.
57226
57247
  //
57227
- // A sub-composition fragment has no html/body or viewport to compare
57228
- // against, which makes this top-level-only without a separate guard.
57229
- ({ rootTag, source }) => {
57230
- if (!rootTag) return [];
57248
+ // Sub-compositions are exempt: loadExternalCompositions (packages/core/src/
57249
+ // runtime/compositionLoader.ts) mounts only the matched <template>/<body>
57250
+ // subtree, so a sub-comp's own <html>/<head>/<meta viewport> never reach
57251
+ // the rendering document, even when it's a full standalone document.
57252
+ ({ rootTag, source, options }) => {
57253
+ if (!rootTag || options.isSubComposition) return [];
57231
57254
  const dataWidth = readAttr(rootTag.raw, "data-width");
57232
57255
  const dataHeight = readAttr(rootTag.raw, "data-height");
57233
57256
  if (!dataWidth || !dataHeight) return [];
57234
- const mismatches = findScaffoldSizeMismatches(source, dataWidth, dataHeight);
57235
- if (mismatches.length === 0) return [];
57257
+ const drift = describeRootDimensionsDrift(source, dataWidth, dataHeight);
57258
+ if (!drift) return [];
57236
57259
  return [
57237
57260
  {
57238
57261
  code: "root_dimensions_mismatch",
57239
57262
  severity: "warning",
57240
- message: `Root composition declares data-width="${dataWidth}" data-height="${dataHeight}", but ${mismatches.join(" and ")}. The scaffolded body clips the composition at its old size.`,
57263
+ message: drift.message,
57241
57264
  elementId: readAttr(rootTag.raw, "id") || void 0,
57242
- fixHint: "update html/body CSS and the meta viewport to match, or scaffold with `hyperframes init --resolution portrait`",
57265
+ fixHint: drift.fixHint,
57243
57266
  snippet: truncateSnippet(rootTag.raw)
57244
57267
  }
57245
57268
  ];