@hyperframes/producer 0.8.149 → 0.8.150

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.
@@ -1417,9 +1417,18 @@ import {
1417
1417
 
1418
1418
  // src/services/renderMediaCollector.ts
1419
1419
  import { parseHTML as parseHTML2 } from "linkedom";
1420
- import { MEDIA_RENDER_ID_ATTR, resolveAuthoredTimingWindow } from "@hyperframes/core";
1420
+ import {
1421
+ MEDIA_RENDER_ID_ATTR,
1422
+ normalizeRateSpec as normalizeRateSpec2,
1423
+ resolveAuthoredTimingWindow,
1424
+ shiftRateLane,
1425
+ sourceTimeAt as sourceTimeAt2
1426
+ } from "@hyperframes/core";
1421
1427
  import {
1422
1428
  MEDIA_START_BASIS_ATTR,
1429
+ compositionOriginSeconds,
1430
+ cutToHostSlots,
1431
+ hostInPointSeconds,
1423
1432
  readMediaStartBasis,
1424
1433
  resolveAbsoluteMediaStartSeconds
1425
1434
  } from "@hyperframes/core/media-timing";
@@ -1429,7 +1438,6 @@ import {
1429
1438
  parseAudioElements,
1430
1439
  resolveReferencedStart
1431
1440
  } from "@hyperframes/engine";
1432
- var COMPOSITION_HOST_ATTR = "data-composition-file";
1433
1441
  function resolveHostEnd(host, hostStart) {
1434
1442
  return resolveAuthoredTimingWindow({
1435
1443
  start: hostStart,
@@ -1437,39 +1445,52 @@ function resolveHostEnd(host, hostStart) {
1437
1445
  end: host.getAttribute("data-end")
1438
1446
  })?.end ?? null;
1439
1447
  }
1440
- var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local" };
1441
- function resolveHostWindow(element, document2, startCache, visiting) {
1448
+ var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local", slots: [] };
1449
+ var COMPOSITION_HOST_SELECTOR = "[data-composition-id], [data-composition-src], [data-composition-file]";
1450
+ function enclosingHosts(element) {
1442
1451
  const hosts = [];
1443
1452
  for (let ancestor = element.parentElement; ancestor; ancestor = ancestor.parentElement) {
1444
- if (ancestor.hasAttribute(COMPOSITION_HOST_ATTR)) hosts.push(ancestor);
1453
+ if (ancestor.matches(COMPOSITION_HOST_SELECTOR)) {
1454
+ hosts.unshift(ancestor);
1455
+ }
1445
1456
  }
1457
+ return hosts;
1458
+ }
1459
+ function resolveHostWindow(element, document2, startCache, visiting) {
1460
+ const hosts = enclosingHosts(element);
1446
1461
  if (hosts.length === 0) return ROOT_WINDOW;
1447
1462
  let offset = 0;
1448
1463
  let limit = Infinity;
1449
- for (const host of hosts.reverse()) {
1464
+ const slots = [];
1465
+ for (const [index, host] of hosts.entries()) {
1450
1466
  const hostStart = resolveReferencedStart(document2, host, startCache, visiting);
1451
1467
  const hostEnd = resolveHostEnd(host, hostStart);
1452
- if (hostEnd != null) limit = Math.min(limit, offset + hostEnd);
1453
- offset += hostStart;
1468
+ if (hostEnd != null && !isRootComposition(host, index)) {
1469
+ limit = Math.min(limit, offset + hostEnd);
1470
+ }
1471
+ if (hostInPointSeconds(host) > 0) {
1472
+ slots.push({ start: offset + hostStart, end: offset + (hostEnd ?? Infinity) });
1473
+ }
1474
+ offset += compositionOriginSeconds(hostStart, host);
1454
1475
  }
1476
+ return { offset, limit, basis: mediaStartBasis(element), slots };
1477
+ }
1478
+ var isRootComposition = (host, depth) => depth === 0 && !host.matches("[data-composition-src], [data-composition-file]");
1479
+ function mediaStartBasis(element) {
1455
1480
  const tag = element.tagName.toLowerCase();
1456
- const basis = tag === "video" || tag === "audio" ? readMediaStartBasis(element.getAttribute(MEDIA_START_BASIS_ATTR)) : "local";
1457
- return {
1458
- offset,
1459
- limit,
1460
- basis
1461
- };
1481
+ return tag === "video" || tag === "audio" ? readMediaStartBasis(element.getAttribute(MEDIA_START_BASIS_ATTR)) : "local";
1462
1482
  }
1463
1483
  function collectHostWindows(html) {
1464
1484
  const { document: document2 } = parseHTML2(html);
1465
1485
  const windows = /* @__PURE__ */ new Map();
1466
1486
  const startCache = /* @__PURE__ */ new Map();
1467
1487
  const visiting = /* @__PURE__ */ new Set();
1468
- for (const element of document2.querySelectorAll(`[${MEDIA_RENDER_ID_ATTR}]`)) {
1488
+ for (const element of document2.querySelectorAll("video, audio, img[src]")) {
1469
1489
  const renderId = element.getAttribute(MEDIA_RENDER_ID_ATTR);
1470
- if (!renderId) continue;
1490
+ const id = renderId || element.getAttribute("id");
1491
+ if (!id || !renderId && windows.has(id)) continue;
1471
1492
  windows.set(
1472
- renderId,
1493
+ id,
1473
1494
  resolveHostWindow(element, document2, startCache, visiting)
1474
1495
  );
1475
1496
  }
@@ -1487,34 +1508,59 @@ function toAbsoluteWindow(start, end, window2) {
1487
1508
  hostStart: window2.offset,
1488
1509
  basis: window2.basis
1489
1510
  });
1490
- return { start: absoluteStart, end: Math.min(absoluteEnd, window2.limit) };
1511
+ const absolute = { start: absoluteStart, end: Math.min(absoluteEnd, window2.limit) };
1512
+ const played = cutToHostSlots(absolute, window2.slots);
1513
+ if (played.end === played.start && absolute.end > absolute.start) return null;
1514
+ return { start: absolute.start, end: played.end, played };
1515
+ }
1516
+ function startAtPlayed(clip, played) {
1517
+ const head = played.start - clip.start;
1518
+ if (head <= 0) return clip;
1519
+ if ("loop" in clip && clip.loop) return { ...clip, start: played.start };
1520
+ const rate = normalizeRateSpec2(clip.playbackRate);
1521
+ return {
1522
+ ...clip,
1523
+ start: played.start,
1524
+ mediaStart: clip.mediaStart + sourceTimeAt2(rate, head),
1525
+ playbackRate: shiftRateLane(rate, head)
1526
+ };
1491
1527
  }
1492
1528
  function collectRenderMedia(html) {
1493
1529
  const windows = collectHostWindows(html);
1494
1530
  const windowFor = (id) => windows.get(id) ?? ROOT_WINDOW;
1531
+ const droppedMediaIds = /* @__PURE__ */ new Set();
1495
1532
  const videos = [];
1496
1533
  for (const video of parseVideoElements(html)) {
1497
1534
  const absolute = toAbsoluteWindow(video.start, video.end, windowFor(video.id));
1498
- if (absolute) videos.push({ ...video, ...absolute });
1535
+ if (!absolute) {
1536
+ droppedMediaIds.add(video.id);
1537
+ continue;
1538
+ }
1539
+ videos.push(
1540
+ startAtPlayed({ ...video, start: absolute.start, end: absolute.end }, absolute.played)
1541
+ );
1499
1542
  }
1500
1543
  const images = [];
1501
1544
  for (const image of parseImageElements(html)) {
1502
1545
  const absolute = toAbsoluteWindow(image.start, image.end, windowFor(image.id));
1503
- if (absolute) images.push({ ...image, ...absolute });
1546
+ if (absolute) images.push({ ...image, start: absolute.start, end: absolute.end });
1547
+ else droppedMediaIds.add(image.id);
1504
1548
  }
1505
1549
  const audios = [];
1506
1550
  for (const audio of parseAudioElements(html)) {
1507
1551
  const elementId = audio.type === "video" ? audio.id.replace(/-audio$/, "") : audio.id;
1508
- const authoredEnd = audio.end > 0 ? audio.end : Infinity;
1509
- const absolute = toAbsoluteWindow(audio.start, authoredEnd, windowFor(elementId));
1510
- if (!absolute) continue;
1511
- audios.push({
1512
- ...audio,
1513
- start: absolute.start,
1514
- end: Number.isFinite(absolute.end) ? absolute.end : 0
1515
- });
1552
+ const placed = placeAudio(audio, windowFor(elementId));
1553
+ if (placed) audios.push(placed);
1554
+ else droppedMediaIds.add(elementId);
1516
1555
  }
1517
- return { videos, audios, images };
1556
+ return { videos, audios, images, droppedMediaIds };
1557
+ }
1558
+ function placeAudio(audio, window2) {
1559
+ const authoredEnd = audio.end > 0 ? audio.end : Infinity;
1560
+ const absolute = toAbsoluteWindow(audio.start, authoredEnd, window2);
1561
+ if (!absolute) return null;
1562
+ const end = Number.isFinite(absolute.end) ? absolute.end : 0;
1563
+ return startAtPlayed({ ...audio, start: absolute.start, end }, absolute.played);
1518
1564
  }
1519
1565
 
1520
1566
  // src/services/htmlCompiler.ts
@@ -1522,6 +1568,7 @@ import {
1522
1568
  analyzeKeyframeIntervals,
1523
1569
  assertMediaPayload,
1524
1570
  NotMediaPayloadError as NotMediaPayloadError2,
1571
+ parseMasterFxChain,
1525
1572
  probeMediaProfile as probeMediaProfile2
1526
1573
  } from "@hyperframes/engine";
1527
1574
 
@@ -3221,7 +3268,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
3221
3268
  for (const [relPath, absPath] of preparedGifAssets) {
3222
3269
  externalAssets.set(relPath, absPath);
3223
3270
  }
3224
- const { videos, audios, images } = collectRenderMedia(html);
3271
+ const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
3272
+ const masterFxChain = parseMasterFxChain(html);
3225
3273
  for (const video of videos) {
3226
3274
  if (isHttpUrl(video.src)) continue;
3227
3275
  const videoPath = resolve5(projectDir, video.src);
@@ -3255,6 +3303,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
3255
3303
  videos,
3256
3304
  audios,
3257
3305
  images,
3306
+ droppedMediaIds,
3307
+ ...masterFxChain ? { masterFxChain } : {},
3258
3308
  unresolvedCompositions,
3259
3309
  externalAssets,
3260
3310
  width,
@@ -3687,6 +3737,7 @@ async function runCompileStage(input) {
3687
3737
  videos: compiled.videos,
3688
3738
  audios: compiled.audios,
3689
3739
  images: compiled.images,
3740
+ masterFxChain: compiled.masterFxChain,
3690
3741
  width: compiled.width,
3691
3742
  height: compiled.height
3692
3743
  };
@@ -3749,7 +3800,7 @@ import {
3749
3800
  isTransientBrowserError,
3750
3801
  probeBeginFrameLiveness
3751
3802
  } from "@hyperframes/engine";
3752
- import { fpsToNumber as fpsToNumber7 } from "@hyperframes/core";
3803
+ import { MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2, fpsToNumber as fpsToNumber7, readMediaStart as readMediaStart2 } from "@hyperframes/core";
3753
3804
  import { extractMediaSrcMutations } from "@hyperframes/parsers";
3754
3805
 
3755
3806
  // src/services/render/stages/probeFailures.ts
@@ -3796,6 +3847,26 @@ function hasVariableBoundMedia(html, variables) {
3796
3847
  return Boolean(variableId && Object.hasOwn(variables, variableId));
3797
3848
  });
3798
3849
  }
3850
+ function authoredMediaStarts(html) {
3851
+ const { document: document2 } = parseHTML5(html);
3852
+ const starts = /* @__PURE__ */ new Map();
3853
+ for (const media of document2.querySelectorAll("video, audio")) {
3854
+ const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
3855
+ if (id && !starts.has(id)) starts.set(id, readMediaStart2(media));
3856
+ }
3857
+ return starts;
3858
+ }
3859
+ function adoptRuntimeMediaStart(composition, el, authored) {
3860
+ const shift = el.mediaStart - authored;
3861
+ if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
3862
+ const entries = el.tagName === "video" ? [
3863
+ ...composition.videos.filter((video) => video.id === el.id),
3864
+ ...composition.audios.filter(
3865
+ (audio) => audio.type === "video" && audio.id === `${el.id}-audio`
3866
+ )
3867
+ ] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
3868
+ for (const entry of entries) entry.mediaStart += shift;
3869
+ }
3799
3870
  function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
3800
3871
  if (projectedEnd <= 0) return existingEnd;
3801
3872
  if (sourceChanged && durationInferred) return projectedEnd;
@@ -4049,6 +4120,7 @@ async function runProbeStage(input) {
4049
4120
  const existingVideoIds = new Set(composition.videos.map((v) => v.id));
4050
4121
  const existingAudioIds = new Set(composition.audios.map((a) => a.id));
4051
4122
  const existingImageIds = new Set(composition.images.map((image) => image.id));
4123
+ const authoredStarts = authoredMediaStarts(compiled.html);
4052
4124
  pruneMutedBrowserMedia(composition, browserMedia, existingAudioIds);
4053
4125
  for (const el of browserMedia) {
4054
4126
  if (!el.src || el.src === "about:blank") continue;
@@ -4076,9 +4148,11 @@ async function runProbeStage(input) {
4076
4148
  sourceChanged,
4077
4149
  el.durationInferred
4078
4150
  );
4079
- if (el.mediaStart > 0 && (existing.mediaStart <= 0 || Math.abs(existing.mediaStart - el.mediaStart) > BROWSER_MEDIA_EPSILON)) {
4080
- existing.mediaStart = el.mediaStart;
4081
- }
4151
+ adoptRuntimeMediaStart(
4152
+ composition,
4153
+ el,
4154
+ authoredStarts.get(el.id) ?? existing.mediaStart
4155
+ );
4082
4156
  if (el.hasAudio && !el.muted && !existing.hasAudio) {
4083
4157
  existing.hasAudio = true;
4084
4158
  }
@@ -4086,7 +4160,7 @@ async function runProbeStage(input) {
4086
4160
  existing.loop = true;
4087
4161
  }
4088
4162
  }
4089
- } else {
4163
+ } else if (!compiled.droppedMediaIds.has(el.id)) {
4090
4164
  composition.videos.push({
4091
4165
  id: el.id,
4092
4166
  src,
@@ -4117,14 +4191,16 @@ async function runProbeStage(input) {
4117
4191
  sourceChanged,
4118
4192
  el.durationInferred
4119
4193
  );
4120
- if (el.mediaStart > 0 && (existing.mediaStart <= 0 || Math.abs(existing.mediaStart - el.mediaStart) > BROWSER_MEDIA_EPSILON)) {
4121
- existing.mediaStart = el.mediaStart;
4122
- }
4194
+ adoptRuntimeMediaStart(
4195
+ composition,
4196
+ el,
4197
+ authoredStarts.get(el.id) ?? existing.mediaStart
4198
+ );
4123
4199
  if (el.volume > 0 && Math.abs((existing.volume ?? 1) - el.volume) > BROWSER_MEDIA_EPSILON) {
4124
4200
  existing.volume = el.volume;
4125
4201
  }
4126
4202
  }
4127
- } else {
4203
+ } else if (!compiled.droppedMediaIds.has(el.id)) {
4128
4204
  composition.audios.push({
4129
4205
  id: el.id,
4130
4206
  src,
@@ -4152,7 +4228,7 @@ async function runProbeStage(input) {
4152
4228
  existing.end = projectedEnd;
4153
4229
  }
4154
4230
  }
4155
- } else {
4231
+ } else if (!compiled.droppedMediaIds.has(el.id)) {
4156
4232
  composition.images.push({
4157
4233
  id: el.id,
4158
4234
  src,
@@ -4771,6 +4847,7 @@ async function runAudioStage(input) {
4771
4847
  ffmpegProcessTimeout,
4772
4848
  audioGain,
4773
4849
  audios,
4850
+ masterFxChain,
4774
4851
  abortSignal,
4775
4852
  assertNotAborted,
4776
4853
  log
@@ -4790,7 +4867,7 @@ async function runAudioStage(input) {
4790
4867
  audioOutputPath,
4791
4868
  duration,
4792
4869
  abortSignal,
4793
- { ffmpegProcessTimeout, audioGain },
4870
+ { ffmpegProcessTimeout, audioGain, masterFxChain },
4794
4871
  compiledDir
4795
4872
  );
4796
4873
  } catch (err) {
@@ -7449,6 +7526,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
7449
7526
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
7450
7527
  audioGain: cfg.audioGain,
7451
7528
  audios: composition.audios,
7529
+ masterFxChain: composition.masterFxChain,
7452
7530
  abortSignal,
7453
7531
  assertNotAborted
7454
7532
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "0.1.0",
3
3
  "buildId": "dev",
4
- "sha256": "675e02ee21f31540e3a6abaec061f86cea92a0178ae67a390700acb4f801dbbc",
4
+ "sha256": "5e5acecab6120687d5061738fa55e78cbfc0fe48f7dcbc81eb494e36ba6bcc50",
5
5
  "artifacts": {
6
6
  "iife": "hyperframe.runtime.iife.js",
7
7
  "esm": "hyperframe.runtime.mjs"