@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.
- package/dist/distributed.js +119 -41
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -58
- package/dist/index.js +120 -41
- package/dist/public-server.js +119 -41
- package/dist/services/htmlCompiler.d.ts +2 -0
- package/dist/services/render/shared.d.ts +1 -0
- package/dist/services/render/stages/audioStage.d.ts +1 -0
- package/dist/services/renderMediaCollector.d.ts +5 -11
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -5220,9 +5220,18 @@ import {
|
|
|
5220
5220
|
|
|
5221
5221
|
// src/services/renderMediaCollector.ts
|
|
5222
5222
|
import { parseHTML as parseHTML2 } from "linkedom";
|
|
5223
|
-
import {
|
|
5223
|
+
import {
|
|
5224
|
+
MEDIA_RENDER_ID_ATTR,
|
|
5225
|
+
normalizeRateSpec as normalizeRateSpec2,
|
|
5226
|
+
resolveAuthoredTimingWindow,
|
|
5227
|
+
shiftRateLane,
|
|
5228
|
+
sourceTimeAt as sourceTimeAt2
|
|
5229
|
+
} from "@hyperframes/core";
|
|
5224
5230
|
import {
|
|
5225
5231
|
MEDIA_START_BASIS_ATTR,
|
|
5232
|
+
compositionOriginSeconds,
|
|
5233
|
+
cutToHostSlots,
|
|
5234
|
+
hostInPointSeconds,
|
|
5226
5235
|
readMediaStartBasis,
|
|
5227
5236
|
resolveAbsoluteMediaStartSeconds
|
|
5228
5237
|
} from "@hyperframes/core/media-timing";
|
|
@@ -5232,7 +5241,6 @@ import {
|
|
|
5232
5241
|
parseAudioElements,
|
|
5233
5242
|
resolveReferencedStart
|
|
5234
5243
|
} from "@hyperframes/engine";
|
|
5235
|
-
var COMPOSITION_HOST_ATTR = "data-composition-file";
|
|
5236
5244
|
function resolveHostEnd(host, hostStart) {
|
|
5237
5245
|
return resolveAuthoredTimingWindow({
|
|
5238
5246
|
start: hostStart,
|
|
@@ -5240,39 +5248,52 @@ function resolveHostEnd(host, hostStart) {
|
|
|
5240
5248
|
end: host.getAttribute("data-end")
|
|
5241
5249
|
})?.end ?? null;
|
|
5242
5250
|
}
|
|
5243
|
-
var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local" };
|
|
5244
|
-
|
|
5251
|
+
var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local", slots: [] };
|
|
5252
|
+
var COMPOSITION_HOST_SELECTOR = "[data-composition-id], [data-composition-src], [data-composition-file]";
|
|
5253
|
+
function enclosingHosts(element) {
|
|
5245
5254
|
const hosts = [];
|
|
5246
5255
|
for (let ancestor = element.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
|
5247
|
-
if (ancestor.
|
|
5256
|
+
if (ancestor.matches(COMPOSITION_HOST_SELECTOR)) {
|
|
5257
|
+
hosts.unshift(ancestor);
|
|
5258
|
+
}
|
|
5248
5259
|
}
|
|
5260
|
+
return hosts;
|
|
5261
|
+
}
|
|
5262
|
+
function resolveHostWindow(element, document2, startCache, visiting) {
|
|
5263
|
+
const hosts = enclosingHosts(element);
|
|
5249
5264
|
if (hosts.length === 0) return ROOT_WINDOW;
|
|
5250
5265
|
let offset = 0;
|
|
5251
5266
|
let limit = Infinity;
|
|
5252
|
-
|
|
5267
|
+
const slots = [];
|
|
5268
|
+
for (const [index, host] of hosts.entries()) {
|
|
5253
5269
|
const hostStart = resolveReferencedStart(document2, host, startCache, visiting);
|
|
5254
5270
|
const hostEnd = resolveHostEnd(host, hostStart);
|
|
5255
|
-
if (hostEnd != null
|
|
5256
|
-
|
|
5271
|
+
if (hostEnd != null && !isRootComposition(host, index)) {
|
|
5272
|
+
limit = Math.min(limit, offset + hostEnd);
|
|
5273
|
+
}
|
|
5274
|
+
if (hostInPointSeconds(host) > 0) {
|
|
5275
|
+
slots.push({ start: offset + hostStart, end: offset + (hostEnd ?? Infinity) });
|
|
5276
|
+
}
|
|
5277
|
+
offset += compositionOriginSeconds(hostStart, host);
|
|
5257
5278
|
}
|
|
5279
|
+
return { offset, limit, basis: mediaStartBasis(element), slots };
|
|
5280
|
+
}
|
|
5281
|
+
var isRootComposition = (host, depth) => depth === 0 && !host.matches("[data-composition-src], [data-composition-file]");
|
|
5282
|
+
function mediaStartBasis(element) {
|
|
5258
5283
|
const tag = element.tagName.toLowerCase();
|
|
5259
|
-
|
|
5260
|
-
return {
|
|
5261
|
-
offset,
|
|
5262
|
-
limit,
|
|
5263
|
-
basis
|
|
5264
|
-
};
|
|
5284
|
+
return tag === "video" || tag === "audio" ? readMediaStartBasis(element.getAttribute(MEDIA_START_BASIS_ATTR)) : "local";
|
|
5265
5285
|
}
|
|
5266
5286
|
function collectHostWindows(html) {
|
|
5267
5287
|
const { document: document2 } = parseHTML2(html);
|
|
5268
5288
|
const windows = /* @__PURE__ */ new Map();
|
|
5269
5289
|
const startCache = /* @__PURE__ */ new Map();
|
|
5270
5290
|
const visiting = /* @__PURE__ */ new Set();
|
|
5271
|
-
for (const element of document2.querySelectorAll(
|
|
5291
|
+
for (const element of document2.querySelectorAll("video, audio, img[src]")) {
|
|
5272
5292
|
const renderId = element.getAttribute(MEDIA_RENDER_ID_ATTR);
|
|
5273
|
-
|
|
5293
|
+
const id = renderId || element.getAttribute("id");
|
|
5294
|
+
if (!id || !renderId && windows.has(id)) continue;
|
|
5274
5295
|
windows.set(
|
|
5275
|
-
|
|
5296
|
+
id,
|
|
5276
5297
|
resolveHostWindow(element, document2, startCache, visiting)
|
|
5277
5298
|
);
|
|
5278
5299
|
}
|
|
@@ -5290,34 +5311,59 @@ function toAbsoluteWindow(start, end, window2) {
|
|
|
5290
5311
|
hostStart: window2.offset,
|
|
5291
5312
|
basis: window2.basis
|
|
5292
5313
|
});
|
|
5293
|
-
|
|
5314
|
+
const absolute = { start: absoluteStart, end: Math.min(absoluteEnd, window2.limit) };
|
|
5315
|
+
const played = cutToHostSlots(absolute, window2.slots);
|
|
5316
|
+
if (played.end === played.start && absolute.end > absolute.start) return null;
|
|
5317
|
+
return { start: absolute.start, end: played.end, played };
|
|
5318
|
+
}
|
|
5319
|
+
function startAtPlayed(clip, played) {
|
|
5320
|
+
const head = played.start - clip.start;
|
|
5321
|
+
if (head <= 0) return clip;
|
|
5322
|
+
if ("loop" in clip && clip.loop) return { ...clip, start: played.start };
|
|
5323
|
+
const rate = normalizeRateSpec2(clip.playbackRate);
|
|
5324
|
+
return {
|
|
5325
|
+
...clip,
|
|
5326
|
+
start: played.start,
|
|
5327
|
+
mediaStart: clip.mediaStart + sourceTimeAt2(rate, head),
|
|
5328
|
+
playbackRate: shiftRateLane(rate, head)
|
|
5329
|
+
};
|
|
5294
5330
|
}
|
|
5295
5331
|
function collectRenderMedia(html) {
|
|
5296
5332
|
const windows = collectHostWindows(html);
|
|
5297
5333
|
const windowFor = (id) => windows.get(id) ?? ROOT_WINDOW;
|
|
5334
|
+
const droppedMediaIds = /* @__PURE__ */ new Set();
|
|
5298
5335
|
const videos = [];
|
|
5299
5336
|
for (const video of parseVideoElements(html)) {
|
|
5300
5337
|
const absolute = toAbsoluteWindow(video.start, video.end, windowFor(video.id));
|
|
5301
|
-
if (absolute)
|
|
5338
|
+
if (!absolute) {
|
|
5339
|
+
droppedMediaIds.add(video.id);
|
|
5340
|
+
continue;
|
|
5341
|
+
}
|
|
5342
|
+
videos.push(
|
|
5343
|
+
startAtPlayed({ ...video, start: absolute.start, end: absolute.end }, absolute.played)
|
|
5344
|
+
);
|
|
5302
5345
|
}
|
|
5303
5346
|
const images = [];
|
|
5304
5347
|
for (const image of parseImageElements(html)) {
|
|
5305
5348
|
const absolute = toAbsoluteWindow(image.start, image.end, windowFor(image.id));
|
|
5306
|
-
if (absolute) images.push({ ...image,
|
|
5349
|
+
if (absolute) images.push({ ...image, start: absolute.start, end: absolute.end });
|
|
5350
|
+
else droppedMediaIds.add(image.id);
|
|
5307
5351
|
}
|
|
5308
5352
|
const audios = [];
|
|
5309
5353
|
for (const audio of parseAudioElements(html)) {
|
|
5310
5354
|
const elementId = audio.type === "video" ? audio.id.replace(/-audio$/, "") : audio.id;
|
|
5311
|
-
const
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
audios.push({
|
|
5315
|
-
...audio,
|
|
5316
|
-
start: absolute.start,
|
|
5317
|
-
end: Number.isFinite(absolute.end) ? absolute.end : 0
|
|
5318
|
-
});
|
|
5355
|
+
const placed = placeAudio(audio, windowFor(elementId));
|
|
5356
|
+
if (placed) audios.push(placed);
|
|
5357
|
+
else droppedMediaIds.add(elementId);
|
|
5319
5358
|
}
|
|
5320
|
-
return { videos, audios, images };
|
|
5359
|
+
return { videos, audios, images, droppedMediaIds };
|
|
5360
|
+
}
|
|
5361
|
+
function placeAudio(audio, window2) {
|
|
5362
|
+
const authoredEnd = audio.end > 0 ? audio.end : Infinity;
|
|
5363
|
+
const absolute = toAbsoluteWindow(audio.start, authoredEnd, window2);
|
|
5364
|
+
if (!absolute) return null;
|
|
5365
|
+
const end = Number.isFinite(absolute.end) ? absolute.end : 0;
|
|
5366
|
+
return startAtPlayed({ ...audio, start: absolute.start, end }, absolute.played);
|
|
5321
5367
|
}
|
|
5322
5368
|
|
|
5323
5369
|
// src/services/htmlCompiler.ts
|
|
@@ -5325,6 +5371,7 @@ import {
|
|
|
5325
5371
|
analyzeKeyframeIntervals,
|
|
5326
5372
|
assertMediaPayload,
|
|
5327
5373
|
NotMediaPayloadError as NotMediaPayloadError2,
|
|
5374
|
+
parseMasterFxChain,
|
|
5328
5375
|
probeMediaProfile as probeMediaProfile2
|
|
5329
5376
|
} from "@hyperframes/engine";
|
|
5330
5377
|
|
|
@@ -7024,7 +7071,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7024
7071
|
for (const [relPath, absPath] of preparedGifAssets) {
|
|
7025
7072
|
externalAssets.set(relPath, absPath);
|
|
7026
7073
|
}
|
|
7027
|
-
const { videos, audios, images } = collectRenderMedia(html);
|
|
7074
|
+
const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
|
|
7075
|
+
const masterFxChain = parseMasterFxChain(html);
|
|
7028
7076
|
for (const video of videos) {
|
|
7029
7077
|
if (isHttpUrl(video.src)) continue;
|
|
7030
7078
|
const videoPath = resolve6(projectDir, video.src);
|
|
@@ -7058,6 +7106,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7058
7106
|
videos,
|
|
7059
7107
|
audios,
|
|
7060
7108
|
images,
|
|
7109
|
+
droppedMediaIds,
|
|
7110
|
+
...masterFxChain ? { masterFxChain } : {},
|
|
7061
7111
|
unresolvedCompositions,
|
|
7062
7112
|
externalAssets,
|
|
7063
7113
|
width,
|
|
@@ -7490,6 +7540,7 @@ async function runCompileStage(input) {
|
|
|
7490
7540
|
videos: compiled.videos,
|
|
7491
7541
|
audios: compiled.audios,
|
|
7492
7542
|
images: compiled.images,
|
|
7543
|
+
masterFxChain: compiled.masterFxChain,
|
|
7493
7544
|
width: compiled.width,
|
|
7494
7545
|
height: compiled.height
|
|
7495
7546
|
};
|
|
@@ -7552,7 +7603,7 @@ import {
|
|
|
7552
7603
|
isTransientBrowserError,
|
|
7553
7604
|
probeBeginFrameLiveness
|
|
7554
7605
|
} from "@hyperframes/engine";
|
|
7555
|
-
import { fpsToNumber as fpsToNumber7 } from "@hyperframes/core";
|
|
7606
|
+
import { MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2, fpsToNumber as fpsToNumber7, readMediaStart as readMediaStart2 } from "@hyperframes/core";
|
|
7556
7607
|
import { extractMediaSrcMutations } from "@hyperframes/parsers";
|
|
7557
7608
|
|
|
7558
7609
|
// src/services/render/stages/probeFailures.ts
|
|
@@ -7599,6 +7650,26 @@ function hasVariableBoundMedia(html, variables) {
|
|
|
7599
7650
|
return Boolean(variableId && Object.hasOwn(variables, variableId));
|
|
7600
7651
|
});
|
|
7601
7652
|
}
|
|
7653
|
+
function authoredMediaStarts(html) {
|
|
7654
|
+
const { document: document2 } = parseHTML5(html);
|
|
7655
|
+
const starts = /* @__PURE__ */ new Map();
|
|
7656
|
+
for (const media of document2.querySelectorAll("video, audio")) {
|
|
7657
|
+
const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
|
|
7658
|
+
if (id && !starts.has(id)) starts.set(id, readMediaStart2(media));
|
|
7659
|
+
}
|
|
7660
|
+
return starts;
|
|
7661
|
+
}
|
|
7662
|
+
function adoptRuntimeMediaStart(composition, el, authored) {
|
|
7663
|
+
const shift = el.mediaStart - authored;
|
|
7664
|
+
if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
|
|
7665
|
+
const entries = el.tagName === "video" ? [
|
|
7666
|
+
...composition.videos.filter((video) => video.id === el.id),
|
|
7667
|
+
...composition.audios.filter(
|
|
7668
|
+
(audio) => audio.type === "video" && audio.id === `${el.id}-audio`
|
|
7669
|
+
)
|
|
7670
|
+
] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
|
|
7671
|
+
for (const entry of entries) entry.mediaStart += shift;
|
|
7672
|
+
}
|
|
7602
7673
|
function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
|
|
7603
7674
|
if (projectedEnd <= 0) return existingEnd;
|
|
7604
7675
|
if (sourceChanged && durationInferred) return projectedEnd;
|
|
@@ -7852,6 +7923,7 @@ async function runProbeStage(input) {
|
|
|
7852
7923
|
const existingVideoIds = new Set(composition.videos.map((v) => v.id));
|
|
7853
7924
|
const existingAudioIds = new Set(composition.audios.map((a) => a.id));
|
|
7854
7925
|
const existingImageIds = new Set(composition.images.map((image) => image.id));
|
|
7926
|
+
const authoredStarts = authoredMediaStarts(compiled.html);
|
|
7855
7927
|
pruneMutedBrowserMedia(composition, browserMedia, existingAudioIds);
|
|
7856
7928
|
for (const el of browserMedia) {
|
|
7857
7929
|
if (!el.src || el.src === "about:blank") continue;
|
|
@@ -7879,9 +7951,11 @@ async function runProbeStage(input) {
|
|
|
7879
7951
|
sourceChanged,
|
|
7880
7952
|
el.durationInferred
|
|
7881
7953
|
);
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7954
|
+
adoptRuntimeMediaStart(
|
|
7955
|
+
composition,
|
|
7956
|
+
el,
|
|
7957
|
+
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
7958
|
+
);
|
|
7885
7959
|
if (el.hasAudio && !el.muted && !existing.hasAudio) {
|
|
7886
7960
|
existing.hasAudio = true;
|
|
7887
7961
|
}
|
|
@@ -7889,7 +7963,7 @@ async function runProbeStage(input) {
|
|
|
7889
7963
|
existing.loop = true;
|
|
7890
7964
|
}
|
|
7891
7965
|
}
|
|
7892
|
-
} else {
|
|
7966
|
+
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
7893
7967
|
composition.videos.push({
|
|
7894
7968
|
id: el.id,
|
|
7895
7969
|
src,
|
|
@@ -7920,14 +7994,16 @@ async function runProbeStage(input) {
|
|
|
7920
7994
|
sourceChanged,
|
|
7921
7995
|
el.durationInferred
|
|
7922
7996
|
);
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7997
|
+
adoptRuntimeMediaStart(
|
|
7998
|
+
composition,
|
|
7999
|
+
el,
|
|
8000
|
+
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
8001
|
+
);
|
|
7926
8002
|
if (el.volume > 0 && Math.abs((existing.volume ?? 1) - el.volume) > BROWSER_MEDIA_EPSILON) {
|
|
7927
8003
|
existing.volume = el.volume;
|
|
7928
8004
|
}
|
|
7929
8005
|
}
|
|
7930
|
-
} else {
|
|
8006
|
+
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
7931
8007
|
composition.audios.push({
|
|
7932
8008
|
id: el.id,
|
|
7933
8009
|
src,
|
|
@@ -7955,7 +8031,7 @@ async function runProbeStage(input) {
|
|
|
7955
8031
|
existing.end = projectedEnd;
|
|
7956
8032
|
}
|
|
7957
8033
|
}
|
|
7958
|
-
} else {
|
|
8034
|
+
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
7959
8035
|
composition.images.push({
|
|
7960
8036
|
id: el.id,
|
|
7961
8037
|
src,
|
|
@@ -8574,6 +8650,7 @@ async function runAudioStage(input) {
|
|
|
8574
8650
|
ffmpegProcessTimeout,
|
|
8575
8651
|
audioGain,
|
|
8576
8652
|
audios,
|
|
8653
|
+
masterFxChain,
|
|
8577
8654
|
abortSignal,
|
|
8578
8655
|
assertNotAborted,
|
|
8579
8656
|
log
|
|
@@ -8593,7 +8670,7 @@ async function runAudioStage(input) {
|
|
|
8593
8670
|
audioOutputPath,
|
|
8594
8671
|
duration,
|
|
8595
8672
|
abortSignal,
|
|
8596
|
-
{ ffmpegProcessTimeout, audioGain },
|
|
8673
|
+
{ ffmpegProcessTimeout, audioGain, masterFxChain },
|
|
8597
8674
|
compiledDir
|
|
8598
8675
|
);
|
|
8599
8676
|
} catch (err) {
|
|
@@ -12062,6 +12139,7 @@ async function executeRenderPipeline(input) {
|
|
|
12062
12139
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
|
|
12063
12140
|
audioGain: cfg.audioGain,
|
|
12064
12141
|
audios: composition.audios,
|
|
12142
|
+
masterFxChain: composition.masterFxChain,
|
|
12065
12143
|
abortSignal: executionSignal,
|
|
12066
12144
|
assertNotAborted,
|
|
12067
12145
|
log
|
|
@@ -16012,6 +16090,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16012
16090
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
|
|
16013
16091
|
audioGain: cfg.audioGain,
|
|
16014
16092
|
audios: composition.audios,
|
|
16093
|
+
masterFxChain: composition.masterFxChain,
|
|
16015
16094
|
abortSignal,
|
|
16016
16095
|
assertNotAborted
|
|
16017
16096
|
});
|
package/dist/public-server.js
CHANGED
|
@@ -5239,9 +5239,18 @@ import {
|
|
|
5239
5239
|
|
|
5240
5240
|
// src/services/renderMediaCollector.ts
|
|
5241
5241
|
import { parseHTML as parseHTML2 } from "linkedom";
|
|
5242
|
-
import {
|
|
5242
|
+
import {
|
|
5243
|
+
MEDIA_RENDER_ID_ATTR,
|
|
5244
|
+
normalizeRateSpec as normalizeRateSpec2,
|
|
5245
|
+
resolveAuthoredTimingWindow,
|
|
5246
|
+
shiftRateLane,
|
|
5247
|
+
sourceTimeAt as sourceTimeAt2
|
|
5248
|
+
} from "@hyperframes/core";
|
|
5243
5249
|
import {
|
|
5244
5250
|
MEDIA_START_BASIS_ATTR,
|
|
5251
|
+
compositionOriginSeconds,
|
|
5252
|
+
cutToHostSlots,
|
|
5253
|
+
hostInPointSeconds,
|
|
5245
5254
|
readMediaStartBasis,
|
|
5246
5255
|
resolveAbsoluteMediaStartSeconds
|
|
5247
5256
|
} from "@hyperframes/core/media-timing";
|
|
@@ -5251,7 +5260,6 @@ import {
|
|
|
5251
5260
|
parseAudioElements,
|
|
5252
5261
|
resolveReferencedStart
|
|
5253
5262
|
} from "@hyperframes/engine";
|
|
5254
|
-
var COMPOSITION_HOST_ATTR = "data-composition-file";
|
|
5255
5263
|
function resolveHostEnd(host, hostStart) {
|
|
5256
5264
|
return resolveAuthoredTimingWindow({
|
|
5257
5265
|
start: hostStart,
|
|
@@ -5259,39 +5267,52 @@ function resolveHostEnd(host, hostStart) {
|
|
|
5259
5267
|
end: host.getAttribute("data-end")
|
|
5260
5268
|
})?.end ?? null;
|
|
5261
5269
|
}
|
|
5262
|
-
var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local" };
|
|
5263
|
-
|
|
5270
|
+
var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local", slots: [] };
|
|
5271
|
+
var COMPOSITION_HOST_SELECTOR = "[data-composition-id], [data-composition-src], [data-composition-file]";
|
|
5272
|
+
function enclosingHosts(element) {
|
|
5264
5273
|
const hosts = [];
|
|
5265
5274
|
for (let ancestor = element.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
|
5266
|
-
if (ancestor.
|
|
5275
|
+
if (ancestor.matches(COMPOSITION_HOST_SELECTOR)) {
|
|
5276
|
+
hosts.unshift(ancestor);
|
|
5277
|
+
}
|
|
5267
5278
|
}
|
|
5279
|
+
return hosts;
|
|
5280
|
+
}
|
|
5281
|
+
function resolveHostWindow(element, document2, startCache, visiting) {
|
|
5282
|
+
const hosts = enclosingHosts(element);
|
|
5268
5283
|
if (hosts.length === 0) return ROOT_WINDOW;
|
|
5269
5284
|
let offset = 0;
|
|
5270
5285
|
let limit = Infinity;
|
|
5271
|
-
|
|
5286
|
+
const slots = [];
|
|
5287
|
+
for (const [index, host] of hosts.entries()) {
|
|
5272
5288
|
const hostStart = resolveReferencedStart(document2, host, startCache, visiting);
|
|
5273
5289
|
const hostEnd = resolveHostEnd(host, hostStart);
|
|
5274
|
-
if (hostEnd != null
|
|
5275
|
-
|
|
5290
|
+
if (hostEnd != null && !isRootComposition(host, index)) {
|
|
5291
|
+
limit = Math.min(limit, offset + hostEnd);
|
|
5292
|
+
}
|
|
5293
|
+
if (hostInPointSeconds(host) > 0) {
|
|
5294
|
+
slots.push({ start: offset + hostStart, end: offset + (hostEnd ?? Infinity) });
|
|
5295
|
+
}
|
|
5296
|
+
offset += compositionOriginSeconds(hostStart, host);
|
|
5276
5297
|
}
|
|
5298
|
+
return { offset, limit, basis: mediaStartBasis(element), slots };
|
|
5299
|
+
}
|
|
5300
|
+
var isRootComposition = (host, depth) => depth === 0 && !host.matches("[data-composition-src], [data-composition-file]");
|
|
5301
|
+
function mediaStartBasis(element) {
|
|
5277
5302
|
const tag = element.tagName.toLowerCase();
|
|
5278
|
-
|
|
5279
|
-
return {
|
|
5280
|
-
offset,
|
|
5281
|
-
limit,
|
|
5282
|
-
basis
|
|
5283
|
-
};
|
|
5303
|
+
return tag === "video" || tag === "audio" ? readMediaStartBasis(element.getAttribute(MEDIA_START_BASIS_ATTR)) : "local";
|
|
5284
5304
|
}
|
|
5285
5305
|
function collectHostWindows(html) {
|
|
5286
5306
|
const { document: document2 } = parseHTML2(html);
|
|
5287
5307
|
const windows = /* @__PURE__ */ new Map();
|
|
5288
5308
|
const startCache = /* @__PURE__ */ new Map();
|
|
5289
5309
|
const visiting = /* @__PURE__ */ new Set();
|
|
5290
|
-
for (const element of document2.querySelectorAll(
|
|
5310
|
+
for (const element of document2.querySelectorAll("video, audio, img[src]")) {
|
|
5291
5311
|
const renderId = element.getAttribute(MEDIA_RENDER_ID_ATTR);
|
|
5292
|
-
|
|
5312
|
+
const id = renderId || element.getAttribute("id");
|
|
5313
|
+
if (!id || !renderId && windows.has(id)) continue;
|
|
5293
5314
|
windows.set(
|
|
5294
|
-
|
|
5315
|
+
id,
|
|
5295
5316
|
resolveHostWindow(element, document2, startCache, visiting)
|
|
5296
5317
|
);
|
|
5297
5318
|
}
|
|
@@ -5309,34 +5330,59 @@ function toAbsoluteWindow(start, end, window2) {
|
|
|
5309
5330
|
hostStart: window2.offset,
|
|
5310
5331
|
basis: window2.basis
|
|
5311
5332
|
});
|
|
5312
|
-
|
|
5333
|
+
const absolute = { start: absoluteStart, end: Math.min(absoluteEnd, window2.limit) };
|
|
5334
|
+
const played = cutToHostSlots(absolute, window2.slots);
|
|
5335
|
+
if (played.end === played.start && absolute.end > absolute.start) return null;
|
|
5336
|
+
return { start: absolute.start, end: played.end, played };
|
|
5337
|
+
}
|
|
5338
|
+
function startAtPlayed(clip, played) {
|
|
5339
|
+
const head = played.start - clip.start;
|
|
5340
|
+
if (head <= 0) return clip;
|
|
5341
|
+
if ("loop" in clip && clip.loop) return { ...clip, start: played.start };
|
|
5342
|
+
const rate = normalizeRateSpec2(clip.playbackRate);
|
|
5343
|
+
return {
|
|
5344
|
+
...clip,
|
|
5345
|
+
start: played.start,
|
|
5346
|
+
mediaStart: clip.mediaStart + sourceTimeAt2(rate, head),
|
|
5347
|
+
playbackRate: shiftRateLane(rate, head)
|
|
5348
|
+
};
|
|
5313
5349
|
}
|
|
5314
5350
|
function collectRenderMedia(html) {
|
|
5315
5351
|
const windows = collectHostWindows(html);
|
|
5316
5352
|
const windowFor = (id) => windows.get(id) ?? ROOT_WINDOW;
|
|
5353
|
+
const droppedMediaIds = /* @__PURE__ */ new Set();
|
|
5317
5354
|
const videos = [];
|
|
5318
5355
|
for (const video of parseVideoElements(html)) {
|
|
5319
5356
|
const absolute = toAbsoluteWindow(video.start, video.end, windowFor(video.id));
|
|
5320
|
-
if (absolute)
|
|
5357
|
+
if (!absolute) {
|
|
5358
|
+
droppedMediaIds.add(video.id);
|
|
5359
|
+
continue;
|
|
5360
|
+
}
|
|
5361
|
+
videos.push(
|
|
5362
|
+
startAtPlayed({ ...video, start: absolute.start, end: absolute.end }, absolute.played)
|
|
5363
|
+
);
|
|
5321
5364
|
}
|
|
5322
5365
|
const images = [];
|
|
5323
5366
|
for (const image of parseImageElements(html)) {
|
|
5324
5367
|
const absolute = toAbsoluteWindow(image.start, image.end, windowFor(image.id));
|
|
5325
|
-
if (absolute) images.push({ ...image,
|
|
5368
|
+
if (absolute) images.push({ ...image, start: absolute.start, end: absolute.end });
|
|
5369
|
+
else droppedMediaIds.add(image.id);
|
|
5326
5370
|
}
|
|
5327
5371
|
const audios = [];
|
|
5328
5372
|
for (const audio of parseAudioElements(html)) {
|
|
5329
5373
|
const elementId = audio.type === "video" ? audio.id.replace(/-audio$/, "") : audio.id;
|
|
5330
|
-
const
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
audios.push({
|
|
5334
|
-
...audio,
|
|
5335
|
-
start: absolute.start,
|
|
5336
|
-
end: Number.isFinite(absolute.end) ? absolute.end : 0
|
|
5337
|
-
});
|
|
5374
|
+
const placed = placeAudio(audio, windowFor(elementId));
|
|
5375
|
+
if (placed) audios.push(placed);
|
|
5376
|
+
else droppedMediaIds.add(elementId);
|
|
5338
5377
|
}
|
|
5339
|
-
return { videos, audios, images };
|
|
5378
|
+
return { videos, audios, images, droppedMediaIds };
|
|
5379
|
+
}
|
|
5380
|
+
function placeAudio(audio, window2) {
|
|
5381
|
+
const authoredEnd = audio.end > 0 ? audio.end : Infinity;
|
|
5382
|
+
const absolute = toAbsoluteWindow(audio.start, authoredEnd, window2);
|
|
5383
|
+
if (!absolute) return null;
|
|
5384
|
+
const end = Number.isFinite(absolute.end) ? absolute.end : 0;
|
|
5385
|
+
return startAtPlayed({ ...audio, start: absolute.start, end }, absolute.played);
|
|
5340
5386
|
}
|
|
5341
5387
|
|
|
5342
5388
|
// src/services/htmlCompiler.ts
|
|
@@ -5344,6 +5390,7 @@ import {
|
|
|
5344
5390
|
analyzeKeyframeIntervals,
|
|
5345
5391
|
assertMediaPayload,
|
|
5346
5392
|
NotMediaPayloadError as NotMediaPayloadError2,
|
|
5393
|
+
parseMasterFxChain,
|
|
5347
5394
|
probeMediaProfile as probeMediaProfile2
|
|
5348
5395
|
} from "@hyperframes/engine";
|
|
5349
5396
|
|
|
@@ -7043,7 +7090,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7043
7090
|
for (const [relPath, absPath] of preparedGifAssets) {
|
|
7044
7091
|
externalAssets.set(relPath, absPath);
|
|
7045
7092
|
}
|
|
7046
|
-
const { videos, audios, images } = collectRenderMedia(html);
|
|
7093
|
+
const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
|
|
7094
|
+
const masterFxChain = parseMasterFxChain(html);
|
|
7047
7095
|
for (const video of videos) {
|
|
7048
7096
|
if (isHttpUrl(video.src)) continue;
|
|
7049
7097
|
const videoPath = resolve6(projectDir, video.src);
|
|
@@ -7077,6 +7125,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7077
7125
|
videos,
|
|
7078
7126
|
audios,
|
|
7079
7127
|
images,
|
|
7128
|
+
droppedMediaIds,
|
|
7129
|
+
...masterFxChain ? { masterFxChain } : {},
|
|
7080
7130
|
unresolvedCompositions,
|
|
7081
7131
|
externalAssets,
|
|
7082
7132
|
width,
|
|
@@ -7509,6 +7559,7 @@ async function runCompileStage(input) {
|
|
|
7509
7559
|
videos: compiled.videos,
|
|
7510
7560
|
audios: compiled.audios,
|
|
7511
7561
|
images: compiled.images,
|
|
7562
|
+
masterFxChain: compiled.masterFxChain,
|
|
7512
7563
|
width: compiled.width,
|
|
7513
7564
|
height: compiled.height
|
|
7514
7565
|
};
|
|
@@ -7571,7 +7622,7 @@ import {
|
|
|
7571
7622
|
isTransientBrowserError,
|
|
7572
7623
|
probeBeginFrameLiveness
|
|
7573
7624
|
} from "@hyperframes/engine";
|
|
7574
|
-
import { fpsToNumber as fpsToNumber7 } from "@hyperframes/core";
|
|
7625
|
+
import { MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2, fpsToNumber as fpsToNumber7, readMediaStart as readMediaStart2 } from "@hyperframes/core";
|
|
7575
7626
|
import { extractMediaSrcMutations } from "@hyperframes/parsers";
|
|
7576
7627
|
|
|
7577
7628
|
// src/services/render/stages/probeFailures.ts
|
|
@@ -7618,6 +7669,26 @@ function hasVariableBoundMedia(html, variables) {
|
|
|
7618
7669
|
return Boolean(variableId && Object.hasOwn(variables, variableId));
|
|
7619
7670
|
});
|
|
7620
7671
|
}
|
|
7672
|
+
function authoredMediaStarts(html) {
|
|
7673
|
+
const { document: document2 } = parseHTML5(html);
|
|
7674
|
+
const starts = /* @__PURE__ */ new Map();
|
|
7675
|
+
for (const media of document2.querySelectorAll("video, audio")) {
|
|
7676
|
+
const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
|
|
7677
|
+
if (id && !starts.has(id)) starts.set(id, readMediaStart2(media));
|
|
7678
|
+
}
|
|
7679
|
+
return starts;
|
|
7680
|
+
}
|
|
7681
|
+
function adoptRuntimeMediaStart(composition, el, authored) {
|
|
7682
|
+
const shift = el.mediaStart - authored;
|
|
7683
|
+
if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
|
|
7684
|
+
const entries = el.tagName === "video" ? [
|
|
7685
|
+
...composition.videos.filter((video) => video.id === el.id),
|
|
7686
|
+
...composition.audios.filter(
|
|
7687
|
+
(audio) => audio.type === "video" && audio.id === `${el.id}-audio`
|
|
7688
|
+
)
|
|
7689
|
+
] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
|
|
7690
|
+
for (const entry of entries) entry.mediaStart += shift;
|
|
7691
|
+
}
|
|
7621
7692
|
function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
|
|
7622
7693
|
if (projectedEnd <= 0) return existingEnd;
|
|
7623
7694
|
if (sourceChanged && durationInferred) return projectedEnd;
|
|
@@ -7871,6 +7942,7 @@ async function runProbeStage(input) {
|
|
|
7871
7942
|
const existingVideoIds = new Set(composition.videos.map((v) => v.id));
|
|
7872
7943
|
const existingAudioIds = new Set(composition.audios.map((a) => a.id));
|
|
7873
7944
|
const existingImageIds = new Set(composition.images.map((image) => image.id));
|
|
7945
|
+
const authoredStarts = authoredMediaStarts(compiled.html);
|
|
7874
7946
|
pruneMutedBrowserMedia(composition, browserMedia, existingAudioIds);
|
|
7875
7947
|
for (const el of browserMedia) {
|
|
7876
7948
|
if (!el.src || el.src === "about:blank") continue;
|
|
@@ -7898,9 +7970,11 @@ async function runProbeStage(input) {
|
|
|
7898
7970
|
sourceChanged,
|
|
7899
7971
|
el.durationInferred
|
|
7900
7972
|
);
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7973
|
+
adoptRuntimeMediaStart(
|
|
7974
|
+
composition,
|
|
7975
|
+
el,
|
|
7976
|
+
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
7977
|
+
);
|
|
7904
7978
|
if (el.hasAudio && !el.muted && !existing.hasAudio) {
|
|
7905
7979
|
existing.hasAudio = true;
|
|
7906
7980
|
}
|
|
@@ -7908,7 +7982,7 @@ async function runProbeStage(input) {
|
|
|
7908
7982
|
existing.loop = true;
|
|
7909
7983
|
}
|
|
7910
7984
|
}
|
|
7911
|
-
} else {
|
|
7985
|
+
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
7912
7986
|
composition.videos.push({
|
|
7913
7987
|
id: el.id,
|
|
7914
7988
|
src,
|
|
@@ -7939,14 +8013,16 @@ async function runProbeStage(input) {
|
|
|
7939
8013
|
sourceChanged,
|
|
7940
8014
|
el.durationInferred
|
|
7941
8015
|
);
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
8016
|
+
adoptRuntimeMediaStart(
|
|
8017
|
+
composition,
|
|
8018
|
+
el,
|
|
8019
|
+
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
8020
|
+
);
|
|
7945
8021
|
if (el.volume > 0 && Math.abs((existing.volume ?? 1) - el.volume) > BROWSER_MEDIA_EPSILON) {
|
|
7946
8022
|
existing.volume = el.volume;
|
|
7947
8023
|
}
|
|
7948
8024
|
}
|
|
7949
|
-
} else {
|
|
8025
|
+
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
7950
8026
|
composition.audios.push({
|
|
7951
8027
|
id: el.id,
|
|
7952
8028
|
src,
|
|
@@ -7974,7 +8050,7 @@ async function runProbeStage(input) {
|
|
|
7974
8050
|
existing.end = projectedEnd;
|
|
7975
8051
|
}
|
|
7976
8052
|
}
|
|
7977
|
-
} else {
|
|
8053
|
+
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
7978
8054
|
composition.images.push({
|
|
7979
8055
|
id: el.id,
|
|
7980
8056
|
src,
|
|
@@ -8557,6 +8633,7 @@ async function runAudioStage(input) {
|
|
|
8557
8633
|
ffmpegProcessTimeout,
|
|
8558
8634
|
audioGain,
|
|
8559
8635
|
audios,
|
|
8636
|
+
masterFxChain,
|
|
8560
8637
|
abortSignal,
|
|
8561
8638
|
assertNotAborted,
|
|
8562
8639
|
log
|
|
@@ -8576,7 +8653,7 @@ async function runAudioStage(input) {
|
|
|
8576
8653
|
audioOutputPath,
|
|
8577
8654
|
duration,
|
|
8578
8655
|
abortSignal,
|
|
8579
|
-
{ ffmpegProcessTimeout, audioGain },
|
|
8656
|
+
{ ffmpegProcessTimeout, audioGain, masterFxChain },
|
|
8580
8657
|
compiledDir
|
|
8581
8658
|
);
|
|
8582
8659
|
} catch (err) {
|
|
@@ -12045,6 +12122,7 @@ async function executeRenderPipeline(input) {
|
|
|
12045
12122
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
|
|
12046
12123
|
audioGain: cfg.audioGain,
|
|
12047
12124
|
audios: composition.audios,
|
|
12125
|
+
masterFxChain: composition.masterFxChain,
|
|
12048
12126
|
abortSignal: executionSignal,
|
|
12049
12127
|
assertNotAborted,
|
|
12050
12128
|
log
|
|
@@ -8,6 +8,8 @@ export interface CompiledComposition {
|
|
|
8
8
|
videos: VideoElement[];
|
|
9
9
|
audios: AudioElement[];
|
|
10
10
|
images: ImageElement[];
|
|
11
|
+
droppedMediaIds: Set<string>;
|
|
12
|
+
masterFxChain?: string;
|
|
11
13
|
unresolvedCompositions: UnresolvedElement[];
|
|
12
14
|
/** Assets that resolve outside projectDir. Keys are the path used in HTML, values are absolute filesystem paths. */
|
|
13
15
|
externalAssets: Map<string, string>;
|
|
@@ -32,6 +32,7 @@ export interface AudioStageInput {
|
|
|
32
32
|
audioGain: number;
|
|
33
33
|
/** Read-only view of `composition.audios`. */
|
|
34
34
|
audios: CompositionMetadata["audios"];
|
|
35
|
+
masterFxChain?: string;
|
|
35
36
|
abortSignal: AbortSignal | undefined;
|
|
36
37
|
assertNotAborted: () => void;
|
|
37
38
|
/** Where a per-track failure's detail goes. Optional so tests need not pass one. */
|