@hyperframes/producer 0.8.149 → 0.8.151
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 +128 -44
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -58
- package/dist/index.js +134 -47
- package/dist/public-server.js +132 -46
- package/dist/services/htmlCompiler.d.ts +2 -0
- package/dist/services/render/captureImageFormat.d.ts +6 -1
- package/dist/services/render/segmentManifest.d.ts +1 -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/public-server.js
CHANGED
|
@@ -4323,7 +4323,8 @@ function computeSegmentPlanHash(input) {
|
|
|
4323
4323
|
input.imageFormat,
|
|
4324
4324
|
input.useGpu ? "gpu" : "cpu",
|
|
4325
4325
|
`${input.outputWidth}x${input.outputHeight}`,
|
|
4326
|
-
input.motionBlur
|
|
4326
|
+
input.motionBlur,
|
|
4327
|
+
input.captureQuality === void 0 ? "" : String(input.captureQuality)
|
|
4327
4328
|
].join(" ");
|
|
4328
4329
|
return createHash2("sha256").update(ordered).digest("hex").slice(0, 16);
|
|
4329
4330
|
}
|
|
@@ -5239,9 +5240,18 @@ import {
|
|
|
5239
5240
|
|
|
5240
5241
|
// src/services/renderMediaCollector.ts
|
|
5241
5242
|
import { parseHTML as parseHTML2 } from "linkedom";
|
|
5242
|
-
import {
|
|
5243
|
+
import {
|
|
5244
|
+
MEDIA_RENDER_ID_ATTR,
|
|
5245
|
+
normalizeRateSpec as normalizeRateSpec2,
|
|
5246
|
+
resolveAuthoredTimingWindow,
|
|
5247
|
+
shiftRateLane,
|
|
5248
|
+
sourceTimeAt as sourceTimeAt2
|
|
5249
|
+
} from "@hyperframes/core";
|
|
5243
5250
|
import {
|
|
5244
5251
|
MEDIA_START_BASIS_ATTR,
|
|
5252
|
+
compositionOriginSeconds,
|
|
5253
|
+
cutToHostSlots,
|
|
5254
|
+
hostInPointSeconds,
|
|
5245
5255
|
readMediaStartBasis,
|
|
5246
5256
|
resolveAbsoluteMediaStartSeconds
|
|
5247
5257
|
} from "@hyperframes/core/media-timing";
|
|
@@ -5251,7 +5261,6 @@ import {
|
|
|
5251
5261
|
parseAudioElements,
|
|
5252
5262
|
resolveReferencedStart
|
|
5253
5263
|
} from "@hyperframes/engine";
|
|
5254
|
-
var COMPOSITION_HOST_ATTR = "data-composition-file";
|
|
5255
5264
|
function resolveHostEnd(host, hostStart) {
|
|
5256
5265
|
return resolveAuthoredTimingWindow({
|
|
5257
5266
|
start: hostStart,
|
|
@@ -5259,39 +5268,52 @@ function resolveHostEnd(host, hostStart) {
|
|
|
5259
5268
|
end: host.getAttribute("data-end")
|
|
5260
5269
|
})?.end ?? null;
|
|
5261
5270
|
}
|
|
5262
|
-
var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local" };
|
|
5263
|
-
|
|
5271
|
+
var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local", slots: [] };
|
|
5272
|
+
var COMPOSITION_HOST_SELECTOR = "[data-composition-id], [data-composition-src], [data-composition-file]";
|
|
5273
|
+
function enclosingHosts(element) {
|
|
5264
5274
|
const hosts = [];
|
|
5265
5275
|
for (let ancestor = element.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
|
5266
|
-
if (ancestor.
|
|
5276
|
+
if (ancestor.matches(COMPOSITION_HOST_SELECTOR)) {
|
|
5277
|
+
hosts.unshift(ancestor);
|
|
5278
|
+
}
|
|
5267
5279
|
}
|
|
5280
|
+
return hosts;
|
|
5281
|
+
}
|
|
5282
|
+
function resolveHostWindow(element, document2, startCache, visiting) {
|
|
5283
|
+
const hosts = enclosingHosts(element);
|
|
5268
5284
|
if (hosts.length === 0) return ROOT_WINDOW;
|
|
5269
5285
|
let offset = 0;
|
|
5270
5286
|
let limit = Infinity;
|
|
5271
|
-
|
|
5287
|
+
const slots = [];
|
|
5288
|
+
for (const [index, host] of hosts.entries()) {
|
|
5272
5289
|
const hostStart = resolveReferencedStart(document2, host, startCache, visiting);
|
|
5273
5290
|
const hostEnd = resolveHostEnd(host, hostStart);
|
|
5274
|
-
if (hostEnd != null
|
|
5275
|
-
|
|
5291
|
+
if (hostEnd != null && !isRootComposition(host, index)) {
|
|
5292
|
+
limit = Math.min(limit, offset + hostEnd);
|
|
5293
|
+
}
|
|
5294
|
+
if (hostInPointSeconds(host) > 0) {
|
|
5295
|
+
slots.push({ start: offset + hostStart, end: offset + (hostEnd ?? Infinity) });
|
|
5296
|
+
}
|
|
5297
|
+
offset += compositionOriginSeconds(hostStart, host);
|
|
5276
5298
|
}
|
|
5299
|
+
return { offset, limit, basis: mediaStartBasis(element), slots };
|
|
5300
|
+
}
|
|
5301
|
+
var isRootComposition = (host, depth) => depth === 0 && !host.matches("[data-composition-src], [data-composition-file]");
|
|
5302
|
+
function mediaStartBasis(element) {
|
|
5277
5303
|
const tag = element.tagName.toLowerCase();
|
|
5278
|
-
|
|
5279
|
-
return {
|
|
5280
|
-
offset,
|
|
5281
|
-
limit,
|
|
5282
|
-
basis
|
|
5283
|
-
};
|
|
5304
|
+
return tag === "video" || tag === "audio" ? readMediaStartBasis(element.getAttribute(MEDIA_START_BASIS_ATTR)) : "local";
|
|
5284
5305
|
}
|
|
5285
5306
|
function collectHostWindows(html) {
|
|
5286
5307
|
const { document: document2 } = parseHTML2(html);
|
|
5287
5308
|
const windows = /* @__PURE__ */ new Map();
|
|
5288
5309
|
const startCache = /* @__PURE__ */ new Map();
|
|
5289
5310
|
const visiting = /* @__PURE__ */ new Set();
|
|
5290
|
-
for (const element of document2.querySelectorAll(
|
|
5311
|
+
for (const element of document2.querySelectorAll("video, audio, img[src]")) {
|
|
5291
5312
|
const renderId = element.getAttribute(MEDIA_RENDER_ID_ATTR);
|
|
5292
|
-
|
|
5313
|
+
const id = renderId || element.getAttribute("id");
|
|
5314
|
+
if (!id || !renderId && windows.has(id)) continue;
|
|
5293
5315
|
windows.set(
|
|
5294
|
-
|
|
5316
|
+
id,
|
|
5295
5317
|
resolveHostWindow(element, document2, startCache, visiting)
|
|
5296
5318
|
);
|
|
5297
5319
|
}
|
|
@@ -5309,34 +5331,59 @@ function toAbsoluteWindow(start, end, window2) {
|
|
|
5309
5331
|
hostStart: window2.offset,
|
|
5310
5332
|
basis: window2.basis
|
|
5311
5333
|
});
|
|
5312
|
-
|
|
5334
|
+
const absolute = { start: absoluteStart, end: Math.min(absoluteEnd, window2.limit) };
|
|
5335
|
+
const played = cutToHostSlots(absolute, window2.slots);
|
|
5336
|
+
if (played.end === played.start && absolute.end > absolute.start) return null;
|
|
5337
|
+
return { start: absolute.start, end: played.end, played };
|
|
5338
|
+
}
|
|
5339
|
+
function startAtPlayed(clip, played) {
|
|
5340
|
+
const head = played.start - clip.start;
|
|
5341
|
+
if (head <= 0) return clip;
|
|
5342
|
+
if ("loop" in clip && clip.loop) return { ...clip, start: played.start };
|
|
5343
|
+
const rate = normalizeRateSpec2(clip.playbackRate);
|
|
5344
|
+
return {
|
|
5345
|
+
...clip,
|
|
5346
|
+
start: played.start,
|
|
5347
|
+
mediaStart: clip.mediaStart + sourceTimeAt2(rate, head),
|
|
5348
|
+
playbackRate: shiftRateLane(rate, head)
|
|
5349
|
+
};
|
|
5313
5350
|
}
|
|
5314
5351
|
function collectRenderMedia(html) {
|
|
5315
5352
|
const windows = collectHostWindows(html);
|
|
5316
5353
|
const windowFor = (id) => windows.get(id) ?? ROOT_WINDOW;
|
|
5354
|
+
const droppedMediaIds = /* @__PURE__ */ new Set();
|
|
5317
5355
|
const videos = [];
|
|
5318
5356
|
for (const video of parseVideoElements(html)) {
|
|
5319
5357
|
const absolute = toAbsoluteWindow(video.start, video.end, windowFor(video.id));
|
|
5320
|
-
if (absolute)
|
|
5358
|
+
if (!absolute) {
|
|
5359
|
+
droppedMediaIds.add(video.id);
|
|
5360
|
+
continue;
|
|
5361
|
+
}
|
|
5362
|
+
videos.push(
|
|
5363
|
+
startAtPlayed({ ...video, start: absolute.start, end: absolute.end }, absolute.played)
|
|
5364
|
+
);
|
|
5321
5365
|
}
|
|
5322
5366
|
const images = [];
|
|
5323
5367
|
for (const image of parseImageElements(html)) {
|
|
5324
5368
|
const absolute = toAbsoluteWindow(image.start, image.end, windowFor(image.id));
|
|
5325
|
-
if (absolute) images.push({ ...image,
|
|
5369
|
+
if (absolute) images.push({ ...image, start: absolute.start, end: absolute.end });
|
|
5370
|
+
else droppedMediaIds.add(image.id);
|
|
5326
5371
|
}
|
|
5327
5372
|
const audios = [];
|
|
5328
5373
|
for (const audio of parseAudioElements(html)) {
|
|
5329
5374
|
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
|
-
});
|
|
5375
|
+
const placed = placeAudio(audio, windowFor(elementId));
|
|
5376
|
+
if (placed) audios.push(placed);
|
|
5377
|
+
else droppedMediaIds.add(elementId);
|
|
5338
5378
|
}
|
|
5339
|
-
return { videos, audios, images };
|
|
5379
|
+
return { videos, audios, images, droppedMediaIds };
|
|
5380
|
+
}
|
|
5381
|
+
function placeAudio(audio, window2) {
|
|
5382
|
+
const authoredEnd = audio.end > 0 ? audio.end : Infinity;
|
|
5383
|
+
const absolute = toAbsoluteWindow(audio.start, authoredEnd, window2);
|
|
5384
|
+
if (!absolute) return null;
|
|
5385
|
+
const end = Number.isFinite(absolute.end) ? absolute.end : 0;
|
|
5386
|
+
return startAtPlayed({ ...audio, start: absolute.start, end }, absolute.played);
|
|
5340
5387
|
}
|
|
5341
5388
|
|
|
5342
5389
|
// src/services/htmlCompiler.ts
|
|
@@ -5344,6 +5391,7 @@ import {
|
|
|
5344
5391
|
analyzeKeyframeIntervals,
|
|
5345
5392
|
assertMediaPayload,
|
|
5346
5393
|
NotMediaPayloadError as NotMediaPayloadError2,
|
|
5394
|
+
parseMasterFxChain,
|
|
5347
5395
|
probeMediaProfile as probeMediaProfile2
|
|
5348
5396
|
} from "@hyperframes/engine";
|
|
5349
5397
|
|
|
@@ -7043,7 +7091,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7043
7091
|
for (const [relPath, absPath] of preparedGifAssets) {
|
|
7044
7092
|
externalAssets.set(relPath, absPath);
|
|
7045
7093
|
}
|
|
7046
|
-
const { videos, audios, images } = collectRenderMedia(html);
|
|
7094
|
+
const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
|
|
7095
|
+
const masterFxChain = parseMasterFxChain(html);
|
|
7047
7096
|
for (const video of videos) {
|
|
7048
7097
|
if (isHttpUrl(video.src)) continue;
|
|
7049
7098
|
const videoPath = resolve6(projectDir, video.src);
|
|
@@ -7077,6 +7126,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7077
7126
|
videos,
|
|
7078
7127
|
audios,
|
|
7079
7128
|
images,
|
|
7129
|
+
droppedMediaIds,
|
|
7130
|
+
...masterFxChain ? { masterFxChain } : {},
|
|
7080
7131
|
unresolvedCompositions,
|
|
7081
7132
|
externalAssets,
|
|
7082
7133
|
width,
|
|
@@ -7509,6 +7560,7 @@ async function runCompileStage(input) {
|
|
|
7509
7560
|
videos: compiled.videos,
|
|
7510
7561
|
audios: compiled.audios,
|
|
7511
7562
|
images: compiled.images,
|
|
7563
|
+
masterFxChain: compiled.masterFxChain,
|
|
7512
7564
|
width: compiled.width,
|
|
7513
7565
|
height: compiled.height
|
|
7514
7566
|
};
|
|
@@ -7571,7 +7623,7 @@ import {
|
|
|
7571
7623
|
isTransientBrowserError,
|
|
7572
7624
|
probeBeginFrameLiveness
|
|
7573
7625
|
} from "@hyperframes/engine";
|
|
7574
|
-
import { fpsToNumber as fpsToNumber7 } from "@hyperframes/core";
|
|
7626
|
+
import { MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2, fpsToNumber as fpsToNumber7, readMediaStart as readMediaStart2 } from "@hyperframes/core";
|
|
7575
7627
|
import { extractMediaSrcMutations } from "@hyperframes/parsers";
|
|
7576
7628
|
|
|
7577
7629
|
// src/services/render/stages/probeFailures.ts
|
|
@@ -7585,6 +7637,9 @@ function isActionableProbeFailure(line) {
|
|
|
7585
7637
|
function resolveCaptureImageFormat(input) {
|
|
7586
7638
|
return input.needsAlpha || input.motionBlur ? "png" : "jpeg";
|
|
7587
7639
|
}
|
|
7640
|
+
function resolveCaptureJpegQuality(quality) {
|
|
7641
|
+
return quality === "draft" ? 80 : 100;
|
|
7642
|
+
}
|
|
7588
7643
|
|
|
7589
7644
|
// src/services/render/stages/probeStage.ts
|
|
7590
7645
|
var FRAME_BOUNDARY_EPSILON = 1e-3;
|
|
@@ -7618,6 +7673,26 @@ function hasVariableBoundMedia(html, variables) {
|
|
|
7618
7673
|
return Boolean(variableId && Object.hasOwn(variables, variableId));
|
|
7619
7674
|
});
|
|
7620
7675
|
}
|
|
7676
|
+
function authoredMediaStarts(html) {
|
|
7677
|
+
const { document: document2 } = parseHTML5(html);
|
|
7678
|
+
const starts = /* @__PURE__ */ new Map();
|
|
7679
|
+
for (const media of document2.querySelectorAll("video, audio")) {
|
|
7680
|
+
const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
|
|
7681
|
+
if (id && !starts.has(id)) starts.set(id, readMediaStart2(media));
|
|
7682
|
+
}
|
|
7683
|
+
return starts;
|
|
7684
|
+
}
|
|
7685
|
+
function adoptRuntimeMediaStart(composition, el, authored) {
|
|
7686
|
+
const shift = el.mediaStart - authored;
|
|
7687
|
+
if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
|
|
7688
|
+
const entries = el.tagName === "video" ? [
|
|
7689
|
+
...composition.videos.filter((video) => video.id === el.id),
|
|
7690
|
+
...composition.audios.filter(
|
|
7691
|
+
(audio) => audio.type === "video" && audio.id === `${el.id}-audio`
|
|
7692
|
+
)
|
|
7693
|
+
] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
|
|
7694
|
+
for (const entry of entries) entry.mediaStart += shift;
|
|
7695
|
+
}
|
|
7621
7696
|
function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
|
|
7622
7697
|
if (projectedEnd <= 0) return existingEnd;
|
|
7623
7698
|
if (sourceChanged && durationInferred) return projectedEnd;
|
|
@@ -7720,7 +7795,7 @@ async function runProbeStage(input) {
|
|
|
7720
7795
|
height,
|
|
7721
7796
|
fps: job.config.fps,
|
|
7722
7797
|
format: captureFormat,
|
|
7723
|
-
quality: captureFormat === "png" ? void 0 :
|
|
7798
|
+
quality: captureFormat === "png" ? void 0 : resolveCaptureJpegQuality(job.config.quality),
|
|
7724
7799
|
variables: job.config.variables,
|
|
7725
7800
|
deviceScaleFactor,
|
|
7726
7801
|
motionBlur,
|
|
@@ -7871,6 +7946,7 @@ async function runProbeStage(input) {
|
|
|
7871
7946
|
const existingVideoIds = new Set(composition.videos.map((v) => v.id));
|
|
7872
7947
|
const existingAudioIds = new Set(composition.audios.map((a) => a.id));
|
|
7873
7948
|
const existingImageIds = new Set(composition.images.map((image) => image.id));
|
|
7949
|
+
const authoredStarts = authoredMediaStarts(compiled.html);
|
|
7874
7950
|
pruneMutedBrowserMedia(composition, browserMedia, existingAudioIds);
|
|
7875
7951
|
for (const el of browserMedia) {
|
|
7876
7952
|
if (!el.src || el.src === "about:blank") continue;
|
|
@@ -7898,9 +7974,11 @@ async function runProbeStage(input) {
|
|
|
7898
7974
|
sourceChanged,
|
|
7899
7975
|
el.durationInferred
|
|
7900
7976
|
);
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7977
|
+
adoptRuntimeMediaStart(
|
|
7978
|
+
composition,
|
|
7979
|
+
el,
|
|
7980
|
+
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
7981
|
+
);
|
|
7904
7982
|
if (el.hasAudio && !el.muted && !existing.hasAudio) {
|
|
7905
7983
|
existing.hasAudio = true;
|
|
7906
7984
|
}
|
|
@@ -7908,7 +7986,7 @@ async function runProbeStage(input) {
|
|
|
7908
7986
|
existing.loop = true;
|
|
7909
7987
|
}
|
|
7910
7988
|
}
|
|
7911
|
-
} else {
|
|
7989
|
+
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
7912
7990
|
composition.videos.push({
|
|
7913
7991
|
id: el.id,
|
|
7914
7992
|
src,
|
|
@@ -7939,14 +8017,16 @@ async function runProbeStage(input) {
|
|
|
7939
8017
|
sourceChanged,
|
|
7940
8018
|
el.durationInferred
|
|
7941
8019
|
);
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
8020
|
+
adoptRuntimeMediaStart(
|
|
8021
|
+
composition,
|
|
8022
|
+
el,
|
|
8023
|
+
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
8024
|
+
);
|
|
7945
8025
|
if (el.volume > 0 && Math.abs((existing.volume ?? 1) - el.volume) > BROWSER_MEDIA_EPSILON) {
|
|
7946
8026
|
existing.volume = el.volume;
|
|
7947
8027
|
}
|
|
7948
8028
|
}
|
|
7949
|
-
} else {
|
|
8029
|
+
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
7950
8030
|
composition.audios.push({
|
|
7951
8031
|
id: el.id,
|
|
7952
8032
|
src,
|
|
@@ -7974,7 +8054,7 @@ async function runProbeStage(input) {
|
|
|
7974
8054
|
existing.end = projectedEnd;
|
|
7975
8055
|
}
|
|
7976
8056
|
}
|
|
7977
|
-
} else {
|
|
8057
|
+
} else if (!compiled.droppedMediaIds.has(el.id)) {
|
|
7978
8058
|
composition.images.push({
|
|
7979
8059
|
id: el.id,
|
|
7980
8060
|
src,
|
|
@@ -8557,6 +8637,7 @@ async function runAudioStage(input) {
|
|
|
8557
8637
|
ffmpegProcessTimeout,
|
|
8558
8638
|
audioGain,
|
|
8559
8639
|
audios,
|
|
8640
|
+
masterFxChain,
|
|
8560
8641
|
abortSignal,
|
|
8561
8642
|
assertNotAborted,
|
|
8562
8643
|
log
|
|
@@ -8576,7 +8657,7 @@ async function runAudioStage(input) {
|
|
|
8576
8657
|
audioOutputPath,
|
|
8577
8658
|
duration,
|
|
8578
8659
|
abortSignal,
|
|
8579
|
-
{ ffmpegProcessTimeout, audioGain },
|
|
8660
|
+
{ ffmpegProcessTimeout, audioGain, masterFxChain },
|
|
8580
8661
|
compiledDir
|
|
8581
8662
|
);
|
|
8582
8663
|
} catch (err) {
|
|
@@ -8662,13 +8743,16 @@ function shouldAllowAdaptiveCaptureRetry(workerCount, _explicitlyConfigured) {
|
|
|
8662
8743
|
return workerCount > 1;
|
|
8663
8744
|
}
|
|
8664
8745
|
var JPEG_RAW_RGBA_DIVISOR = 8;
|
|
8746
|
+
var HIGH_QUALITY_JPEG_RAW_RGBA_DIVISOR = 4;
|
|
8665
8747
|
var DISK_PROJECTION_SAMPLE_FRAMES = 10;
|
|
8666
8748
|
function estimateDiskCaptureBytes(totalFrames, captureOptions) {
|
|
8667
8749
|
const scale = captureOptions.deviceScaleFactor ?? 1;
|
|
8668
8750
|
const outputWidth = Math.ceil(captureOptions.width * scale);
|
|
8669
8751
|
const outputHeight = Math.ceil(captureOptions.height * scale);
|
|
8670
8752
|
const rawBytes = Math.ceil(totalFrames) * outputWidth * outputHeight * 4;
|
|
8671
|
-
return frameFileExtension(captureOptions.format) === "jpg" ? Math.ceil(
|
|
8753
|
+
return frameFileExtension(captureOptions.format) === "jpg" ? Math.ceil(
|
|
8754
|
+
rawBytes / ((captureOptions.quality ?? 80) > 80 ? HIGH_QUALITY_JPEG_RAW_RGBA_DIVISOR : JPEG_RAW_RGBA_DIVISOR)
|
|
8755
|
+
) : rawBytes;
|
|
8672
8756
|
}
|
|
8673
8757
|
function freeDiskBytesAt(path) {
|
|
8674
8758
|
try {
|
|
@@ -12045,6 +12129,7 @@ async function executeRenderPipeline(input) {
|
|
|
12045
12129
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
|
|
12046
12130
|
audioGain: cfg.audioGain,
|
|
12047
12131
|
audios: composition.audios,
|
|
12132
|
+
masterFxChain: composition.masterFxChain,
|
|
12048
12133
|
abortSignal: executionSignal,
|
|
12049
12134
|
assertNotAborted,
|
|
12050
12135
|
log
|
|
@@ -12129,7 +12214,7 @@ async function executeRenderPipeline(input) {
|
|
|
12129
12214
|
height,
|
|
12130
12215
|
fps: job.config.fps,
|
|
12131
12216
|
format: captureImageFormat,
|
|
12132
|
-
quality: captureImageFormat === "png" ? void 0 : job.config.quality
|
|
12217
|
+
quality: captureImageFormat === "png" ? void 0 : resolveCaptureJpegQuality(job.config.quality),
|
|
12133
12218
|
variables: job.config.variables,
|
|
12134
12219
|
deviceScaleFactor,
|
|
12135
12220
|
motionBlur: job.config.motionBlur,
|
|
@@ -12757,7 +12842,8 @@ async function executeRenderPipeline(input) {
|
|
|
12757
12842
|
// Device-scaled: the capture buffer, not the CSS composition size.
|
|
12758
12843
|
outputWidth: captureCompositionWidth ?? width,
|
|
12759
12844
|
outputHeight: captureCompositionHeight ?? height,
|
|
12760
|
-
motionBlur: job.config.motionBlur ? JSON.stringify(job.config.motionBlur) : ""
|
|
12845
|
+
motionBlur: job.config.motionBlur ? JSON.stringify(job.config.motionBlur) : "",
|
|
12846
|
+
captureQuality: captureOptions.quality
|
|
12761
12847
|
});
|
|
12762
12848
|
const segmentDir = segmentDirFor(join26(projectDir, "renders"), segmentPlanHash);
|
|
12763
12849
|
let completedSegments = /* @__PURE__ */ new Set();
|
|
@@ -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>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MotionBlurOptions } from "@hyperframes/engine";
|
|
1
|
+
import type { EngineConfig, MotionBlurOptions } from "@hyperframes/engine";
|
|
2
2
|
/**
|
|
3
3
|
* The format a render captures its frames in.
|
|
4
4
|
*
|
|
@@ -16,3 +16,8 @@ export declare function resolveCaptureImageFormat(input: {
|
|
|
16
16
|
needsAlpha: boolean;
|
|
17
17
|
motionBlur: MotionBlurOptions | undefined;
|
|
18
18
|
}): "jpeg" | "png";
|
|
19
|
+
/**
|
|
20
|
+
* Below 100, Chrome's JPEG turns graded colour and grain into 8x8 chroma blotches that survive
|
|
21
|
+
* the final encode. Draft keeps the smaller, faster frames.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveCaptureJpegQuality(quality: EngineConfig["quality"]): number;
|
|
@@ -37,6 +37,7 @@ export interface SegmentPlanHashInput {
|
|
|
37
37
|
outputHeight: number;
|
|
38
38
|
/** Serialised motion-blur options, or "" when off; sample count and shutter change every pixel. */
|
|
39
39
|
motionBlur: string;
|
|
40
|
+
captureQuality: number | undefined;
|
|
40
41
|
}
|
|
41
42
|
/** Everything that determines segment bytes. Key order is fixed by construction. */
|
|
42
43
|
export declare function computeSegmentPlanHash(input: SegmentPlanHashInput): 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. */
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Collect the render pipeline's media list from the fully inlined document.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* document, so two scenes that both declare `<video id="clip">` — or that both
|
|
8
|
-
* declare a bare `<video>` and get the per-file auto-id `hf-video-0` — collapse
|
|
9
|
-
* into a single entry. See mediaRenderIds.ts for the full failure.
|
|
10
|
-
*
|
|
11
|
-
* Reading the inlined document instead makes the render document the single
|
|
12
|
-
* source of truth for what media exists: every element is present exactly once,
|
|
13
|
-
* `assignMediaRenderIds` has already given it a document-unique key, and the
|
|
14
|
-
* timeline offsets are recoverable from the composition hosts it sits inside.
|
|
4
|
+
* Ids are unique per composition file, not per render document (see mediaRenderIds.ts), so media is
|
|
5
|
+
* read from the inlined document: each element once, keyed by its render id or else its author id,
|
|
6
|
+
* and placed by the composition hosts it sits inside.
|
|
15
7
|
*/
|
|
16
8
|
import { type VideoElement, type ImageElement, type AudioElement } from "@hyperframes/engine";
|
|
17
9
|
export interface RenderMedia {
|
|
18
10
|
videos: VideoElement[];
|
|
19
11
|
audios: AudioElement[];
|
|
20
12
|
images: ImageElement[];
|
|
13
|
+
/** Ids of clips in the document that never play; the browser probe must not add them back. */
|
|
14
|
+
droppedMediaIds: Set<string>;
|
|
21
15
|
}
|
|
22
16
|
/**
|
|
23
17
|
* Parse every media element in the inlined render document, with each clip's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/producer",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.151",
|
|
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/
|
|
41
|
-
"@hyperframes/
|
|
42
|
-
"@hyperframes/
|
|
38
|
+
"@hyperframes/lint": "0.8.151",
|
|
39
|
+
"@hyperframes/engine": "0.8.151",
|
|
40
|
+
"@hyperframes/studio-server": "0.8.151",
|
|
41
|
+
"@hyperframes/parsers": "0.8.151",
|
|
42
|
+
"@hyperframes/core": "0.8.151"
|
|
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.151"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=22"
|