@hyperframes/producer 0.4.30 → 0.4.32
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/README.md +62 -12
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +172 -7
- package/dist/index.js +451 -159
- package/dist/index.js.map +4 -4
- package/dist/parity-fixtures.d.ts +2 -0
- package/dist/parity-fixtures.d.ts.map +1 -0
- package/dist/public-server.js +451 -159
- package/dist/public-server.js.map +4 -4
- package/dist/services/htmlCompiler.d.ts.map +1 -1
- package/dist/services/renderOrchestrator.d.ts +34 -2
- package/dist/services/renderOrchestrator.d.ts.map +1 -1
- package/dist/transparency-test.d.ts +19 -0
- package/dist/transparency-test.d.ts.map +1 -0
- package/package.json +6 -5
package/dist/public-server.js
CHANGED
|
@@ -102365,12 +102365,28 @@ var coreRules = [
|
|
|
102365
102365
|
function escapeRegExp(value) {
|
|
102366
102366
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
102367
102367
|
}
|
|
102368
|
-
function
|
|
102368
|
+
function hasAttrName(tagSource, attr) {
|
|
102369
|
+
const escaped = escapeRegExp(attr);
|
|
102370
|
+
const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
|
|
102371
|
+
return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
|
|
102372
|
+
}
|
|
102373
|
+
function classNamesFromAttr(classAttr) {
|
|
102374
|
+
if (!classAttr) return [];
|
|
102375
|
+
return classAttr.split(/\s+/).filter(Boolean);
|
|
102376
|
+
}
|
|
102377
|
+
function selectorTargetsManagedMedia(selector, mediaIndex) {
|
|
102369
102378
|
const normalized = selector.trim();
|
|
102370
102379
|
if (!normalized) return false;
|
|
102371
|
-
if (/\
|
|
102372
|
-
|
|
102373
|
-
|
|
102380
|
+
if (mediaIndex.hasVideo && /\bvideo\b/i.test(normalized)) return true;
|
|
102381
|
+
if (mediaIndex.hasAudio && /\baudio\b/i.test(normalized)) return true;
|
|
102382
|
+
for (const mediaId of mediaIndex.ids) {
|
|
102383
|
+
const escapedId = escapeRegExp(mediaId);
|
|
102384
|
+
if (new RegExp(`#${escapedId}(?![\\w-])`).test(normalized) || normalized.includes(`[id="${mediaId}"]`) || normalized.includes(`[id='${mediaId}']`)) {
|
|
102385
|
+
return true;
|
|
102386
|
+
}
|
|
102387
|
+
}
|
|
102388
|
+
for (const className of mediaIndex.classes) {
|
|
102389
|
+
if (new RegExp(`\\.${escapeRegExp(className)}(?![\\w-])`).test(normalized)) {
|
|
102374
102390
|
return true;
|
|
102375
102391
|
}
|
|
102376
102392
|
}
|
|
@@ -102378,66 +102394,96 @@ function selectorTargetsManagedMedia(selector, mediaIds) {
|
|
|
102378
102394
|
}
|
|
102379
102395
|
function findImperativeMediaControlFindings(ctx) {
|
|
102380
102396
|
const findings = [];
|
|
102381
|
-
const
|
|
102382
|
-
|
|
102383
|
-
|
|
102384
|
-
|
|
102397
|
+
const mediaTags = ctx.tags.filter((tag) => tag.name === "video" || tag.name === "audio");
|
|
102398
|
+
const mediaIndex = {
|
|
102399
|
+
ids: new Set(
|
|
102400
|
+
mediaTags.map((tag) => readAttr(tag.raw, "id")).filter((id) => Boolean(id))
|
|
102401
|
+
),
|
|
102402
|
+
classes: new Set(mediaTags.flatMap((tag) => classNamesFromAttr(readAttr(tag.raw, "class")))),
|
|
102403
|
+
hasVideo: mediaTags.some((tag) => tag.name === "video"),
|
|
102404
|
+
hasAudio: mediaTags.some((tag) => tag.name === "audio")
|
|
102405
|
+
};
|
|
102406
|
+
if (mediaTags.length === 0 || ctx.scripts.length === 0) return findings;
|
|
102385
102407
|
for (const script of ctx.scripts) {
|
|
102386
102408
|
const mediaVars = /* @__PURE__ */ new Map();
|
|
102387
102409
|
const assignmentPatterns = [
|
|
102388
|
-
|
|
102389
|
-
|
|
102410
|
+
{
|
|
102411
|
+
pattern: /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)/g,
|
|
102412
|
+
variableIndex: 1,
|
|
102413
|
+
targetIndex: 2
|
|
102414
|
+
},
|
|
102415
|
+
{
|
|
102416
|
+
pattern: /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\2\s*\)/g,
|
|
102417
|
+
variableIndex: 1,
|
|
102418
|
+
targetIndex: 3
|
|
102419
|
+
}
|
|
102390
102420
|
];
|
|
102391
|
-
for (const pattern of assignmentPatterns) {
|
|
102421
|
+
for (const { pattern, variableIndex, targetIndex } of assignmentPatterns) {
|
|
102392
102422
|
let match2;
|
|
102393
102423
|
while ((match2 = pattern.exec(script.content)) !== null) {
|
|
102394
|
-
const variableName = match2[
|
|
102395
|
-
const target = match2[
|
|
102424
|
+
const variableName = match2[variableIndex];
|
|
102425
|
+
const target = match2[targetIndex];
|
|
102396
102426
|
if (!variableName || !target) continue;
|
|
102397
|
-
if (
|
|
102398
|
-
mediaVars.set(variableName,
|
|
102427
|
+
if (mediaIndex.ids.has(target) || selectorTargetsManagedMedia(target, mediaIndex)) {
|
|
102428
|
+
mediaVars.set(variableName, mediaIndex.ids.has(target) ? target : void 0);
|
|
102399
102429
|
}
|
|
102400
102430
|
}
|
|
102401
102431
|
}
|
|
102402
102432
|
const directIdPatterns = [
|
|
102403
102433
|
{
|
|
102404
102434
|
pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.play\s*\(/g,
|
|
102405
|
-
kind: "play()"
|
|
102435
|
+
kind: "play()",
|
|
102436
|
+
targetIndex: 1
|
|
102406
102437
|
},
|
|
102407
102438
|
{
|
|
102408
102439
|
pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.pause\s*\(/g,
|
|
102409
|
-
kind: "pause()"
|
|
102440
|
+
kind: "pause()",
|
|
102441
|
+
targetIndex: 1
|
|
102410
102442
|
},
|
|
102411
102443
|
{
|
|
102412
102444
|
pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.currentTime\s*=/g,
|
|
102413
|
-
kind: "currentTime"
|
|
102445
|
+
kind: "currentTime",
|
|
102446
|
+
targetIndex: 1
|
|
102447
|
+
},
|
|
102448
|
+
{
|
|
102449
|
+
pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.muted\s*=/g,
|
|
102450
|
+
kind: "muted assignment",
|
|
102451
|
+
targetIndex: 1
|
|
102414
102452
|
},
|
|
102415
102453
|
{
|
|
102416
|
-
pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([
|
|
102417
|
-
kind: "play()"
|
|
102454
|
+
pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.play\s*\(/g,
|
|
102455
|
+
kind: "play()",
|
|
102456
|
+
targetIndex: 2
|
|
102418
102457
|
},
|
|
102419
102458
|
{
|
|
102420
|
-
pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([
|
|
102421
|
-
kind: "pause()"
|
|
102459
|
+
pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.pause\s*\(/g,
|
|
102460
|
+
kind: "pause()",
|
|
102461
|
+
targetIndex: 2
|
|
102422
102462
|
},
|
|
102423
102463
|
{
|
|
102424
|
-
pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([
|
|
102425
|
-
kind: "currentTime"
|
|
102464
|
+
pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.currentTime\s*=/g,
|
|
102465
|
+
kind: "currentTime",
|
|
102466
|
+
targetIndex: 2
|
|
102467
|
+
},
|
|
102468
|
+
{
|
|
102469
|
+
pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.muted\s*=/g,
|
|
102470
|
+
kind: "muted assignment",
|
|
102471
|
+
targetIndex: 2
|
|
102426
102472
|
}
|
|
102427
102473
|
];
|
|
102428
|
-
for (const { pattern, kind } of directIdPatterns) {
|
|
102474
|
+
for (const { pattern, kind, targetIndex } of directIdPatterns) {
|
|
102429
102475
|
let match2;
|
|
102430
102476
|
while ((match2 = pattern.exec(script.content)) !== null) {
|
|
102431
|
-
const target = match2[
|
|
102477
|
+
const target = match2[targetIndex];
|
|
102432
102478
|
if (!target) continue;
|
|
102433
|
-
const elementId =
|
|
102479
|
+
const elementId = mediaIndex.ids.has(target) ? target : selectorTargetsManagedMedia(target, mediaIndex) ? void 0 : null;
|
|
102434
102480
|
if (elementId === null) continue;
|
|
102435
102481
|
findings.push({
|
|
102436
102482
|
code: "imperative_media_control",
|
|
102437
102483
|
severity: "error",
|
|
102438
102484
|
message: `Inline <script> imperatively controls managed media via ${kind}. HyperFrames must own media play/pause/seek to keep preview, timeline, and renders deterministic.`,
|
|
102439
102485
|
elementId: elementId || void 0,
|
|
102440
|
-
fixHint: "Remove imperative media play/pause/currentTime control. Express timing with data-start/data-duration and media offsets like data-media-start or data-playback-start instead.",
|
|
102486
|
+
fixHint: "Remove imperative media play/pause/currentTime/muted control. Express timing with data-start/data-duration and media offsets like data-media-start or data-playback-start instead.",
|
|
102441
102487
|
snippet: truncateSnippet(match2[0])
|
|
102442
102488
|
});
|
|
102443
102489
|
}
|
|
@@ -102447,7 +102493,11 @@ function findImperativeMediaControlFindings(ctx) {
|
|
|
102447
102493
|
const variablePatterns = [
|
|
102448
102494
|
{ pattern: new RegExp(`\\b${escapedVar}\\.play\\s*\\(`, "g"), kind: "play()" },
|
|
102449
102495
|
{ pattern: new RegExp(`\\b${escapedVar}\\.pause\\s*\\(`, "g"), kind: "pause()" },
|
|
102450
|
-
{ pattern: new RegExp(`\\b${escapedVar}\\.currentTime\\s*=`, "g"), kind: "currentTime" }
|
|
102496
|
+
{ pattern: new RegExp(`\\b${escapedVar}\\.currentTime\\s*=`, "g"), kind: "currentTime" },
|
|
102497
|
+
{
|
|
102498
|
+
pattern: new RegExp(`\\b${escapedVar}\\.muted\\s*=`, "g"),
|
|
102499
|
+
kind: "muted assignment"
|
|
102500
|
+
}
|
|
102451
102501
|
];
|
|
102452
102502
|
for (const { pattern, kind } of variablePatterns) {
|
|
102453
102503
|
let match2;
|
|
@@ -102457,7 +102507,7 @@ function findImperativeMediaControlFindings(ctx) {
|
|
|
102457
102507
|
severity: "error",
|
|
102458
102508
|
message: `Inline <script> imperatively controls managed media via ${kind}. HyperFrames must own media play/pause/seek to keep preview, timeline, and renders deterministic.`,
|
|
102459
102509
|
elementId,
|
|
102460
|
-
fixHint: "Remove imperative media play/pause/currentTime control. Express timing with data-start/data-duration and media offsets like data-media-start or data-playback-start instead.",
|
|
102510
|
+
fixHint: "Remove imperative media play/pause/currentTime/muted control. Express timing with data-start/data-duration and media offsets like data-media-start or data-playback-start instead.",
|
|
102461
102511
|
snippet: truncateSnippet(match2[0])
|
|
102462
102512
|
});
|
|
102463
102513
|
}
|
|
@@ -102519,15 +102569,27 @@ var mediaRules = [
|
|
|
102519
102569
|
const findings = [];
|
|
102520
102570
|
for (const tag of tags) {
|
|
102521
102571
|
if (tag.name !== "video") continue;
|
|
102522
|
-
const hasMuted =
|
|
102523
|
-
|
|
102572
|
+
const hasMuted = hasAttrName(tag.raw, "muted");
|
|
102573
|
+
const hasDeclaredAudio = readAttr(tag.raw, "data-has-audio") === "true";
|
|
102574
|
+
if (!hasMuted && !hasDeclaredAudio && readAttr(tag.raw, "data-start")) {
|
|
102524
102575
|
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
102525
102576
|
findings.push({
|
|
102526
102577
|
code: "video_missing_muted",
|
|
102527
102578
|
severity: "error",
|
|
102528
|
-
message: `<video${elementId ? ` id="${elementId}"` : ""}> has data-start but is not muted.
|
|
102579
|
+
message: `<video${elementId ? ` id="${elementId}"` : ""}> has data-start but is not muted. Mark audible videos with data-has-audio="true"; otherwise keep video muted and use a separate <audio> element for sound.`,
|
|
102580
|
+
elementId,
|
|
102581
|
+
fixHint: 'Add the `muted` attribute for silent video, or add data-has-audio="true" when the video track should contribute audio.',
|
|
102582
|
+
snippet: truncateSnippet(tag.raw)
|
|
102583
|
+
});
|
|
102584
|
+
}
|
|
102585
|
+
if (hasMuted && hasDeclaredAudio) {
|
|
102586
|
+
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
102587
|
+
findings.push({
|
|
102588
|
+
code: "video_muted_with_declared_audio",
|
|
102589
|
+
severity: "error",
|
|
102590
|
+
message: `<video${elementId ? ` id="${elementId}"` : ""}> declares data-has-audio="true" but also has muted. Studio preview will silence the video audio.`,
|
|
102529
102591
|
elementId,
|
|
102530
|
-
fixHint:
|
|
102592
|
+
fixHint: 'Remove the `muted` attribute if this video should be audible, or remove data-has-audio="true" and use data-volume="0" for silent visual video.',
|
|
102531
102593
|
snippet: truncateSnippet(tag.raw)
|
|
102532
102594
|
});
|
|
102533
102595
|
}
|
|
@@ -102649,7 +102711,7 @@ var mediaRules = [
|
|
|
102649
102711
|
}
|
|
102650
102712
|
return findings;
|
|
102651
102713
|
},
|
|
102652
|
-
// media_missing_id + media_missing_src + media_preload_none
|
|
102714
|
+
// media_missing_data_start + media_missing_id + media_missing_src + media_preload_none
|
|
102653
102715
|
({ tags }) => {
|
|
102654
102716
|
const findings = [];
|
|
102655
102717
|
for (const tag of tags) {
|
|
@@ -102657,6 +102719,16 @@ var mediaRules = [
|
|
|
102657
102719
|
const hasDataStart = readAttr(tag.raw, "data-start");
|
|
102658
102720
|
const hasId = readAttr(tag.raw, "id");
|
|
102659
102721
|
const hasSrc = readAttr(tag.raw, "src");
|
|
102722
|
+
if (hasSrc && !hasDataStart) {
|
|
102723
|
+
findings.push({
|
|
102724
|
+
code: "media_missing_data_start",
|
|
102725
|
+
severity: "error",
|
|
102726
|
+
message: `<${tag.name}${hasId ? ` id="${hasId}"` : ""}> has src but no data-start. HyperFrames cannot own playback for untimed media, so preview and render behavior can diverge.`,
|
|
102727
|
+
elementId: hasId || void 0,
|
|
102728
|
+
fixHint: `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
|
|
102729
|
+
snippet: truncateSnippet(tag.raw)
|
|
102730
|
+
});
|
|
102731
|
+
}
|
|
102660
102732
|
if (hasDataStart && !hasId) {
|
|
102661
102733
|
findings.push({
|
|
102662
102734
|
code: "media_missing_id",
|
|
@@ -103513,6 +103585,31 @@ var compositionRules = [
|
|
|
103513
103585
|
}
|
|
103514
103586
|
return findings;
|
|
103515
103587
|
},
|
|
103588
|
+
// split_data_attribute_selector
|
|
103589
|
+
({ scripts, styles }) => {
|
|
103590
|
+
const findings = [];
|
|
103591
|
+
const splitDataAttrSelectorPattern = /\[data-composition-id=(["'])([^"'\]]+)\1\s+(data-[\w:-]+)=(["'])([^"'\]]*)\4\]/g;
|
|
103592
|
+
const scan = (content) => {
|
|
103593
|
+
splitDataAttrSelectorPattern.lastIndex = 0;
|
|
103594
|
+
let match2;
|
|
103595
|
+
while ((match2 = splitDataAttrSelectorPattern.exec(content)) !== null) {
|
|
103596
|
+
const compId = match2[2] ?? "";
|
|
103597
|
+
const attrName = match2[3] ?? "";
|
|
103598
|
+
const attrValue = match2[5] ?? "";
|
|
103599
|
+
findings.push({
|
|
103600
|
+
code: "split_data_attribute_selector",
|
|
103601
|
+
severity: "error",
|
|
103602
|
+
message: `Selector "${match2[0]}" combines two attributes inside one CSS attribute selector. Browsers reject it, so GSAP timelines or querySelector calls will fail before registering.`,
|
|
103603
|
+
selector: match2[0],
|
|
103604
|
+
fixHint: `Use separate attribute selectors: [data-composition-id="${compId}"][${attrName}="${attrValue}"].`,
|
|
103605
|
+
snippet: truncateSnippet(match2[0])
|
|
103606
|
+
});
|
|
103607
|
+
}
|
|
103608
|
+
};
|
|
103609
|
+
for (const style of styles) scan(style.content);
|
|
103610
|
+
for (const script of scripts) scan(script.content);
|
|
103611
|
+
return findings;
|
|
103612
|
+
},
|
|
103516
103613
|
// template_literal_selector
|
|
103517
103614
|
({ scripts }) => {
|
|
103518
103615
|
const findings = [];
|
|
@@ -103926,13 +104023,13 @@ async function beginFrameCapture(page, options, frameTimeTicks, interval) {
|
|
|
103926
104023
|
}
|
|
103927
104024
|
async function pageScreenshotCapture(page, options) {
|
|
103928
104025
|
const client = await getCdpSession(page);
|
|
103929
|
-
const
|
|
104026
|
+
const isPng = options.format === "png";
|
|
103930
104027
|
const result = await client.send("Page.captureScreenshot", {
|
|
103931
|
-
format:
|
|
103932
|
-
quality:
|
|
104028
|
+
format: isPng ? "png" : "jpeg",
|
|
104029
|
+
quality: isPng ? void 0 : options.quality ?? 80,
|
|
103933
104030
|
fromSurface: true,
|
|
103934
104031
|
captureBeyondViewport: false,
|
|
103935
|
-
optimizeForSpeed:
|
|
104032
|
+
optimizeForSpeed: !isPng
|
|
103936
104033
|
});
|
|
103937
104034
|
return Buffer.from(result.data, "base64");
|
|
103938
104035
|
}
|
|
@@ -104145,12 +104242,6 @@ async function createCaptureSession(serverUrl, outputDir, options, onBeforeCaptu
|
|
|
104145
104242
|
deviceScaleFactor: options.deviceScaleFactor || 1
|
|
104146
104243
|
};
|
|
104147
104244
|
await page.setViewport(viewport);
|
|
104148
|
-
if (options.format === "png") {
|
|
104149
|
-
const cdp = await getCdpSession(page);
|
|
104150
|
-
await cdp.send("Emulation.setDefaultBackgroundColorOverride", {
|
|
104151
|
-
color: { r: 0, g: 0, b: 0, a: 0 }
|
|
104152
|
-
});
|
|
104153
|
-
}
|
|
104154
104245
|
return {
|
|
104155
104246
|
browser,
|
|
104156
104247
|
page,
|
|
@@ -104246,6 +104337,9 @@ async function initializeSession(session) {
|
|
|
104246
104337
|
);
|
|
104247
104338
|
}
|
|
104248
104339
|
await page.evaluate(`document.fonts?.ready`);
|
|
104340
|
+
if (session.options.format === "png") {
|
|
104341
|
+
await initTransparentBackground(session.page);
|
|
104342
|
+
}
|
|
104249
104343
|
session.isInitialized = true;
|
|
104250
104344
|
return;
|
|
104251
104345
|
}
|
|
@@ -104309,6 +104403,9 @@ async function initializeSession(session) {
|
|
|
104309
104403
|
await page.evaluate(`document.fonts?.ready`);
|
|
104310
104404
|
warmupRunning = false;
|
|
104311
104405
|
session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
|
|
104406
|
+
if (session.options.format === "png") {
|
|
104407
|
+
await initTransparentBackground(session.page);
|
|
104408
|
+
}
|
|
104312
104409
|
session.isInitialized = true;
|
|
104313
104410
|
}
|
|
104314
104411
|
async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
|
|
@@ -108823,7 +108920,217 @@ function createFileServer2(options) {
|
|
|
108823
108920
|
// src/services/htmlCompiler.ts
|
|
108824
108921
|
import { readFileSync as readFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
|
|
108825
108922
|
import { join as join15, dirname as dirname9, resolve as resolve10 } from "path";
|
|
108923
|
+
|
|
108924
|
+
// ../core/src/compiler/htmlBundler.ts
|
|
108925
|
+
import { transformSync } from "esbuild";
|
|
108926
|
+
|
|
108927
|
+
// ../core/src/compiler/compositionScoping.ts
|
|
108826
108928
|
import postcss from "postcss";
|
|
108929
|
+
function escapeRegExp2(value) {
|
|
108930
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
108931
|
+
}
|
|
108932
|
+
function escapeCssAttributeValue(value) {
|
|
108933
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
108934
|
+
}
|
|
108935
|
+
function scopeSelector(selector, scope, compositionId) {
|
|
108936
|
+
const selectorWithoutRootTiming = normalizeCompositionRootSelector(
|
|
108937
|
+
selector,
|
|
108938
|
+
scope,
|
|
108939
|
+
compositionId
|
|
108940
|
+
);
|
|
108941
|
+
const trimmed = selectorWithoutRootTiming.trim();
|
|
108942
|
+
if (!trimmed) return selector;
|
|
108943
|
+
if (/^(html|body|:root|\*)$/i.test(trimmed)) return selector;
|
|
108944
|
+
const compositionIdPattern = new RegExp(
|
|
108945
|
+
`data-composition-id\\s*=\\s*(["'])${escapeRegExp2(compositionId)}\\1`
|
|
108946
|
+
);
|
|
108947
|
+
if (compositionIdPattern.test(trimmed)) return selectorWithoutRootTiming;
|
|
108948
|
+
const leading = selectorWithoutRootTiming.match(/^\s*/)?.[0] ?? "";
|
|
108949
|
+
const trailing = selectorWithoutRootTiming.match(/\s*$/)?.[0] ?? "";
|
|
108950
|
+
return `${leading}${scope} ${trimmed}${trailing}`;
|
|
108951
|
+
}
|
|
108952
|
+
function normalizeCompositionRootSelector(selector, scope, compositionId) {
|
|
108953
|
+
const quotedCompId = escapeRegExp2(compositionId);
|
|
108954
|
+
const compAttr = String.raw`\[\s*data-composition-id\s*=\s*(?:"${quotedCompId}"|'${quotedCompId}')\s*\]`;
|
|
108955
|
+
const timingAttr = String.raw`\s*\[\s*data-(?:start|duration)\s*=\s*(?:"[^"]*"|'[^']*')\s*\]`;
|
|
108956
|
+
return selector.replace(new RegExp(`${compAttr}(?:${timingAttr})+`, "g"), scope).replace(new RegExp(`(?:${timingAttr})+${compAttr}`, "g"), scope);
|
|
108957
|
+
}
|
|
108958
|
+
var GLOBAL_AT_RULES = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
|
|
108959
|
+
function isAtRuleNode(node) {
|
|
108960
|
+
return node?.type === "atrule";
|
|
108961
|
+
}
|
|
108962
|
+
function isInsideGlobalAtRule(rule) {
|
|
108963
|
+
let current = rule.parent;
|
|
108964
|
+
while (current) {
|
|
108965
|
+
if (isAtRuleNode(current) && GLOBAL_AT_RULES.has(current.name.toLowerCase())) {
|
|
108966
|
+
return true;
|
|
108967
|
+
}
|
|
108968
|
+
current = current.parent;
|
|
108969
|
+
}
|
|
108970
|
+
return false;
|
|
108971
|
+
}
|
|
108972
|
+
function scopeCssToComposition(css, compositionId) {
|
|
108973
|
+
const trimmedCompositionId = compositionId.trim();
|
|
108974
|
+
if (!css || !trimmedCompositionId) return css;
|
|
108975
|
+
const scope = `[data-composition-id="${escapeCssAttributeValue(trimmedCompositionId)}"]`;
|
|
108976
|
+
const root = postcss.parse(css);
|
|
108977
|
+
root.walkRules((rule) => {
|
|
108978
|
+
if (isInsideGlobalAtRule(rule)) return;
|
|
108979
|
+
rule.selectors = rule.selectors.map(
|
|
108980
|
+
(selector) => scopeSelector(selector, scope, trimmedCompositionId)
|
|
108981
|
+
);
|
|
108982
|
+
});
|
|
108983
|
+
return root.toResult({ map: false }).css;
|
|
108984
|
+
}
|
|
108985
|
+
function wrapScopedCompositionScript(source2, compositionId, errorLabel = "[HyperFrames] composition script error:") {
|
|
108986
|
+
const compositionIdLiteral = JSON.stringify(compositionId);
|
|
108987
|
+
const errorLabelLiteral = JSON.stringify(errorLabel);
|
|
108988
|
+
const escapedCompositionId = escapeRegExp2(compositionId);
|
|
108989
|
+
const rootSelectorPatternLiteral = JSON.stringify(
|
|
108990
|
+
String.raw`\[\s*data-composition-id\s*=\s*(?:"${escapedCompositionId}"|'${escapedCompositionId}')\s*\]`
|
|
108991
|
+
);
|
|
108992
|
+
const timingSelectorPatternLiteral = JSON.stringify(
|
|
108993
|
+
String.raw`\s*\[\s*data-(?:start|duration)\s*=\s*(?:"[^"]*"|'[^']*')\s*\]`
|
|
108994
|
+
);
|
|
108995
|
+
return `(function(){
|
|
108996
|
+
var __hfCompId = ${compositionIdLiteral};
|
|
108997
|
+
var __hfErrorLabel = ${errorLabelLiteral};
|
|
108998
|
+
var __hfEscapeAttr = function(value) {
|
|
108999
|
+
return (value + "").replace(/\\\\/g, "\\\\\\\\").replace(/"/g, "\\\\\\"");
|
|
109000
|
+
};
|
|
109001
|
+
var __hfRootSelector = __hfCompId
|
|
109002
|
+
? '[data-composition-id="' + __hfEscapeAttr(__hfCompId) + '"]'
|
|
109003
|
+
: "";
|
|
109004
|
+
var __hfRoot = null;
|
|
109005
|
+
var __hfRootSelectorPattern = ${rootSelectorPatternLiteral};
|
|
109006
|
+
var __hfTimingSelectorPattern = ${timingSelectorPatternLiteral};
|
|
109007
|
+
var __hfNormalizeSelector = function(selector) {
|
|
109008
|
+
if (!__hfCompId || typeof selector !== "string") return selector;
|
|
109009
|
+
return selector
|
|
109010
|
+
.replace(new RegExp(__hfRootSelectorPattern + '(?:' + __hfTimingSelectorPattern + ')+', 'g'), __hfRootSelector)
|
|
109011
|
+
.replace(new RegExp('(?:' + __hfTimingSelectorPattern + ')+' + __hfRootSelectorPattern, 'g'), __hfRootSelector);
|
|
109012
|
+
};
|
|
109013
|
+
var __hfFindRoot = function() {
|
|
109014
|
+
if (!__hfRoot && __hfRootSelector) {
|
|
109015
|
+
__hfRoot = window.document.querySelector(__hfRootSelector);
|
|
109016
|
+
}
|
|
109017
|
+
return __hfRoot;
|
|
109018
|
+
};
|
|
109019
|
+
var __hfContains = function(node) {
|
|
109020
|
+
var root = __hfFindRoot();
|
|
109021
|
+
return !root || node === root || root.contains(node);
|
|
109022
|
+
};
|
|
109023
|
+
var __hfQueryAll = function(selector) {
|
|
109024
|
+
var root = __hfFindRoot();
|
|
109025
|
+
if (!root || typeof selector !== "string") {
|
|
109026
|
+
return window.document.querySelectorAll(selector);
|
|
109027
|
+
}
|
|
109028
|
+
return Array.prototype.filter.call(window.document.querySelectorAll(__hfNormalizeSelector(selector)), function(node) {
|
|
109029
|
+
return __hfContains(node);
|
|
109030
|
+
});
|
|
109031
|
+
};
|
|
109032
|
+
var __hfQueryOne = function(selector) {
|
|
109033
|
+
var matches = __hfQueryAll(selector);
|
|
109034
|
+
return matches[0] || null;
|
|
109035
|
+
};
|
|
109036
|
+
var __hfScopedDocument = typeof Proxy === "function"
|
|
109037
|
+
? new Proxy(window.document, {
|
|
109038
|
+
get: function(target, prop, receiver) {
|
|
109039
|
+
if (prop === "querySelector") return __hfQueryOne;
|
|
109040
|
+
if (prop === "querySelectorAll") return __hfQueryAll;
|
|
109041
|
+
if (prop === "getElementById") {
|
|
109042
|
+
return function(id) {
|
|
109043
|
+
var found = target.getElementById(id);
|
|
109044
|
+
return found && __hfContains(found) ? found : null;
|
|
109045
|
+
};
|
|
109046
|
+
}
|
|
109047
|
+
var value = Reflect.get(target, prop, receiver);
|
|
109048
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
109049
|
+
},
|
|
109050
|
+
})
|
|
109051
|
+
: window.document;
|
|
109052
|
+
var __hfResolveGsapTarget = function(target) {
|
|
109053
|
+
if (typeof target !== "string") return target;
|
|
109054
|
+
return __hfQueryAll(target);
|
|
109055
|
+
};
|
|
109056
|
+
var __hfScopeTimeline = function(timeline) {
|
|
109057
|
+
if (!timeline || timeline.__hfScopedCompositionRoot === __hfFindRoot()) return timeline;
|
|
109058
|
+
["to", "from", "fromTo", "set"].forEach(function(method) {
|
|
109059
|
+
var original = timeline[method];
|
|
109060
|
+
if (typeof original !== "function") return;
|
|
109061
|
+
timeline[method] = function(target) {
|
|
109062
|
+
var args = Array.prototype.slice.call(arguments);
|
|
109063
|
+
args[0] = __hfResolveGsapTarget(target);
|
|
109064
|
+
return original.apply(timeline, args);
|
|
109065
|
+
};
|
|
109066
|
+
});
|
|
109067
|
+
try {
|
|
109068
|
+
Object.defineProperty(timeline, "__hfScopedCompositionRoot", {
|
|
109069
|
+
value: __hfFindRoot(),
|
|
109070
|
+
configurable: true,
|
|
109071
|
+
});
|
|
109072
|
+
} catch (_err) {}
|
|
109073
|
+
return timeline;
|
|
109074
|
+
};
|
|
109075
|
+
var __hfBaseGsap = typeof gsap === "undefined" ? window.gsap : gsap;
|
|
109076
|
+
var __hfScopedGsap = !__hfBaseGsap || typeof Proxy !== "function"
|
|
109077
|
+
? __hfBaseGsap
|
|
109078
|
+
: new Proxy(__hfBaseGsap, {
|
|
109079
|
+
get: function(target, prop, receiver) {
|
|
109080
|
+
if (prop === "timeline") {
|
|
109081
|
+
return function() {
|
|
109082
|
+
return __hfScopeTimeline(target.timeline.apply(target, arguments));
|
|
109083
|
+
};
|
|
109084
|
+
}
|
|
109085
|
+
if (prop === "to" || prop === "from" || prop === "fromTo" || prop === "set") {
|
|
109086
|
+
return function(firstArg) {
|
|
109087
|
+
var args = Array.prototype.slice.call(arguments);
|
|
109088
|
+
args[0] = __hfResolveGsapTarget(firstArg);
|
|
109089
|
+
return target[prop].apply(target, args);
|
|
109090
|
+
};
|
|
109091
|
+
}
|
|
109092
|
+
if (prop === "utils" && target.utils && typeof Proxy === "function") {
|
|
109093
|
+
return new Proxy(target.utils, {
|
|
109094
|
+
get: function(utilsTarget, utilsProp, utilsReceiver) {
|
|
109095
|
+
if (utilsProp === "toArray") {
|
|
109096
|
+
return function(firstArg) {
|
|
109097
|
+
var args = Array.prototype.slice.call(arguments);
|
|
109098
|
+
args[0] = __hfResolveGsapTarget(firstArg);
|
|
109099
|
+
return utilsTarget.toArray.apply(utilsTarget, args);
|
|
109100
|
+
};
|
|
109101
|
+
}
|
|
109102
|
+
if (utilsProp === "selector") {
|
|
109103
|
+
return function(base) {
|
|
109104
|
+
var baseEl = typeof base === "string" ? __hfQueryOne(base) : base;
|
|
109105
|
+
var root = baseEl || __hfFindRoot();
|
|
109106
|
+
return function(selector) {
|
|
109107
|
+
if (!root || typeof selector !== "string") return [];
|
|
109108
|
+
return Array.prototype.slice.call(root.querySelectorAll(selector));
|
|
109109
|
+
};
|
|
109110
|
+
};
|
|
109111
|
+
}
|
|
109112
|
+
var value = Reflect.get(utilsTarget, utilsProp, utilsReceiver);
|
|
109113
|
+
return typeof value === "function" ? value.bind(utilsTarget) : value;
|
|
109114
|
+
},
|
|
109115
|
+
});
|
|
109116
|
+
}
|
|
109117
|
+
var value = Reflect.get(target, prop, receiver);
|
|
109118
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
109119
|
+
},
|
|
109120
|
+
});
|
|
109121
|
+
var __hfRun = function() {
|
|
109122
|
+
try {
|
|
109123
|
+
(function(document, gsap) {
|
|
109124
|
+
${source2}
|
|
109125
|
+
}).call(window, __hfScopedDocument, __hfScopedGsap);
|
|
109126
|
+
} catch (_err) {
|
|
109127
|
+
console.error(__hfErrorLabel, __hfCompId, _err);
|
|
109128
|
+
}
|
|
109129
|
+
};
|
|
109130
|
+
__hfFindRoot();
|
|
109131
|
+
__hfRun();
|
|
109132
|
+
})()`;
|
|
109133
|
+
}
|
|
108827
109134
|
|
|
108828
109135
|
// src/utils/paths.ts
|
|
108829
109136
|
import { resolve as resolve9, basename as basename2, join as join13, relative as relative2, isAbsolute as isAbsolute4 } from "node:path";
|
|
@@ -109480,27 +109787,6 @@ function promoteCssImportsToLinkTags(html) {
|
|
|
109480
109787
|
}
|
|
109481
109788
|
return document2.toString();
|
|
109482
109789
|
}
|
|
109483
|
-
function scopeCssToComposition(css, compositionId) {
|
|
109484
|
-
const scope = `[data-composition-id="${compositionId}"]`;
|
|
109485
|
-
const globalAtRules = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
|
|
109486
|
-
const root = postcss.parse(css);
|
|
109487
|
-
root.walkRules((rule) => {
|
|
109488
|
-
let node = rule.parent;
|
|
109489
|
-
while (node) {
|
|
109490
|
-
if (node.type === "atrule" && globalAtRules.has(node.name.toLowerCase())) {
|
|
109491
|
-
return;
|
|
109492
|
-
}
|
|
109493
|
-
node = node.parent;
|
|
109494
|
-
}
|
|
109495
|
-
rule.selectors = rule.selectors.map((sel) => {
|
|
109496
|
-
if (!sel.trim()) return sel;
|
|
109497
|
-
if (/^(html|body|:root|\*)$/i.test(sel.trim())) return sel;
|
|
109498
|
-
if (sel.includes(`data-composition-id="${compositionId}"`)) return sel;
|
|
109499
|
-
return `${scope} ${sel}`;
|
|
109500
|
-
});
|
|
109501
|
-
});
|
|
109502
|
-
return root.toResult().css;
|
|
109503
|
-
}
|
|
109504
109790
|
function coalesceHeadStylesAndBodyScripts(html) {
|
|
109505
109791
|
const { document: document2 } = parseHTML(html);
|
|
109506
109792
|
const head = document2.querySelector("head");
|
|
@@ -109629,28 +109915,13 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
109629
109915
|
const content = (scriptEl.textContent || "").trim();
|
|
109630
109916
|
if (content) {
|
|
109631
109917
|
const scriptMountCompId = compId || inferredCompId || "";
|
|
109632
|
-
|
|
109633
|
-
|
|
109634
|
-
|
|
109635
|
-
|
|
109636
|
-
|
|
109637
|
-
|
|
109638
|
-
|
|
109639
|
-
console.error("[Compiler] Composition script failed", __compId, _err);
|
|
109640
|
-
}
|
|
109641
|
-
};
|
|
109642
|
-
if (!__compId) { __run(); return; }
|
|
109643
|
-
${COMPILER_MOUNT_BLOCK_START}
|
|
109644
|
-
var __selector = '[data-composition-id="' + (__compId + '').replace(/"/g, '\\\\"') + '"]';
|
|
109645
|
-
var __attempt = 0;
|
|
109646
|
-
var __tryRun = function() {
|
|
109647
|
-
if (document.querySelector(__selector)) { __run(); return; }
|
|
109648
|
-
if (++__attempt >= 8) { __run(); return; }
|
|
109649
|
-
requestAnimationFrame(__tryRun);
|
|
109650
|
-
};
|
|
109651
|
-
__tryRun();
|
|
109652
|
-
${COMPILER_MOUNT_BLOCK_END}
|
|
109653
|
-
})()`);
|
|
109918
|
+
collectedScripts.push(
|
|
109919
|
+
scriptMountCompId ? wrapScopedCompositionScript(
|
|
109920
|
+
content,
|
|
109921
|
+
scriptMountCompId,
|
|
109922
|
+
"[Compiler] Composition script failed"
|
|
109923
|
+
) : `(function(){ try { ${content} } catch (_err) { console.error("[Compiler] Composition script failed", _err); } })()`
|
|
109924
|
+
);
|
|
109654
109925
|
}
|
|
109655
109926
|
scriptEl.remove();
|
|
109656
109927
|
}
|
|
@@ -109667,23 +109938,12 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
109667
109938
|
if (innerW && !host.getAttribute("data-width")) host.setAttribute("data-width", innerW);
|
|
109668
109939
|
if (innerH && !host.getAttribute("data-height")) host.setAttribute("data-height", innerH);
|
|
109669
109940
|
innerRoot.querySelectorAll("style, script").forEach((el) => el.remove());
|
|
109670
|
-
|
|
109671
|
-
host.innerHTML = innerRoot.outerHTML || "";
|
|
109672
|
-
} else {
|
|
109673
|
-
host.innerHTML = innerRoot.innerHTML || "";
|
|
109674
|
-
}
|
|
109941
|
+
host.innerHTML = compId ? innerRoot.innerHTML || "" : innerRoot.outerHTML || "";
|
|
109675
109942
|
} else {
|
|
109676
109943
|
contentDoc.querySelectorAll("style, script").forEach((el) => el.remove());
|
|
109677
109944
|
host.innerHTML = contentDoc.toString();
|
|
109678
109945
|
}
|
|
109679
109946
|
host.removeAttribute("data-composition-src");
|
|
109680
|
-
const hostDataStart = host.getAttribute("data-start");
|
|
109681
|
-
if (hostDataStart != null) {
|
|
109682
|
-
const innerComp = host.querySelector("[data-composition-id]");
|
|
109683
|
-
if (innerComp && !innerComp.getAttribute("data-start")) {
|
|
109684
|
-
innerComp.setAttribute("data-start", hostDataStart);
|
|
109685
|
-
}
|
|
109686
|
-
}
|
|
109687
109947
|
const hostW = host.getAttribute("data-width");
|
|
109688
109948
|
const hostH = host.getAttribute("data-height");
|
|
109689
109949
|
if (hostW && hostH) {
|
|
@@ -110628,13 +110888,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
110628
110888
|
const outputFormat = job.config.format ?? "mp4";
|
|
110629
110889
|
const isWebm = outputFormat === "webm";
|
|
110630
110890
|
const isMov = outputFormat === "mov";
|
|
110631
|
-
const
|
|
110891
|
+
const isPngSequence = outputFormat === "png-sequence";
|
|
110892
|
+
const needsAlpha = isWebm || isMov || isPngSequence;
|
|
110632
110893
|
if (needsAlpha) {
|
|
110633
110894
|
cfg.forceScreenshot = true;
|
|
110634
110895
|
}
|
|
110635
110896
|
const enableChunkedEncode = cfg.enableChunkedEncode;
|
|
110636
110897
|
const chunkedEncodeSize = cfg.chunkSizeFrames;
|
|
110637
|
-
const enableStreamingEncode = cfg.enableStreamingEncode;
|
|
110898
|
+
const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
|
|
110638
110899
|
let peakRssBytes = 0;
|
|
110639
110900
|
let peakHeapUsedBytes = 0;
|
|
110640
110901
|
const sampleMemory = () => {
|
|
@@ -111022,7 +111283,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111022
111283
|
}
|
|
111023
111284
|
if (effectiveHdr && outputFormat !== "mp4") {
|
|
111024
111285
|
log.warn(
|
|
111025
|
-
`[Render] HDR source detected but format is ${outputFormat} \u2014 falling back to SDR. Use --format mp4 for HDR10 output.`
|
|
111286
|
+
`[Render] HDR source detected but format is "${outputFormat}" \u2014 falling back to SDR. HDR + alpha is not supported. Use --format mp4 for HDR10 output.`
|
|
111026
111287
|
);
|
|
111027
111288
|
effectiveHdr = void 0;
|
|
111028
111289
|
}
|
|
@@ -111077,13 +111338,19 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111077
111338
|
skipReadinessVideoIds: Array.from(nativeHdrVideoIds)
|
|
111078
111339
|
});
|
|
111079
111340
|
const workerCount = calculateOptimalWorkers(totalFrames, job.config.workers, cfg);
|
|
111080
|
-
const FORMAT_EXT = {
|
|
111341
|
+
const FORMAT_EXT = {
|
|
111342
|
+
mp4: ".mp4",
|
|
111343
|
+
webm: ".webm",
|
|
111344
|
+
mov: ".mov",
|
|
111345
|
+
"png-sequence": ""
|
|
111346
|
+
};
|
|
111081
111347
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
111082
111348
|
const videoOnlyPath = join16(workDir, `video-only${videoExt}`);
|
|
111083
111349
|
const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
|
|
111084
111350
|
const hasHdrContent = effectiveHdr && nativeHdrIds.size > 0;
|
|
111085
111351
|
const encoderHdr = hasHdrContent ? effectiveHdr : void 0;
|
|
111086
|
-
const
|
|
111352
|
+
const presetFormat = isPngSequence ? "mp4" : outputFormat;
|
|
111353
|
+
const preset = getEncoderPreset(job.config.quality, presetFormat, encoderHdr);
|
|
111087
111354
|
if (job.config.crf != null && job.config.videoBitrate) {
|
|
111088
111355
|
log.warn(
|
|
111089
111356
|
`[Render] Both crf=${job.config.crf} and videoBitrate=${job.config.videoBitrate} were set. These are mutually exclusive; honoring crf and ignoring videoBitrate. Set only one to silence this warning.`
|
|
@@ -111733,41 +112000,64 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111733
112000
|
}
|
|
111734
112001
|
}
|
|
111735
112002
|
perfStages.captureMs = Date.now() - stage4Start;
|
|
111736
|
-
|
|
111737
|
-
|
|
111738
|
-
|
|
111739
|
-
|
|
111740
|
-
|
|
111741
|
-
|
|
111742
|
-
|
|
111743
|
-
|
|
111744
|
-
|
|
111745
|
-
|
|
111746
|
-
|
|
111747
|
-
|
|
111748
|
-
|
|
111749
|
-
|
|
111750
|
-
|
|
111751
|
-
|
|
111752
|
-
|
|
111753
|
-
|
|
111754
|
-
|
|
111755
|
-
|
|
111756
|
-
|
|
111757
|
-
|
|
111758
|
-
|
|
111759
|
-
|
|
111760
|
-
|
|
111761
|
-
framePattern
|
|
111762
|
-
|
|
111763
|
-
|
|
111764
|
-
|
|
111765
|
-
|
|
111766
|
-
|
|
111767
|
-
|
|
111768
|
-
|
|
112003
|
+
if (isPngSequence) {
|
|
112004
|
+
const stage5Start = Date.now();
|
|
112005
|
+
updateJobStatus(job, "encoding", "Writing PNG sequence", 75, onProgress);
|
|
112006
|
+
if (!existsSync16(outputPath)) mkdirSync11(outputPath, { recursive: true });
|
|
112007
|
+
const captured = readdirSync8(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
112008
|
+
if (captured.length === 0) {
|
|
112009
|
+
throw new Error(
|
|
112010
|
+
`[Render] png-sequence output requested but no PNGs were captured to ${framesDir}`
|
|
112011
|
+
);
|
|
112012
|
+
}
|
|
112013
|
+
captured.forEach((name, i) => {
|
|
112014
|
+
const dst = join16(outputPath, `frame_${String(i + 1).padStart(6, "0")}.png`);
|
|
112015
|
+
copyFileSync2(join16(framesDir, name), dst);
|
|
112016
|
+
});
|
|
112017
|
+
if (hasAudio && existsSync16(audioOutputPath)) {
|
|
112018
|
+
copyFileSync2(audioOutputPath, join16(outputPath, "audio.aac"));
|
|
112019
|
+
log.info(
|
|
112020
|
+
`[Render] png-sequence: audio.aac sidecar written to ${outputPath}/audio.aac`
|
|
112021
|
+
);
|
|
112022
|
+
}
|
|
112023
|
+
perfStages.encodeMs = Date.now() - stage5Start;
|
|
112024
|
+
} else {
|
|
112025
|
+
const stage5Start = Date.now();
|
|
112026
|
+
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
112027
|
+
const frameExt = needsAlpha ? "png" : "jpg";
|
|
112028
|
+
const framePattern = `frame_%06d.${frameExt}`;
|
|
112029
|
+
const encoderOpts = {
|
|
112030
|
+
fps: job.config.fps,
|
|
112031
|
+
width,
|
|
112032
|
+
height,
|
|
112033
|
+
codec: preset.codec,
|
|
112034
|
+
preset: preset.preset,
|
|
112035
|
+
quality: effectiveQuality,
|
|
112036
|
+
bitrate: effectiveBitrate,
|
|
112037
|
+
pixelFormat: preset.pixelFormat,
|
|
112038
|
+
useGpu: job.config.useGpu,
|
|
112039
|
+
hdr: preset.hdr
|
|
112040
|
+
};
|
|
112041
|
+
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
112042
|
+
framesDir,
|
|
112043
|
+
framePattern,
|
|
112044
|
+
videoOnlyPath,
|
|
112045
|
+
encoderOpts,
|
|
112046
|
+
chunkedEncodeSize,
|
|
112047
|
+
abortSignal
|
|
112048
|
+
) : await encodeFramesFromDir(
|
|
112049
|
+
framesDir,
|
|
112050
|
+
framePattern,
|
|
112051
|
+
videoOnlyPath,
|
|
112052
|
+
encoderOpts,
|
|
112053
|
+
abortSignal
|
|
112054
|
+
);
|
|
112055
|
+
assertNotAborted();
|
|
112056
|
+
if (!encodeResult.success) {
|
|
112057
|
+
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
112058
|
+
}
|
|
112059
|
+
perfStages.encodeMs = Date.now() - stage5Start;
|
|
111769
112060
|
}
|
|
111770
|
-
perfStages.encodeMs = Date.now() - stage5Start;
|
|
111771
112061
|
}
|
|
111772
112062
|
} finally {
|
|
111773
112063
|
if (streamingEncoder && !streamingEncoderClosed) {
|
|
@@ -111790,27 +112080,29 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111790
112080
|
if (frameLookup) frameLookup.cleanup();
|
|
111791
112081
|
fileServer.close();
|
|
111792
112082
|
fileServer = null;
|
|
111793
|
-
|
|
111794
|
-
|
|
111795
|
-
|
|
111796
|
-
|
|
111797
|
-
|
|
111798
|
-
|
|
111799
|
-
|
|
111800
|
-
|
|
111801
|
-
|
|
111802
|
-
|
|
111803
|
-
|
|
111804
|
-
|
|
111805
|
-
|
|
111806
|
-
|
|
111807
|
-
|
|
111808
|
-
|
|
111809
|
-
|
|
111810
|
-
|
|
112083
|
+
if (!isPngSequence) {
|
|
112084
|
+
const stage6Start = Date.now();
|
|
112085
|
+
updateJobStatus(job, "assembling", "Assembling final video", 90, onProgress);
|
|
112086
|
+
if (hasAudio) {
|
|
112087
|
+
const muxResult = await muxVideoWithAudio(
|
|
112088
|
+
videoOnlyPath,
|
|
112089
|
+
audioOutputPath,
|
|
112090
|
+
outputPath,
|
|
112091
|
+
abortSignal
|
|
112092
|
+
);
|
|
112093
|
+
assertNotAborted();
|
|
112094
|
+
if (!muxResult.success) {
|
|
112095
|
+
throw new Error(`Audio muxing failed: ${muxResult.error}`);
|
|
112096
|
+
}
|
|
112097
|
+
} else {
|
|
112098
|
+
const faststartResult = await applyFaststart(videoOnlyPath, outputPath, abortSignal);
|
|
112099
|
+
assertNotAborted();
|
|
112100
|
+
if (!faststartResult.success) {
|
|
112101
|
+
throw new Error(`Faststart failed: ${faststartResult.error}`);
|
|
112102
|
+
}
|
|
111811
112103
|
}
|
|
112104
|
+
perfStages.assembleMs = Date.now() - stage6Start;
|
|
111812
112105
|
}
|
|
111813
|
-
perfStages.assembleMs = Date.now() - stage6Start;
|
|
111814
112106
|
job.outputPath = outputPath;
|
|
111815
112107
|
updateJobStatus(job, "complete", "Render complete", 100, onProgress);
|
|
111816
112108
|
const totalElapsed = Date.now() - pipelineStart;
|
|
@@ -111849,7 +112141,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111849
112141
|
}
|
|
111850
112142
|
}
|
|
111851
112143
|
if (job.config.debug) {
|
|
111852
|
-
if (existsSync16(outputPath)) {
|
|
112144
|
+
if (!isPngSequence && existsSync16(outputPath)) {
|
|
111853
112145
|
const debugOutput = join16(workDir, `output${videoExt}`);
|
|
111854
112146
|
copyFileSync2(outputPath, debugOutput);
|
|
111855
112147
|
}
|