@hyperframes/producer 0.8.193 → 0.8.194
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 +63 -14
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +69 -63
- package/dist/index.js +116 -24
- package/dist/public-server.js +116 -24
- package/dist/services/htmlCompiler.d.ts +2 -1
- package/dist/services/render/artifactTransaction.d.ts +3 -1
- package/dist/services/renderOrchestrator.d.ts +4 -2
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1564,8 +1564,11 @@ function assertDurationWithinTolerance(expectedSeconds, probedSeconds, tolerance
|
|
|
1564
1564
|
`Render artifact is truncated: expected ${expectedSeconds.toFixed(3)}s, probed ${probedSeconds.toFixed(3)}s (deficit ${deficit.toFixed(3)}s exceeds tolerance ${toleranceSeconds.toFixed(3)}s). Artifact: ${stagingPath}`
|
|
1565
1565
|
);
|
|
1566
1566
|
}
|
|
1567
|
+
function hasUsableFrameCount(frames) {
|
|
1568
|
+
return frames !== void 0 && Number.isFinite(frames) && frames > 0;
|
|
1569
|
+
}
|
|
1567
1570
|
function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPath, toleranceFrames = 1) {
|
|
1568
|
-
if (
|
|
1571
|
+
if (!hasUsableFrameCount(probedFrames)) {
|
|
1569
1572
|
return;
|
|
1570
1573
|
}
|
|
1571
1574
|
const shortfall = expectedFrames - probedFrames;
|
|
@@ -1585,9 +1588,10 @@ function assertSizeMatches(expected, probed, stagingPath) {
|
|
|
1585
1588
|
);
|
|
1586
1589
|
}
|
|
1587
1590
|
var ArtifactTransaction = class {
|
|
1588
|
-
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
|
|
1591
|
+
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe, logger = defaultLogger) {
|
|
1589
1592
|
this.kind = kind;
|
|
1590
1593
|
this.fileSystem = fileSystem;
|
|
1594
|
+
this.logger = logger;
|
|
1591
1595
|
this.destinationPath = resolve4(destinationPath);
|
|
1592
1596
|
this.transactionDirectory = createSiblingTransactionDirectory(this.destinationPath);
|
|
1593
1597
|
this.stagingPath = join5(this.transactionDirectory, basename3(this.destinationPath));
|
|
@@ -1670,6 +1674,19 @@ var ArtifactTransaction = class {
|
|
|
1670
1674
|
probePath
|
|
1671
1675
|
);
|
|
1672
1676
|
if (expected.expectedFrames !== void 0) {
|
|
1677
|
+
if (!hasUsableFrameCount(probed.frames)) {
|
|
1678
|
+
try {
|
|
1679
|
+
this.logger.debug(
|
|
1680
|
+
"[Render] Frame-count validation skipped: no usable frame count was reported.",
|
|
1681
|
+
{
|
|
1682
|
+
event: "artifact_frame_count_unavailable",
|
|
1683
|
+
artifact: this.destinationPath,
|
|
1684
|
+
expectedFrames: expected.expectedFrames
|
|
1685
|
+
}
|
|
1686
|
+
);
|
|
1687
|
+
} catch {
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1673
1690
|
assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, probePath);
|
|
1674
1691
|
}
|
|
1675
1692
|
assertSizeMatches(expected, probed, probePath);
|
|
@@ -5257,6 +5274,8 @@ import {
|
|
|
5257
5274
|
rewriteInlineStyleAssetUrls
|
|
5258
5275
|
} from "@hyperframes/core";
|
|
5259
5276
|
import { MAX_AUDIO_GAIN } from "@hyperframes/core/audio-gain";
|
|
5277
|
+
import { readPlaybackRate } from "@hyperframes/parsers/media-duration";
|
|
5278
|
+
import { resolveRateSpec } from "@hyperframes/core/speed-ramp";
|
|
5260
5279
|
import { gsapCdnDist } from "@hyperframes/core/gsap-cdn";
|
|
5261
5280
|
import {
|
|
5262
5281
|
assignBundledRuntimeCompositionIds,
|
|
@@ -7232,6 +7251,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7232
7251
|
const intrinsicDuration = isImage ? 0 : htmlEl.duration;
|
|
7233
7252
|
const playbackStartRaw = htmlEl.getAttribute("data-playback-start");
|
|
7234
7253
|
const mediaStartRaw = htmlEl.getAttribute("data-media-start");
|
|
7254
|
+
const playbackRateRaw = htmlEl.getAttribute("data-playback-rate");
|
|
7255
|
+
const automationRaw = htmlEl.getAttribute("data-automation");
|
|
7256
|
+
const defaultPlaybackRate = isImage ? 1 : htmlEl.defaultPlaybackRate;
|
|
7235
7257
|
const loop = htmlEl.hasAttribute("loop");
|
|
7236
7258
|
const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
|
|
7237
7259
|
const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
|
|
@@ -7247,6 +7269,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7247
7269
|
durationInferred,
|
|
7248
7270
|
playbackStartRaw,
|
|
7249
7271
|
mediaStartRaw,
|
|
7272
|
+
playbackRateRaw,
|
|
7273
|
+
automationRaw,
|
|
7274
|
+
defaultPlaybackRate,
|
|
7250
7275
|
loop,
|
|
7251
7276
|
hasAudio,
|
|
7252
7277
|
volume,
|
|
@@ -7256,7 +7281,17 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7256
7281
|
return results;
|
|
7257
7282
|
});
|
|
7258
7283
|
return elements.map(
|
|
7259
|
-
({
|
|
7284
|
+
({
|
|
7285
|
+
endRaw,
|
|
7286
|
+
durationRaw,
|
|
7287
|
+
intrinsicDuration,
|
|
7288
|
+
playbackStartRaw,
|
|
7289
|
+
mediaStartRaw,
|
|
7290
|
+
playbackRateRaw,
|
|
7291
|
+
automationRaw,
|
|
7292
|
+
defaultPlaybackRate,
|
|
7293
|
+
...element
|
|
7294
|
+
}) => ({
|
|
7260
7295
|
...element,
|
|
7261
7296
|
end: parseStrictFiniteTimingNumber(endRaw) ?? 0,
|
|
7262
7297
|
duration: element.durationInferred && Number.isFinite(intrinsicDuration) && intrinsicDuration > 0 ? intrinsicDuration : parseStrictFiniteTimingNumber(durationRaw) ?? 0,
|
|
@@ -7266,7 +7301,14 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7266
7301
|
if (name === "data-media-start") return mediaStartRaw;
|
|
7267
7302
|
return null;
|
|
7268
7303
|
}
|
|
7269
|
-
})
|
|
7304
|
+
}),
|
|
7305
|
+
playbackRate: resolveRateSpec(
|
|
7306
|
+
automationRaw,
|
|
7307
|
+
readPlaybackRate(
|
|
7308
|
+
(name) => name === "data-playback-rate" ? playbackRateRaw : null,
|
|
7309
|
+
defaultPlaybackRate
|
|
7310
|
+
)
|
|
7311
|
+
)
|
|
7270
7312
|
})
|
|
7271
7313
|
);
|
|
7272
7314
|
}
|
|
@@ -7686,9 +7728,18 @@ import {
|
|
|
7686
7728
|
getCompositionDuration,
|
|
7687
7729
|
initializeSession as initializeSession4,
|
|
7688
7730
|
isTransientBrowserError,
|
|
7689
|
-
probeBeginFrameLiveness
|
|
7731
|
+
probeBeginFrameLiveness,
|
|
7732
|
+
videoAudioId
|
|
7690
7733
|
} from "@hyperframes/engine";
|
|
7691
|
-
import {
|
|
7734
|
+
import {
|
|
7735
|
+
MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2,
|
|
7736
|
+
fpsToNumber as fpsToNumber7,
|
|
7737
|
+
normalizeRateSpec as normalizeRateSpec3,
|
|
7738
|
+
readMediaStart as readMediaStart2,
|
|
7739
|
+
shiftRateLane as shiftRateLane2,
|
|
7740
|
+
sourceTimeAt as sourceTimeAt3,
|
|
7741
|
+
timeAtSourceTime
|
|
7742
|
+
} from "@hyperframes/core";
|
|
7692
7743
|
import { extractMediaSrcMutations } from "@hyperframes/parsers";
|
|
7693
7744
|
|
|
7694
7745
|
// src/services/render/stages/probeFailures.ts
|
|
@@ -7747,16 +7798,28 @@ function authoredMediaStarts(html) {
|
|
|
7747
7798
|
}
|
|
7748
7799
|
return starts;
|
|
7749
7800
|
}
|
|
7750
|
-
function
|
|
7801
|
+
function isVideoAudio(audio, videoId) {
|
|
7802
|
+
return audio.type === "video" && audio.id === videoAudioId(videoId);
|
|
7803
|
+
}
|
|
7804
|
+
function adoptRuntimeMediaTiming(composition, el, authored) {
|
|
7751
7805
|
const shift = el.mediaStart - authored;
|
|
7752
|
-
if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
|
|
7753
7806
|
const entries = el.tagName === "video" ? [
|
|
7754
7807
|
...composition.videos.filter((video) => video.id === el.id),
|
|
7755
|
-
...composition.audios.filter(
|
|
7756
|
-
(audio) => audio.type === "video" && audio.id === `${el.id}-audio`
|
|
7757
|
-
)
|
|
7808
|
+
...composition.audios.filter((audio) => isVideoAudio(audio, el.id))
|
|
7758
7809
|
] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
|
|
7759
|
-
for (const entry of entries)
|
|
7810
|
+
for (const entry of entries) {
|
|
7811
|
+
if (typeof entry.playbackRate !== "object") {
|
|
7812
|
+
if (typeof el.playbackRate === "object") {
|
|
7813
|
+
const consumed = Math.max(0, entry.mediaStart - authored);
|
|
7814
|
+
const head = timeAtSourceTime(normalizeRateSpec3(entry.playbackRate), consumed);
|
|
7815
|
+
entry.mediaStart += sourceTimeAt3(el.playbackRate, head) - consumed;
|
|
7816
|
+
entry.playbackRate = shiftRateLane2(el.playbackRate, head);
|
|
7817
|
+
} else {
|
|
7818
|
+
entry.playbackRate = el.playbackRate;
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
if (el.mediaStart > 0 && Math.abs(shift) > BROWSER_MEDIA_EPSILON) entry.mediaStart += shift;
|
|
7822
|
+
}
|
|
7760
7823
|
}
|
|
7761
7824
|
function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
|
|
7762
7825
|
if (projectedEnd <= 0) return existingEnd;
|
|
@@ -8040,7 +8103,7 @@ async function runProbeStage(input) {
|
|
|
8040
8103
|
sourceChanged,
|
|
8041
8104
|
el.durationInferred
|
|
8042
8105
|
);
|
|
8043
|
-
|
|
8106
|
+
adoptRuntimeMediaTiming(
|
|
8044
8107
|
composition,
|
|
8045
8108
|
el,
|
|
8046
8109
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -8063,6 +8126,7 @@ async function runProbeStage(input) {
|
|
|
8063
8126
|
start: el.start,
|
|
8064
8127
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
8065
8128
|
mediaStart: el.mediaStart,
|
|
8129
|
+
playbackRate: el.playbackRate,
|
|
8066
8130
|
loop: el.loop,
|
|
8067
8131
|
hasAudio: el.hasAudio && !el.muted
|
|
8068
8132
|
});
|
|
@@ -8087,7 +8151,7 @@ async function runProbeStage(input) {
|
|
|
8087
8151
|
sourceChanged,
|
|
8088
8152
|
el.durationInferred
|
|
8089
8153
|
);
|
|
8090
|
-
|
|
8154
|
+
adoptRuntimeMediaTiming(
|
|
8091
8155
|
composition,
|
|
8092
8156
|
el,
|
|
8093
8157
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -8105,6 +8169,7 @@ async function runProbeStage(input) {
|
|
|
8105
8169
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
8106
8170
|
mediaStart: el.mediaStart,
|
|
8107
8171
|
loop: el.loop,
|
|
8172
|
+
playbackRate: el.playbackRate,
|
|
8108
8173
|
layer: 0,
|
|
8109
8174
|
volume: el.volume,
|
|
8110
8175
|
type: "audio"
|
|
@@ -8365,7 +8430,8 @@ import {
|
|
|
8365
8430
|
isHttpUrl as isHttpUrl2,
|
|
8366
8431
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
8367
8432
|
runVideoExtractionWithRetry,
|
|
8368
|
-
safeVideoExtractionSourceIdentity
|
|
8433
|
+
safeVideoExtractionSourceIdentity,
|
|
8434
|
+
videoAudioId as videoAudioId2
|
|
8369
8435
|
} from "@hyperframes/engine";
|
|
8370
8436
|
|
|
8371
8437
|
// src/services/render/extractionFailureMetadata.ts
|
|
@@ -8723,7 +8789,7 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
8723
8789
|
const video = composition.videos.find((v) => v.id === ext.videoId);
|
|
8724
8790
|
if (!video || !video.hasAudio || video.hidden || existingAudioSrcs.has(video.src)) continue;
|
|
8725
8791
|
composition.audios.push({
|
|
8726
|
-
id:
|
|
8792
|
+
id: videoAudioId2(video.id),
|
|
8727
8793
|
src: video.src,
|
|
8728
8794
|
start: video.start,
|
|
8729
8795
|
end: video.end,
|
|
@@ -11749,13 +11815,36 @@ function colorGradingValueHasLut(rawAttributeValue) {
|
|
|
11749
11815
|
return false;
|
|
11750
11816
|
}
|
|
11751
11817
|
}
|
|
11752
|
-
|
|
11818
|
+
var PROBE_READ_TIMEOUT_MS = 5e3;
|
|
11819
|
+
async function evaluateWithinBound(page, fn, what, log) {
|
|
11820
|
+
let timer;
|
|
11821
|
+
const timedOut = new Promise((_, reject) => {
|
|
11822
|
+
timer = setTimeout(
|
|
11823
|
+
() => reject(new Error(`no answer within ${PROBE_READ_TIMEOUT_MS} ms (page busy)`)),
|
|
11824
|
+
PROBE_READ_TIMEOUT_MS
|
|
11825
|
+
);
|
|
11826
|
+
});
|
|
11827
|
+
try {
|
|
11828
|
+
return await Promise.race([page.evaluate(fn), timedOut]);
|
|
11829
|
+
} catch (err) {
|
|
11830
|
+
log.warn(`[Render] live ${what} read skipped; using the static answer`, {
|
|
11831
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11832
|
+
});
|
|
11833
|
+
throw err;
|
|
11834
|
+
} finally {
|
|
11835
|
+
clearTimeout(timer);
|
|
11836
|
+
}
|
|
11837
|
+
}
|
|
11838
|
+
async function resolveCompositionElementCount(probeSession, html, log = defaultLogger) {
|
|
11753
11839
|
if (probeSession?.isInitialized) {
|
|
11754
11840
|
try {
|
|
11755
|
-
const liveCount = await
|
|
11841
|
+
const liveCount = await evaluateWithinBound(
|
|
11842
|
+
probeSession.page,
|
|
11756
11843
|
// Live HTMLCollection length — avoids materializing a static NodeList
|
|
11757
11844
|
// on the large-DOM comps this gate exists to catch (review nit).
|
|
11758
|
-
() => document.getElementsByTagName("*").length
|
|
11845
|
+
() => document.getElementsByTagName("*").length,
|
|
11846
|
+
"element count",
|
|
11847
|
+
log
|
|
11759
11848
|
);
|
|
11760
11849
|
if (typeof liveCount === "number" && Number.isFinite(liveCount)) {
|
|
11761
11850
|
return { count: liveCount, source: "live" };
|
|
@@ -11843,11 +11932,11 @@ function probeAdaptersUsed() {
|
|
|
11843
11932
|
}
|
|
11844
11933
|
return used;
|
|
11845
11934
|
}
|
|
11846
|
-
async function resolveAdaptersUsed(probeSession, html) {
|
|
11935
|
+
async function resolveAdaptersUsed(probeSession, html, log = defaultLogger) {
|
|
11847
11936
|
const staticAdapters = detectAdaptersStatic(html);
|
|
11848
11937
|
if (!probeSession?.isInitialized) return staticAdapters;
|
|
11849
11938
|
try {
|
|
11850
|
-
const live = await probeSession.page
|
|
11939
|
+
const live = await evaluateWithinBound(probeSession.page, probeAdaptersUsed, "adapters", log);
|
|
11851
11940
|
if (!Array.isArray(live)) return staticAdapters;
|
|
11852
11941
|
const detected = /* @__PURE__ */ new Set([...staticAdapters, ...live]);
|
|
11853
11942
|
return KNOWN_RUNTIME_ADAPTERS.filter((name) => detected.has(name));
|
|
@@ -12105,7 +12194,10 @@ async function executeRenderPipeline(input) {
|
|
|
12105
12194
|
);
|
|
12106
12195
|
const artifactTransaction = new ArtifactTransaction(
|
|
12107
12196
|
outputPath,
|
|
12108
|
-
isPngSequence || isHls ? "directory" : "file"
|
|
12197
|
+
isPngSequence || isHls ? "directory" : "file",
|
|
12198
|
+
void 0,
|
|
12199
|
+
void 0,
|
|
12200
|
+
log
|
|
12109
12201
|
);
|
|
12110
12202
|
const stagedOutputPath = artifactTransaction.stagingPath;
|
|
12111
12203
|
const needsAlpha = outputNeedsAlpha(outputFormat);
|
|
@@ -12604,8 +12696,8 @@ async function executeRenderPipeline(input) {
|
|
|
12604
12696
|
vfxHostCount,
|
|
12605
12697
|
vfxCapture,
|
|
12606
12698
|
vfxTypes
|
|
12607
|
-
} = await resolveCompositionElementCount(probeSession, compiled.html);
|
|
12608
|
-
const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html);
|
|
12699
|
+
} = await resolveCompositionElementCount(probeSession, compiled.html, log);
|
|
12700
|
+
const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html, log);
|
|
12609
12701
|
const deShortBandEnabled = deShortBandMaxElements > 0;
|
|
12610
12702
|
const deShortBandOpen = deShortBandEnabled && deShortBandMinFrames > 0 && compositionElementCountSource === "live" && compositionElementCount <= deShortBandMaxElements;
|
|
12611
12703
|
const deShortBandRoute = process.env.HF_DE_SHORT_BAND_ROUTE === "true";
|
package/dist/public-server.js
CHANGED
|
@@ -1583,8 +1583,11 @@ function assertDurationWithinTolerance(expectedSeconds, probedSeconds, tolerance
|
|
|
1583
1583
|
`Render artifact is truncated: expected ${expectedSeconds.toFixed(3)}s, probed ${probedSeconds.toFixed(3)}s (deficit ${deficit.toFixed(3)}s exceeds tolerance ${toleranceSeconds.toFixed(3)}s). Artifact: ${stagingPath}`
|
|
1584
1584
|
);
|
|
1585
1585
|
}
|
|
1586
|
+
function hasUsableFrameCount(frames) {
|
|
1587
|
+
return frames !== void 0 && Number.isFinite(frames) && frames > 0;
|
|
1588
|
+
}
|
|
1586
1589
|
function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPath, toleranceFrames = 1) {
|
|
1587
|
-
if (
|
|
1590
|
+
if (!hasUsableFrameCount(probedFrames)) {
|
|
1588
1591
|
return;
|
|
1589
1592
|
}
|
|
1590
1593
|
const shortfall = expectedFrames - probedFrames;
|
|
@@ -1604,9 +1607,10 @@ function assertSizeMatches(expected, probed, stagingPath) {
|
|
|
1604
1607
|
);
|
|
1605
1608
|
}
|
|
1606
1609
|
var ArtifactTransaction = class {
|
|
1607
|
-
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
|
|
1610
|
+
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe, logger = defaultLogger) {
|
|
1608
1611
|
this.kind = kind;
|
|
1609
1612
|
this.fileSystem = fileSystem;
|
|
1613
|
+
this.logger = logger;
|
|
1610
1614
|
this.destinationPath = resolve4(destinationPath);
|
|
1611
1615
|
this.transactionDirectory = createSiblingTransactionDirectory(this.destinationPath);
|
|
1612
1616
|
this.stagingPath = join5(this.transactionDirectory, basename3(this.destinationPath));
|
|
@@ -1689,6 +1693,19 @@ var ArtifactTransaction = class {
|
|
|
1689
1693
|
probePath
|
|
1690
1694
|
);
|
|
1691
1695
|
if (expected.expectedFrames !== void 0) {
|
|
1696
|
+
if (!hasUsableFrameCount(probed.frames)) {
|
|
1697
|
+
try {
|
|
1698
|
+
this.logger.debug(
|
|
1699
|
+
"[Render] Frame-count validation skipped: no usable frame count was reported.",
|
|
1700
|
+
{
|
|
1701
|
+
event: "artifact_frame_count_unavailable",
|
|
1702
|
+
artifact: this.destinationPath,
|
|
1703
|
+
expectedFrames: expected.expectedFrames
|
|
1704
|
+
}
|
|
1705
|
+
);
|
|
1706
|
+
} catch {
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1692
1709
|
assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, probePath);
|
|
1693
1710
|
}
|
|
1694
1711
|
assertSizeMatches(expected, probed, probePath);
|
|
@@ -5276,6 +5293,8 @@ import {
|
|
|
5276
5293
|
rewriteInlineStyleAssetUrls
|
|
5277
5294
|
} from "@hyperframes/core";
|
|
5278
5295
|
import { MAX_AUDIO_GAIN } from "@hyperframes/core/audio-gain";
|
|
5296
|
+
import { readPlaybackRate } from "@hyperframes/parsers/media-duration";
|
|
5297
|
+
import { resolveRateSpec } from "@hyperframes/core/speed-ramp";
|
|
5279
5298
|
import { gsapCdnDist } from "@hyperframes/core/gsap-cdn";
|
|
5280
5299
|
import {
|
|
5281
5300
|
assignBundledRuntimeCompositionIds,
|
|
@@ -7251,6 +7270,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7251
7270
|
const intrinsicDuration = isImage ? 0 : htmlEl.duration;
|
|
7252
7271
|
const playbackStartRaw = htmlEl.getAttribute("data-playback-start");
|
|
7253
7272
|
const mediaStartRaw = htmlEl.getAttribute("data-media-start");
|
|
7273
|
+
const playbackRateRaw = htmlEl.getAttribute("data-playback-rate");
|
|
7274
|
+
const automationRaw = htmlEl.getAttribute("data-automation");
|
|
7275
|
+
const defaultPlaybackRate = isImage ? 1 : htmlEl.defaultPlaybackRate;
|
|
7254
7276
|
const loop = htmlEl.hasAttribute("loop");
|
|
7255
7277
|
const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
|
|
7256
7278
|
const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
|
|
@@ -7266,6 +7288,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7266
7288
|
durationInferred,
|
|
7267
7289
|
playbackStartRaw,
|
|
7268
7290
|
mediaStartRaw,
|
|
7291
|
+
playbackRateRaw,
|
|
7292
|
+
automationRaw,
|
|
7293
|
+
defaultPlaybackRate,
|
|
7269
7294
|
loop,
|
|
7270
7295
|
hasAudio,
|
|
7271
7296
|
volume,
|
|
@@ -7275,7 +7300,17 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7275
7300
|
return results;
|
|
7276
7301
|
});
|
|
7277
7302
|
return elements.map(
|
|
7278
|
-
({
|
|
7303
|
+
({
|
|
7304
|
+
endRaw,
|
|
7305
|
+
durationRaw,
|
|
7306
|
+
intrinsicDuration,
|
|
7307
|
+
playbackStartRaw,
|
|
7308
|
+
mediaStartRaw,
|
|
7309
|
+
playbackRateRaw,
|
|
7310
|
+
automationRaw,
|
|
7311
|
+
defaultPlaybackRate,
|
|
7312
|
+
...element
|
|
7313
|
+
}) => ({
|
|
7279
7314
|
...element,
|
|
7280
7315
|
end: parseStrictFiniteTimingNumber(endRaw) ?? 0,
|
|
7281
7316
|
duration: element.durationInferred && Number.isFinite(intrinsicDuration) && intrinsicDuration > 0 ? intrinsicDuration : parseStrictFiniteTimingNumber(durationRaw) ?? 0,
|
|
@@ -7285,7 +7320,14 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7285
7320
|
if (name === "data-media-start") return mediaStartRaw;
|
|
7286
7321
|
return null;
|
|
7287
7322
|
}
|
|
7288
|
-
})
|
|
7323
|
+
}),
|
|
7324
|
+
playbackRate: resolveRateSpec(
|
|
7325
|
+
automationRaw,
|
|
7326
|
+
readPlaybackRate(
|
|
7327
|
+
(name) => name === "data-playback-rate" ? playbackRateRaw : null,
|
|
7328
|
+
defaultPlaybackRate
|
|
7329
|
+
)
|
|
7330
|
+
)
|
|
7289
7331
|
})
|
|
7290
7332
|
);
|
|
7291
7333
|
}
|
|
@@ -7705,9 +7747,18 @@ import {
|
|
|
7705
7747
|
getCompositionDuration,
|
|
7706
7748
|
initializeSession as initializeSession4,
|
|
7707
7749
|
isTransientBrowserError,
|
|
7708
|
-
probeBeginFrameLiveness
|
|
7750
|
+
probeBeginFrameLiveness,
|
|
7751
|
+
videoAudioId
|
|
7709
7752
|
} from "@hyperframes/engine";
|
|
7710
|
-
import {
|
|
7753
|
+
import {
|
|
7754
|
+
MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2,
|
|
7755
|
+
fpsToNumber as fpsToNumber7,
|
|
7756
|
+
normalizeRateSpec as normalizeRateSpec3,
|
|
7757
|
+
readMediaStart as readMediaStart2,
|
|
7758
|
+
shiftRateLane as shiftRateLane2,
|
|
7759
|
+
sourceTimeAt as sourceTimeAt3,
|
|
7760
|
+
timeAtSourceTime
|
|
7761
|
+
} from "@hyperframes/core";
|
|
7711
7762
|
import { extractMediaSrcMutations } from "@hyperframes/parsers";
|
|
7712
7763
|
|
|
7713
7764
|
// src/services/render/stages/probeFailures.ts
|
|
@@ -7766,16 +7817,28 @@ function authoredMediaStarts(html) {
|
|
|
7766
7817
|
}
|
|
7767
7818
|
return starts;
|
|
7768
7819
|
}
|
|
7769
|
-
function
|
|
7820
|
+
function isVideoAudio(audio, videoId) {
|
|
7821
|
+
return audio.type === "video" && audio.id === videoAudioId(videoId);
|
|
7822
|
+
}
|
|
7823
|
+
function adoptRuntimeMediaTiming(composition, el, authored) {
|
|
7770
7824
|
const shift = el.mediaStart - authored;
|
|
7771
|
-
if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
|
|
7772
7825
|
const entries = el.tagName === "video" ? [
|
|
7773
7826
|
...composition.videos.filter((video) => video.id === el.id),
|
|
7774
|
-
...composition.audios.filter(
|
|
7775
|
-
(audio) => audio.type === "video" && audio.id === `${el.id}-audio`
|
|
7776
|
-
)
|
|
7827
|
+
...composition.audios.filter((audio) => isVideoAudio(audio, el.id))
|
|
7777
7828
|
] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
|
|
7778
|
-
for (const entry of entries)
|
|
7829
|
+
for (const entry of entries) {
|
|
7830
|
+
if (typeof entry.playbackRate !== "object") {
|
|
7831
|
+
if (typeof el.playbackRate === "object") {
|
|
7832
|
+
const consumed = Math.max(0, entry.mediaStart - authored);
|
|
7833
|
+
const head = timeAtSourceTime(normalizeRateSpec3(entry.playbackRate), consumed);
|
|
7834
|
+
entry.mediaStart += sourceTimeAt3(el.playbackRate, head) - consumed;
|
|
7835
|
+
entry.playbackRate = shiftRateLane2(el.playbackRate, head);
|
|
7836
|
+
} else {
|
|
7837
|
+
entry.playbackRate = el.playbackRate;
|
|
7838
|
+
}
|
|
7839
|
+
}
|
|
7840
|
+
if (el.mediaStart > 0 && Math.abs(shift) > BROWSER_MEDIA_EPSILON) entry.mediaStart += shift;
|
|
7841
|
+
}
|
|
7779
7842
|
}
|
|
7780
7843
|
function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
|
|
7781
7844
|
if (projectedEnd <= 0) return existingEnd;
|
|
@@ -8059,7 +8122,7 @@ async function runProbeStage(input) {
|
|
|
8059
8122
|
sourceChanged,
|
|
8060
8123
|
el.durationInferred
|
|
8061
8124
|
);
|
|
8062
|
-
|
|
8125
|
+
adoptRuntimeMediaTiming(
|
|
8063
8126
|
composition,
|
|
8064
8127
|
el,
|
|
8065
8128
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -8082,6 +8145,7 @@ async function runProbeStage(input) {
|
|
|
8082
8145
|
start: el.start,
|
|
8083
8146
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
8084
8147
|
mediaStart: el.mediaStart,
|
|
8148
|
+
playbackRate: el.playbackRate,
|
|
8085
8149
|
loop: el.loop,
|
|
8086
8150
|
hasAudio: el.hasAudio && !el.muted
|
|
8087
8151
|
});
|
|
@@ -8106,7 +8170,7 @@ async function runProbeStage(input) {
|
|
|
8106
8170
|
sourceChanged,
|
|
8107
8171
|
el.durationInferred
|
|
8108
8172
|
);
|
|
8109
|
-
|
|
8173
|
+
adoptRuntimeMediaTiming(
|
|
8110
8174
|
composition,
|
|
8111
8175
|
el,
|
|
8112
8176
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -8124,6 +8188,7 @@ async function runProbeStage(input) {
|
|
|
8124
8188
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
8125
8189
|
mediaStart: el.mediaStart,
|
|
8126
8190
|
loop: el.loop,
|
|
8191
|
+
playbackRate: el.playbackRate,
|
|
8127
8192
|
layer: 0,
|
|
8128
8193
|
volume: el.volume,
|
|
8129
8194
|
type: "audio"
|
|
@@ -8353,7 +8418,8 @@ import {
|
|
|
8353
8418
|
isHttpUrl as isHttpUrl2,
|
|
8354
8419
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
8355
8420
|
runVideoExtractionWithRetry,
|
|
8356
|
-
safeVideoExtractionSourceIdentity
|
|
8421
|
+
safeVideoExtractionSourceIdentity,
|
|
8422
|
+
videoAudioId as videoAudioId2
|
|
8357
8423
|
} from "@hyperframes/engine";
|
|
8358
8424
|
|
|
8359
8425
|
// src/services/render/extractionFailureMetadata.ts
|
|
@@ -8706,7 +8772,7 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
8706
8772
|
const video = composition.videos.find((v) => v.id === ext.videoId);
|
|
8707
8773
|
if (!video || !video.hasAudio || video.hidden || existingAudioSrcs.has(video.src)) continue;
|
|
8708
8774
|
composition.audios.push({
|
|
8709
|
-
id:
|
|
8775
|
+
id: videoAudioId2(video.id),
|
|
8710
8776
|
src: video.src,
|
|
8711
8777
|
start: video.start,
|
|
8712
8778
|
end: video.end,
|
|
@@ -11732,13 +11798,36 @@ function colorGradingValueHasLut(rawAttributeValue) {
|
|
|
11732
11798
|
return false;
|
|
11733
11799
|
}
|
|
11734
11800
|
}
|
|
11735
|
-
|
|
11801
|
+
var PROBE_READ_TIMEOUT_MS = 5e3;
|
|
11802
|
+
async function evaluateWithinBound(page, fn, what, log) {
|
|
11803
|
+
let timer;
|
|
11804
|
+
const timedOut = new Promise((_, reject) => {
|
|
11805
|
+
timer = setTimeout(
|
|
11806
|
+
() => reject(new Error(`no answer within ${PROBE_READ_TIMEOUT_MS} ms (page busy)`)),
|
|
11807
|
+
PROBE_READ_TIMEOUT_MS
|
|
11808
|
+
);
|
|
11809
|
+
});
|
|
11810
|
+
try {
|
|
11811
|
+
return await Promise.race([page.evaluate(fn), timedOut]);
|
|
11812
|
+
} catch (err) {
|
|
11813
|
+
log.warn(`[Render] live ${what} read skipped; using the static answer`, {
|
|
11814
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11815
|
+
});
|
|
11816
|
+
throw err;
|
|
11817
|
+
} finally {
|
|
11818
|
+
clearTimeout(timer);
|
|
11819
|
+
}
|
|
11820
|
+
}
|
|
11821
|
+
async function resolveCompositionElementCount(probeSession, html, log = defaultLogger) {
|
|
11736
11822
|
if (probeSession?.isInitialized) {
|
|
11737
11823
|
try {
|
|
11738
|
-
const liveCount = await
|
|
11824
|
+
const liveCount = await evaluateWithinBound(
|
|
11825
|
+
probeSession.page,
|
|
11739
11826
|
// Live HTMLCollection length — avoids materializing a static NodeList
|
|
11740
11827
|
// on the large-DOM comps this gate exists to catch (review nit).
|
|
11741
|
-
() => document.getElementsByTagName("*").length
|
|
11828
|
+
() => document.getElementsByTagName("*").length,
|
|
11829
|
+
"element count",
|
|
11830
|
+
log
|
|
11742
11831
|
);
|
|
11743
11832
|
if (typeof liveCount === "number" && Number.isFinite(liveCount)) {
|
|
11744
11833
|
return { count: liveCount, source: "live" };
|
|
@@ -11826,11 +11915,11 @@ function probeAdaptersUsed() {
|
|
|
11826
11915
|
}
|
|
11827
11916
|
return used;
|
|
11828
11917
|
}
|
|
11829
|
-
async function resolveAdaptersUsed(probeSession, html) {
|
|
11918
|
+
async function resolveAdaptersUsed(probeSession, html, log = defaultLogger) {
|
|
11830
11919
|
const staticAdapters = detectAdaptersStatic(html);
|
|
11831
11920
|
if (!probeSession?.isInitialized) return staticAdapters;
|
|
11832
11921
|
try {
|
|
11833
|
-
const live = await probeSession.page
|
|
11922
|
+
const live = await evaluateWithinBound(probeSession.page, probeAdaptersUsed, "adapters", log);
|
|
11834
11923
|
if (!Array.isArray(live)) return staticAdapters;
|
|
11835
11924
|
const detected = /* @__PURE__ */ new Set([...staticAdapters, ...live]);
|
|
11836
11925
|
return KNOWN_RUNTIME_ADAPTERS.filter((name) => detected.has(name));
|
|
@@ -12088,7 +12177,10 @@ async function executeRenderPipeline(input) {
|
|
|
12088
12177
|
);
|
|
12089
12178
|
const artifactTransaction = new ArtifactTransaction(
|
|
12090
12179
|
outputPath,
|
|
12091
|
-
isPngSequence || isHls ? "directory" : "file"
|
|
12180
|
+
isPngSequence || isHls ? "directory" : "file",
|
|
12181
|
+
void 0,
|
|
12182
|
+
void 0,
|
|
12183
|
+
log
|
|
12092
12184
|
);
|
|
12093
12185
|
const stagedOutputPath = artifactTransaction.stagingPath;
|
|
12094
12186
|
const needsAlpha = outputNeedsAlpha(outputFormat);
|
|
@@ -12587,8 +12679,8 @@ async function executeRenderPipeline(input) {
|
|
|
12587
12679
|
vfxHostCount,
|
|
12588
12680
|
vfxCapture,
|
|
12589
12681
|
vfxTypes
|
|
12590
|
-
} = await resolveCompositionElementCount(probeSession, compiled.html);
|
|
12591
|
-
const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html);
|
|
12682
|
+
} = await resolveCompositionElementCount(probeSession, compiled.html, log);
|
|
12683
|
+
const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html, log);
|
|
12592
12684
|
const deShortBandEnabled = deShortBandMaxElements > 0;
|
|
12593
12685
|
const deShortBandOpen = deShortBandEnabled && deShortBandMinFrames > 0 && compositionElementCountSource === "live" && compositionElementCount <= deShortBandMaxElements;
|
|
12594
12686
|
const deShortBandRoute = process.env.HF_DE_SHORT_BAND_ROUTE === "true";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ResolvedDuration, type UnresolvedElement } from "@hyperframes/core";
|
|
1
|
+
import { type RateSpec, type ResolvedDuration, type UnresolvedElement } from "@hyperframes/core";
|
|
2
2
|
import { type VideoElement, type ImageElement, type AudioElement, type AudioVolumeKeyframe } from "@hyperframes/engine";
|
|
3
3
|
import type { Page } from "puppeteer-core";
|
|
4
4
|
import { type ProducerLogger } from "../logger.js";
|
|
@@ -216,6 +216,7 @@ export interface BrowserMediaElement {
|
|
|
216
216
|
/** True when compilation inferred duration from the fallback source. */
|
|
217
217
|
durationInferred: boolean;
|
|
218
218
|
mediaStart: number;
|
|
219
|
+
playbackRate: RateSpec;
|
|
219
220
|
loop: boolean;
|
|
220
221
|
hasAudio: boolean;
|
|
221
222
|
volume: number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ProducerLogger } from "../../logger.js";
|
|
1
2
|
import type { RenderOutputFormat } from "./renderFormat.js";
|
|
2
3
|
export type ArtifactKind = "file" | "directory";
|
|
3
4
|
export interface ArtifactTransactionFileSystem {
|
|
@@ -113,13 +114,14 @@ export declare function buildArtifactExpectation(input: {
|
|
|
113
114
|
export declare class ArtifactTransaction {
|
|
114
115
|
private readonly kind;
|
|
115
116
|
private readonly fileSystem;
|
|
117
|
+
private readonly logger;
|
|
116
118
|
readonly destinationPath: string;
|
|
117
119
|
readonly stagingPath: string;
|
|
118
120
|
private readonly transactionDirectory;
|
|
119
121
|
private readonly backupPath;
|
|
120
122
|
private state;
|
|
121
123
|
private readonly durationProbe;
|
|
122
|
-
constructor(destinationPath: string, kind: ArtifactKind, fileSystem?: ArtifactTransactionFileSystem, durationProbe?: ArtifactDurationProbe);
|
|
124
|
+
constructor(destinationPath: string, kind: ArtifactKind, fileSystem?: ArtifactTransactionFileSystem, durationProbe?: ArtifactDurationProbe, logger?: Pick<ProducerLogger, "debug">);
|
|
123
125
|
validate(expected?: ArtifactValidationExpectation): Promise<void>;
|
|
124
126
|
private assertReadableNonEmptyDirectory;
|
|
125
127
|
/**
|
|
@@ -706,6 +706,8 @@ export interface ElementTagScan {
|
|
|
706
706
|
* live count and uses this only when no such session exists.
|
|
707
707
|
*/
|
|
708
708
|
export declare function scanElementTags(html: string): ElementTagScan;
|
|
709
|
+
/** Longest a live probe-page read may hold the render; a busy page otherwise waits out the CDP timeout. */
|
|
710
|
+
export declare const PROBE_READ_TIMEOUT_MS = 5000;
|
|
709
711
|
/**
|
|
710
712
|
* Element count for the short-comp band's gate, WITH PROVENANCE.
|
|
711
713
|
*
|
|
@@ -728,7 +730,7 @@ export declare function scanElementTags(html: string): ElementTagScan;
|
|
|
728
730
|
* the fleet distribution recorded by the baseline release must be measured
|
|
729
731
|
* by the same resolver that later gates routing, or the baseline is invalid.
|
|
730
732
|
*/
|
|
731
|
-
export declare function resolveCompositionElementCount(probeSession: Pick<CaptureSession, "isInitialized" | "page"> | null, html: string): Promise<{
|
|
733
|
+
export declare function resolveCompositionElementCount(probeSession: Pick<CaptureSession, "isInitialized" | "page"> | null, html: string, log?: Pick<ProducerLogger, "warn">): Promise<{
|
|
732
734
|
count: number;
|
|
733
735
|
source: "live" | "static";
|
|
734
736
|
byTag?: Readonly<Record<string, number>>;
|
|
@@ -761,7 +763,7 @@ export declare function detectAdaptersStatic(html: string): RuntimeAdapterName[]
|
|
|
761
763
|
* Runtime adapters a composition exercises. Unlike `resolveCompositionElementCount`
|
|
762
764
|
* (one source of truth, for gating), this is observational: unions live + static every render.
|
|
763
765
|
*/
|
|
764
|
-
export declare function resolveAdaptersUsed(probeSession: Pick<CaptureSession, "isInitialized" | "page"> | null, html: string): Promise<readonly RuntimeAdapterName[]>;
|
|
766
|
+
export declare function resolveAdaptersUsed(probeSession: Pick<CaptureSession, "isInitialized" | "page"> | null, html: string, log?: Pick<ProducerLogger, "warn">): Promise<readonly RuntimeAdapterName[]>;
|
|
765
767
|
/**
|
|
766
768
|
* Max-merge init telemetry across per-worker capture perf summaries — the
|
|
767
769
|
* success-path channel for PARALLEL renders, whose worker console buffers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/producer",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.194",
|
|
4
4
|
"description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"postcss": "^8.4.0",
|
|
36
36
|
"puppeteer": "^25.2.1",
|
|
37
37
|
"puppeteer-core": "^25.2.1",
|
|
38
|
-
"@hyperframes/
|
|
39
|
-
"@hyperframes/
|
|
40
|
-
"@hyperframes/parsers": "0.8.
|
|
41
|
-
"@hyperframes/lint": "0.8.
|
|
42
|
-
"@hyperframes/studio-server": "0.8.
|
|
38
|
+
"@hyperframes/core": "0.8.194",
|
|
39
|
+
"@hyperframes/engine": "0.8.194",
|
|
40
|
+
"@hyperframes/parsers": "0.8.194",
|
|
41
|
+
"@hyperframes/lint": "0.8.194",
|
|
42
|
+
"@hyperframes/studio-server": "0.8.194"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^25.0.10",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"tsx": "^4.21.0",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^4.1.11",
|
|
51
|
-
"@hyperframes/aws-lambda": "0.8.
|
|
51
|
+
"@hyperframes/aws-lambda": "0.8.194"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=22"
|