@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/index.js
CHANGED
|
@@ -99576,12 +99576,28 @@ var coreRules = [
|
|
|
99576
99576
|
function escapeRegExp(value) {
|
|
99577
99577
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
99578
99578
|
}
|
|
99579
|
-
function
|
|
99579
|
+
function hasAttrName(tagSource, attr) {
|
|
99580
|
+
const escaped = escapeRegExp(attr);
|
|
99581
|
+
const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
|
|
99582
|
+
return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
|
|
99583
|
+
}
|
|
99584
|
+
function classNamesFromAttr(classAttr) {
|
|
99585
|
+
if (!classAttr) return [];
|
|
99586
|
+
return classAttr.split(/\s+/).filter(Boolean);
|
|
99587
|
+
}
|
|
99588
|
+
function selectorTargetsManagedMedia(selector, mediaIndex) {
|
|
99580
99589
|
const normalized = selector.trim();
|
|
99581
99590
|
if (!normalized) return false;
|
|
99582
|
-
if (/\
|
|
99583
|
-
|
|
99584
|
-
|
|
99591
|
+
if (mediaIndex.hasVideo && /\bvideo\b/i.test(normalized)) return true;
|
|
99592
|
+
if (mediaIndex.hasAudio && /\baudio\b/i.test(normalized)) return true;
|
|
99593
|
+
for (const mediaId of mediaIndex.ids) {
|
|
99594
|
+
const escapedId = escapeRegExp(mediaId);
|
|
99595
|
+
if (new RegExp(`#${escapedId}(?![\\w-])`).test(normalized) || normalized.includes(`[id="${mediaId}"]`) || normalized.includes(`[id='${mediaId}']`)) {
|
|
99596
|
+
return true;
|
|
99597
|
+
}
|
|
99598
|
+
}
|
|
99599
|
+
for (const className of mediaIndex.classes) {
|
|
99600
|
+
if (new RegExp(`\\.${escapeRegExp(className)}(?![\\w-])`).test(normalized)) {
|
|
99585
99601
|
return true;
|
|
99586
99602
|
}
|
|
99587
99603
|
}
|
|
@@ -99589,66 +99605,96 @@ function selectorTargetsManagedMedia(selector, mediaIds) {
|
|
|
99589
99605
|
}
|
|
99590
99606
|
function findImperativeMediaControlFindings(ctx) {
|
|
99591
99607
|
const findings = [];
|
|
99592
|
-
const
|
|
99593
|
-
|
|
99594
|
-
|
|
99595
|
-
|
|
99608
|
+
const mediaTags = ctx.tags.filter((tag) => tag.name === "video" || tag.name === "audio");
|
|
99609
|
+
const mediaIndex = {
|
|
99610
|
+
ids: new Set(
|
|
99611
|
+
mediaTags.map((tag) => readAttr(tag.raw, "id")).filter((id) => Boolean(id))
|
|
99612
|
+
),
|
|
99613
|
+
classes: new Set(mediaTags.flatMap((tag) => classNamesFromAttr(readAttr(tag.raw, "class")))),
|
|
99614
|
+
hasVideo: mediaTags.some((tag) => tag.name === "video"),
|
|
99615
|
+
hasAudio: mediaTags.some((tag) => tag.name === "audio")
|
|
99616
|
+
};
|
|
99617
|
+
if (mediaTags.length === 0 || ctx.scripts.length === 0) return findings;
|
|
99596
99618
|
for (const script of ctx.scripts) {
|
|
99597
99619
|
const mediaVars = /* @__PURE__ */ new Map();
|
|
99598
99620
|
const assignmentPatterns = [
|
|
99599
|
-
|
|
99600
|
-
|
|
99621
|
+
{
|
|
99622
|
+
pattern: /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)/g,
|
|
99623
|
+
variableIndex: 1,
|
|
99624
|
+
targetIndex: 2
|
|
99625
|
+
},
|
|
99626
|
+
{
|
|
99627
|
+
pattern: /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\2\s*\)/g,
|
|
99628
|
+
variableIndex: 1,
|
|
99629
|
+
targetIndex: 3
|
|
99630
|
+
}
|
|
99601
99631
|
];
|
|
99602
|
-
for (const pattern of assignmentPatterns) {
|
|
99632
|
+
for (const { pattern, variableIndex, targetIndex } of assignmentPatterns) {
|
|
99603
99633
|
let match2;
|
|
99604
99634
|
while ((match2 = pattern.exec(script.content)) !== null) {
|
|
99605
|
-
const variableName = match2[
|
|
99606
|
-
const target = match2[
|
|
99635
|
+
const variableName = match2[variableIndex];
|
|
99636
|
+
const target = match2[targetIndex];
|
|
99607
99637
|
if (!variableName || !target) continue;
|
|
99608
|
-
if (
|
|
99609
|
-
mediaVars.set(variableName,
|
|
99638
|
+
if (mediaIndex.ids.has(target) || selectorTargetsManagedMedia(target, mediaIndex)) {
|
|
99639
|
+
mediaVars.set(variableName, mediaIndex.ids.has(target) ? target : void 0);
|
|
99610
99640
|
}
|
|
99611
99641
|
}
|
|
99612
99642
|
}
|
|
99613
99643
|
const directIdPatterns = [
|
|
99614
99644
|
{
|
|
99615
99645
|
pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.play\s*\(/g,
|
|
99616
|
-
kind: "play()"
|
|
99646
|
+
kind: "play()",
|
|
99647
|
+
targetIndex: 1
|
|
99617
99648
|
},
|
|
99618
99649
|
{
|
|
99619
99650
|
pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.pause\s*\(/g,
|
|
99620
|
-
kind: "pause()"
|
|
99651
|
+
kind: "pause()",
|
|
99652
|
+
targetIndex: 1
|
|
99621
99653
|
},
|
|
99622
99654
|
{
|
|
99623
99655
|
pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.currentTime\s*=/g,
|
|
99624
|
-
kind: "currentTime"
|
|
99656
|
+
kind: "currentTime",
|
|
99657
|
+
targetIndex: 1
|
|
99658
|
+
},
|
|
99659
|
+
{
|
|
99660
|
+
pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.muted\s*=/g,
|
|
99661
|
+
kind: "muted assignment",
|
|
99662
|
+
targetIndex: 1
|
|
99625
99663
|
},
|
|
99626
99664
|
{
|
|
99627
|
-
pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([
|
|
99628
|
-
kind: "play()"
|
|
99665
|
+
pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.play\s*\(/g,
|
|
99666
|
+
kind: "play()",
|
|
99667
|
+
targetIndex: 2
|
|
99629
99668
|
},
|
|
99630
99669
|
{
|
|
99631
|
-
pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([
|
|
99632
|
-
kind: "pause()"
|
|
99670
|
+
pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.pause\s*\(/g,
|
|
99671
|
+
kind: "pause()",
|
|
99672
|
+
targetIndex: 2
|
|
99633
99673
|
},
|
|
99634
99674
|
{
|
|
99635
|
-
pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([
|
|
99636
|
-
kind: "currentTime"
|
|
99675
|
+
pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.currentTime\s*=/g,
|
|
99676
|
+
kind: "currentTime",
|
|
99677
|
+
targetIndex: 2
|
|
99678
|
+
},
|
|
99679
|
+
{
|
|
99680
|
+
pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.muted\s*=/g,
|
|
99681
|
+
kind: "muted assignment",
|
|
99682
|
+
targetIndex: 2
|
|
99637
99683
|
}
|
|
99638
99684
|
];
|
|
99639
|
-
for (const { pattern, kind } of directIdPatterns) {
|
|
99685
|
+
for (const { pattern, kind, targetIndex } of directIdPatterns) {
|
|
99640
99686
|
let match2;
|
|
99641
99687
|
while ((match2 = pattern.exec(script.content)) !== null) {
|
|
99642
|
-
const target = match2[
|
|
99688
|
+
const target = match2[targetIndex];
|
|
99643
99689
|
if (!target) continue;
|
|
99644
|
-
const elementId =
|
|
99690
|
+
const elementId = mediaIndex.ids.has(target) ? target : selectorTargetsManagedMedia(target, mediaIndex) ? void 0 : null;
|
|
99645
99691
|
if (elementId === null) continue;
|
|
99646
99692
|
findings.push({
|
|
99647
99693
|
code: "imperative_media_control",
|
|
99648
99694
|
severity: "error",
|
|
99649
99695
|
message: `Inline <script> imperatively controls managed media via ${kind}. HyperFrames must own media play/pause/seek to keep preview, timeline, and renders deterministic.`,
|
|
99650
99696
|
elementId: elementId || void 0,
|
|
99651
|
-
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.",
|
|
99697
|
+
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.",
|
|
99652
99698
|
snippet: truncateSnippet(match2[0])
|
|
99653
99699
|
});
|
|
99654
99700
|
}
|
|
@@ -99658,7 +99704,11 @@ function findImperativeMediaControlFindings(ctx) {
|
|
|
99658
99704
|
const variablePatterns = [
|
|
99659
99705
|
{ pattern: new RegExp(`\\b${escapedVar}\\.play\\s*\\(`, "g"), kind: "play()" },
|
|
99660
99706
|
{ pattern: new RegExp(`\\b${escapedVar}\\.pause\\s*\\(`, "g"), kind: "pause()" },
|
|
99661
|
-
{ pattern: new RegExp(`\\b${escapedVar}\\.currentTime\\s*=`, "g"), kind: "currentTime" }
|
|
99707
|
+
{ pattern: new RegExp(`\\b${escapedVar}\\.currentTime\\s*=`, "g"), kind: "currentTime" },
|
|
99708
|
+
{
|
|
99709
|
+
pattern: new RegExp(`\\b${escapedVar}\\.muted\\s*=`, "g"),
|
|
99710
|
+
kind: "muted assignment"
|
|
99711
|
+
}
|
|
99662
99712
|
];
|
|
99663
99713
|
for (const { pattern, kind } of variablePatterns) {
|
|
99664
99714
|
let match2;
|
|
@@ -99668,7 +99718,7 @@ function findImperativeMediaControlFindings(ctx) {
|
|
|
99668
99718
|
severity: "error",
|
|
99669
99719
|
message: `Inline <script> imperatively controls managed media via ${kind}. HyperFrames must own media play/pause/seek to keep preview, timeline, and renders deterministic.`,
|
|
99670
99720
|
elementId,
|
|
99671
|
-
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.",
|
|
99721
|
+
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.",
|
|
99672
99722
|
snippet: truncateSnippet(match2[0])
|
|
99673
99723
|
});
|
|
99674
99724
|
}
|
|
@@ -99730,15 +99780,27 @@ var mediaRules = [
|
|
|
99730
99780
|
const findings = [];
|
|
99731
99781
|
for (const tag of tags) {
|
|
99732
99782
|
if (tag.name !== "video") continue;
|
|
99733
|
-
const hasMuted =
|
|
99734
|
-
|
|
99783
|
+
const hasMuted = hasAttrName(tag.raw, "muted");
|
|
99784
|
+
const hasDeclaredAudio = readAttr(tag.raw, "data-has-audio") === "true";
|
|
99785
|
+
if (!hasMuted && !hasDeclaredAudio && readAttr(tag.raw, "data-start")) {
|
|
99735
99786
|
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
99736
99787
|
findings.push({
|
|
99737
99788
|
code: "video_missing_muted",
|
|
99738
99789
|
severity: "error",
|
|
99739
|
-
message: `<video${elementId ? ` id="${elementId}"` : ""}> has data-start but is not muted.
|
|
99790
|
+
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.`,
|
|
99791
|
+
elementId,
|
|
99792
|
+
fixHint: 'Add the `muted` attribute for silent video, or add data-has-audio="true" when the video track should contribute audio.',
|
|
99793
|
+
snippet: truncateSnippet(tag.raw)
|
|
99794
|
+
});
|
|
99795
|
+
}
|
|
99796
|
+
if (hasMuted && hasDeclaredAudio) {
|
|
99797
|
+
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
99798
|
+
findings.push({
|
|
99799
|
+
code: "video_muted_with_declared_audio",
|
|
99800
|
+
severity: "error",
|
|
99801
|
+
message: `<video${elementId ? ` id="${elementId}"` : ""}> declares data-has-audio="true" but also has muted. Studio preview will silence the video audio.`,
|
|
99740
99802
|
elementId,
|
|
99741
|
-
fixHint:
|
|
99803
|
+
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.',
|
|
99742
99804
|
snippet: truncateSnippet(tag.raw)
|
|
99743
99805
|
});
|
|
99744
99806
|
}
|
|
@@ -99860,7 +99922,7 @@ var mediaRules = [
|
|
|
99860
99922
|
}
|
|
99861
99923
|
return findings;
|
|
99862
99924
|
},
|
|
99863
|
-
// media_missing_id + media_missing_src + media_preload_none
|
|
99925
|
+
// media_missing_data_start + media_missing_id + media_missing_src + media_preload_none
|
|
99864
99926
|
({ tags }) => {
|
|
99865
99927
|
const findings = [];
|
|
99866
99928
|
for (const tag of tags) {
|
|
@@ -99868,6 +99930,16 @@ var mediaRules = [
|
|
|
99868
99930
|
const hasDataStart = readAttr(tag.raw, "data-start");
|
|
99869
99931
|
const hasId = readAttr(tag.raw, "id");
|
|
99870
99932
|
const hasSrc = readAttr(tag.raw, "src");
|
|
99933
|
+
if (hasSrc && !hasDataStart) {
|
|
99934
|
+
findings.push({
|
|
99935
|
+
code: "media_missing_data_start",
|
|
99936
|
+
severity: "error",
|
|
99937
|
+
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.`,
|
|
99938
|
+
elementId: hasId || void 0,
|
|
99939
|
+
fixHint: `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
|
|
99940
|
+
snippet: truncateSnippet(tag.raw)
|
|
99941
|
+
});
|
|
99942
|
+
}
|
|
99871
99943
|
if (hasDataStart && !hasId) {
|
|
99872
99944
|
findings.push({
|
|
99873
99945
|
code: "media_missing_id",
|
|
@@ -100724,6 +100796,31 @@ var compositionRules = [
|
|
|
100724
100796
|
}
|
|
100725
100797
|
return findings;
|
|
100726
100798
|
},
|
|
100799
|
+
// split_data_attribute_selector
|
|
100800
|
+
({ scripts, styles }) => {
|
|
100801
|
+
const findings = [];
|
|
100802
|
+
const splitDataAttrSelectorPattern = /\[data-composition-id=(["'])([^"'\]]+)\1\s+(data-[\w:-]+)=(["'])([^"'\]]*)\4\]/g;
|
|
100803
|
+
const scan = (content) => {
|
|
100804
|
+
splitDataAttrSelectorPattern.lastIndex = 0;
|
|
100805
|
+
let match2;
|
|
100806
|
+
while ((match2 = splitDataAttrSelectorPattern.exec(content)) !== null) {
|
|
100807
|
+
const compId = match2[2] ?? "";
|
|
100808
|
+
const attrName = match2[3] ?? "";
|
|
100809
|
+
const attrValue = match2[5] ?? "";
|
|
100810
|
+
findings.push({
|
|
100811
|
+
code: "split_data_attribute_selector",
|
|
100812
|
+
severity: "error",
|
|
100813
|
+
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.`,
|
|
100814
|
+
selector: match2[0],
|
|
100815
|
+
fixHint: `Use separate attribute selectors: [data-composition-id="${compId}"][${attrName}="${attrValue}"].`,
|
|
100816
|
+
snippet: truncateSnippet(match2[0])
|
|
100817
|
+
});
|
|
100818
|
+
}
|
|
100819
|
+
};
|
|
100820
|
+
for (const style of styles) scan(style.content);
|
|
100821
|
+
for (const script of scripts) scan(script.content);
|
|
100822
|
+
return findings;
|
|
100823
|
+
},
|
|
100727
100824
|
// template_literal_selector
|
|
100728
100825
|
({ scripts }) => {
|
|
100729
100826
|
const findings = [];
|
|
@@ -101137,13 +101234,13 @@ async function beginFrameCapture(page, options, frameTimeTicks, interval) {
|
|
|
101137
101234
|
}
|
|
101138
101235
|
async function pageScreenshotCapture(page, options) {
|
|
101139
101236
|
const client = await getCdpSession(page);
|
|
101140
|
-
const
|
|
101237
|
+
const isPng = options.format === "png";
|
|
101141
101238
|
const result = await client.send("Page.captureScreenshot", {
|
|
101142
|
-
format:
|
|
101143
|
-
quality:
|
|
101239
|
+
format: isPng ? "png" : "jpeg",
|
|
101240
|
+
quality: isPng ? void 0 : options.quality ?? 80,
|
|
101144
101241
|
fromSurface: true,
|
|
101145
101242
|
captureBeyondViewport: false,
|
|
101146
|
-
optimizeForSpeed:
|
|
101243
|
+
optimizeForSpeed: !isPng
|
|
101147
101244
|
});
|
|
101148
101245
|
return Buffer.from(result.data, "base64");
|
|
101149
101246
|
}
|
|
@@ -101356,12 +101453,6 @@ async function createCaptureSession(serverUrl, outputDir, options, onBeforeCaptu
|
|
|
101356
101453
|
deviceScaleFactor: options.deviceScaleFactor || 1
|
|
101357
101454
|
};
|
|
101358
101455
|
await page.setViewport(viewport);
|
|
101359
|
-
if (options.format === "png") {
|
|
101360
|
-
const cdp = await getCdpSession(page);
|
|
101361
|
-
await cdp.send("Emulation.setDefaultBackgroundColorOverride", {
|
|
101362
|
-
color: { r: 0, g: 0, b: 0, a: 0 }
|
|
101363
|
-
});
|
|
101364
|
-
}
|
|
101365
101456
|
return {
|
|
101366
101457
|
browser,
|
|
101367
101458
|
page,
|
|
@@ -101457,6 +101548,9 @@ async function initializeSession(session) {
|
|
|
101457
101548
|
);
|
|
101458
101549
|
}
|
|
101459
101550
|
await page.evaluate(`document.fonts?.ready`);
|
|
101551
|
+
if (session.options.format === "png") {
|
|
101552
|
+
await initTransparentBackground(session.page);
|
|
101553
|
+
}
|
|
101460
101554
|
session.isInitialized = true;
|
|
101461
101555
|
return;
|
|
101462
101556
|
}
|
|
@@ -101520,6 +101614,9 @@ async function initializeSession(session) {
|
|
|
101520
101614
|
await page.evaluate(`document.fonts?.ready`);
|
|
101521
101615
|
warmupRunning = false;
|
|
101522
101616
|
session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
|
|
101617
|
+
if (session.options.format === "png") {
|
|
101618
|
+
await initTransparentBackground(session.page);
|
|
101619
|
+
}
|
|
101523
101620
|
session.isInitialized = true;
|
|
101524
101621
|
}
|
|
101525
101622
|
async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
|
|
@@ -108658,7 +108755,217 @@ function createFileServer2(options) {
|
|
|
108658
108755
|
// src/services/htmlCompiler.ts
|
|
108659
108756
|
import { readFileSync as readFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
|
|
108660
108757
|
import { join as join15, dirname as dirname9, resolve as resolve10 } from "path";
|
|
108758
|
+
|
|
108759
|
+
// ../core/src/compiler/htmlBundler.ts
|
|
108760
|
+
import { transformSync } from "esbuild";
|
|
108761
|
+
|
|
108762
|
+
// ../core/src/compiler/compositionScoping.ts
|
|
108661
108763
|
import postcss from "postcss";
|
|
108764
|
+
function escapeRegExp2(value) {
|
|
108765
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
108766
|
+
}
|
|
108767
|
+
function escapeCssAttributeValue(value) {
|
|
108768
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
108769
|
+
}
|
|
108770
|
+
function scopeSelector(selector, scope, compositionId) {
|
|
108771
|
+
const selectorWithoutRootTiming = normalizeCompositionRootSelector(
|
|
108772
|
+
selector,
|
|
108773
|
+
scope,
|
|
108774
|
+
compositionId
|
|
108775
|
+
);
|
|
108776
|
+
const trimmed = selectorWithoutRootTiming.trim();
|
|
108777
|
+
if (!trimmed) return selector;
|
|
108778
|
+
if (/^(html|body|:root|\*)$/i.test(trimmed)) return selector;
|
|
108779
|
+
const compositionIdPattern = new RegExp(
|
|
108780
|
+
`data-composition-id\\s*=\\s*(["'])${escapeRegExp2(compositionId)}\\1`
|
|
108781
|
+
);
|
|
108782
|
+
if (compositionIdPattern.test(trimmed)) return selectorWithoutRootTiming;
|
|
108783
|
+
const leading = selectorWithoutRootTiming.match(/^\s*/)?.[0] ?? "";
|
|
108784
|
+
const trailing = selectorWithoutRootTiming.match(/\s*$/)?.[0] ?? "";
|
|
108785
|
+
return `${leading}${scope} ${trimmed}${trailing}`;
|
|
108786
|
+
}
|
|
108787
|
+
function normalizeCompositionRootSelector(selector, scope, compositionId) {
|
|
108788
|
+
const quotedCompId = escapeRegExp2(compositionId);
|
|
108789
|
+
const compAttr = String.raw`\[\s*data-composition-id\s*=\s*(?:"${quotedCompId}"|'${quotedCompId}')\s*\]`;
|
|
108790
|
+
const timingAttr = String.raw`\s*\[\s*data-(?:start|duration)\s*=\s*(?:"[^"]*"|'[^']*')\s*\]`;
|
|
108791
|
+
return selector.replace(new RegExp(`${compAttr}(?:${timingAttr})+`, "g"), scope).replace(new RegExp(`(?:${timingAttr})+${compAttr}`, "g"), scope);
|
|
108792
|
+
}
|
|
108793
|
+
var GLOBAL_AT_RULES = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
|
|
108794
|
+
function isAtRuleNode(node) {
|
|
108795
|
+
return node?.type === "atrule";
|
|
108796
|
+
}
|
|
108797
|
+
function isInsideGlobalAtRule(rule) {
|
|
108798
|
+
let current = rule.parent;
|
|
108799
|
+
while (current) {
|
|
108800
|
+
if (isAtRuleNode(current) && GLOBAL_AT_RULES.has(current.name.toLowerCase())) {
|
|
108801
|
+
return true;
|
|
108802
|
+
}
|
|
108803
|
+
current = current.parent;
|
|
108804
|
+
}
|
|
108805
|
+
return false;
|
|
108806
|
+
}
|
|
108807
|
+
function scopeCssToComposition(css, compositionId) {
|
|
108808
|
+
const trimmedCompositionId = compositionId.trim();
|
|
108809
|
+
if (!css || !trimmedCompositionId) return css;
|
|
108810
|
+
const scope = `[data-composition-id="${escapeCssAttributeValue(trimmedCompositionId)}"]`;
|
|
108811
|
+
const root = postcss.parse(css);
|
|
108812
|
+
root.walkRules((rule) => {
|
|
108813
|
+
if (isInsideGlobalAtRule(rule)) return;
|
|
108814
|
+
rule.selectors = rule.selectors.map(
|
|
108815
|
+
(selector) => scopeSelector(selector, scope, trimmedCompositionId)
|
|
108816
|
+
);
|
|
108817
|
+
});
|
|
108818
|
+
return root.toResult({ map: false }).css;
|
|
108819
|
+
}
|
|
108820
|
+
function wrapScopedCompositionScript(source2, compositionId, errorLabel = "[HyperFrames] composition script error:") {
|
|
108821
|
+
const compositionIdLiteral = JSON.stringify(compositionId);
|
|
108822
|
+
const errorLabelLiteral = JSON.stringify(errorLabel);
|
|
108823
|
+
const escapedCompositionId = escapeRegExp2(compositionId);
|
|
108824
|
+
const rootSelectorPatternLiteral = JSON.stringify(
|
|
108825
|
+
String.raw`\[\s*data-composition-id\s*=\s*(?:"${escapedCompositionId}"|'${escapedCompositionId}')\s*\]`
|
|
108826
|
+
);
|
|
108827
|
+
const timingSelectorPatternLiteral = JSON.stringify(
|
|
108828
|
+
String.raw`\s*\[\s*data-(?:start|duration)\s*=\s*(?:"[^"]*"|'[^']*')\s*\]`
|
|
108829
|
+
);
|
|
108830
|
+
return `(function(){
|
|
108831
|
+
var __hfCompId = ${compositionIdLiteral};
|
|
108832
|
+
var __hfErrorLabel = ${errorLabelLiteral};
|
|
108833
|
+
var __hfEscapeAttr = function(value) {
|
|
108834
|
+
return (value + "").replace(/\\\\/g, "\\\\\\\\").replace(/"/g, "\\\\\\"");
|
|
108835
|
+
};
|
|
108836
|
+
var __hfRootSelector = __hfCompId
|
|
108837
|
+
? '[data-composition-id="' + __hfEscapeAttr(__hfCompId) + '"]'
|
|
108838
|
+
: "";
|
|
108839
|
+
var __hfRoot = null;
|
|
108840
|
+
var __hfRootSelectorPattern = ${rootSelectorPatternLiteral};
|
|
108841
|
+
var __hfTimingSelectorPattern = ${timingSelectorPatternLiteral};
|
|
108842
|
+
var __hfNormalizeSelector = function(selector) {
|
|
108843
|
+
if (!__hfCompId || typeof selector !== "string") return selector;
|
|
108844
|
+
return selector
|
|
108845
|
+
.replace(new RegExp(__hfRootSelectorPattern + '(?:' + __hfTimingSelectorPattern + ')+', 'g'), __hfRootSelector)
|
|
108846
|
+
.replace(new RegExp('(?:' + __hfTimingSelectorPattern + ')+' + __hfRootSelectorPattern, 'g'), __hfRootSelector);
|
|
108847
|
+
};
|
|
108848
|
+
var __hfFindRoot = function() {
|
|
108849
|
+
if (!__hfRoot && __hfRootSelector) {
|
|
108850
|
+
__hfRoot = window.document.querySelector(__hfRootSelector);
|
|
108851
|
+
}
|
|
108852
|
+
return __hfRoot;
|
|
108853
|
+
};
|
|
108854
|
+
var __hfContains = function(node) {
|
|
108855
|
+
var root = __hfFindRoot();
|
|
108856
|
+
return !root || node === root || root.contains(node);
|
|
108857
|
+
};
|
|
108858
|
+
var __hfQueryAll = function(selector) {
|
|
108859
|
+
var root = __hfFindRoot();
|
|
108860
|
+
if (!root || typeof selector !== "string") {
|
|
108861
|
+
return window.document.querySelectorAll(selector);
|
|
108862
|
+
}
|
|
108863
|
+
return Array.prototype.filter.call(window.document.querySelectorAll(__hfNormalizeSelector(selector)), function(node) {
|
|
108864
|
+
return __hfContains(node);
|
|
108865
|
+
});
|
|
108866
|
+
};
|
|
108867
|
+
var __hfQueryOne = function(selector) {
|
|
108868
|
+
var matches = __hfQueryAll(selector);
|
|
108869
|
+
return matches[0] || null;
|
|
108870
|
+
};
|
|
108871
|
+
var __hfScopedDocument = typeof Proxy === "function"
|
|
108872
|
+
? new Proxy(window.document, {
|
|
108873
|
+
get: function(target, prop, receiver) {
|
|
108874
|
+
if (prop === "querySelector") return __hfQueryOne;
|
|
108875
|
+
if (prop === "querySelectorAll") return __hfQueryAll;
|
|
108876
|
+
if (prop === "getElementById") {
|
|
108877
|
+
return function(id) {
|
|
108878
|
+
var found = target.getElementById(id);
|
|
108879
|
+
return found && __hfContains(found) ? found : null;
|
|
108880
|
+
};
|
|
108881
|
+
}
|
|
108882
|
+
var value = Reflect.get(target, prop, receiver);
|
|
108883
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
108884
|
+
},
|
|
108885
|
+
})
|
|
108886
|
+
: window.document;
|
|
108887
|
+
var __hfResolveGsapTarget = function(target) {
|
|
108888
|
+
if (typeof target !== "string") return target;
|
|
108889
|
+
return __hfQueryAll(target);
|
|
108890
|
+
};
|
|
108891
|
+
var __hfScopeTimeline = function(timeline) {
|
|
108892
|
+
if (!timeline || timeline.__hfScopedCompositionRoot === __hfFindRoot()) return timeline;
|
|
108893
|
+
["to", "from", "fromTo", "set"].forEach(function(method) {
|
|
108894
|
+
var original = timeline[method];
|
|
108895
|
+
if (typeof original !== "function") return;
|
|
108896
|
+
timeline[method] = function(target) {
|
|
108897
|
+
var args = Array.prototype.slice.call(arguments);
|
|
108898
|
+
args[0] = __hfResolveGsapTarget(target);
|
|
108899
|
+
return original.apply(timeline, args);
|
|
108900
|
+
};
|
|
108901
|
+
});
|
|
108902
|
+
try {
|
|
108903
|
+
Object.defineProperty(timeline, "__hfScopedCompositionRoot", {
|
|
108904
|
+
value: __hfFindRoot(),
|
|
108905
|
+
configurable: true,
|
|
108906
|
+
});
|
|
108907
|
+
} catch (_err) {}
|
|
108908
|
+
return timeline;
|
|
108909
|
+
};
|
|
108910
|
+
var __hfBaseGsap = typeof gsap === "undefined" ? window.gsap : gsap;
|
|
108911
|
+
var __hfScopedGsap = !__hfBaseGsap || typeof Proxy !== "function"
|
|
108912
|
+
? __hfBaseGsap
|
|
108913
|
+
: new Proxy(__hfBaseGsap, {
|
|
108914
|
+
get: function(target, prop, receiver) {
|
|
108915
|
+
if (prop === "timeline") {
|
|
108916
|
+
return function() {
|
|
108917
|
+
return __hfScopeTimeline(target.timeline.apply(target, arguments));
|
|
108918
|
+
};
|
|
108919
|
+
}
|
|
108920
|
+
if (prop === "to" || prop === "from" || prop === "fromTo" || prop === "set") {
|
|
108921
|
+
return function(firstArg) {
|
|
108922
|
+
var args = Array.prototype.slice.call(arguments);
|
|
108923
|
+
args[0] = __hfResolveGsapTarget(firstArg);
|
|
108924
|
+
return target[prop].apply(target, args);
|
|
108925
|
+
};
|
|
108926
|
+
}
|
|
108927
|
+
if (prop === "utils" && target.utils && typeof Proxy === "function") {
|
|
108928
|
+
return new Proxy(target.utils, {
|
|
108929
|
+
get: function(utilsTarget, utilsProp, utilsReceiver) {
|
|
108930
|
+
if (utilsProp === "toArray") {
|
|
108931
|
+
return function(firstArg) {
|
|
108932
|
+
var args = Array.prototype.slice.call(arguments);
|
|
108933
|
+
args[0] = __hfResolveGsapTarget(firstArg);
|
|
108934
|
+
return utilsTarget.toArray.apply(utilsTarget, args);
|
|
108935
|
+
};
|
|
108936
|
+
}
|
|
108937
|
+
if (utilsProp === "selector") {
|
|
108938
|
+
return function(base) {
|
|
108939
|
+
var baseEl = typeof base === "string" ? __hfQueryOne(base) : base;
|
|
108940
|
+
var root = baseEl || __hfFindRoot();
|
|
108941
|
+
return function(selector) {
|
|
108942
|
+
if (!root || typeof selector !== "string") return [];
|
|
108943
|
+
return Array.prototype.slice.call(root.querySelectorAll(selector));
|
|
108944
|
+
};
|
|
108945
|
+
};
|
|
108946
|
+
}
|
|
108947
|
+
var value = Reflect.get(utilsTarget, utilsProp, utilsReceiver);
|
|
108948
|
+
return typeof value === "function" ? value.bind(utilsTarget) : value;
|
|
108949
|
+
},
|
|
108950
|
+
});
|
|
108951
|
+
}
|
|
108952
|
+
var value = Reflect.get(target, prop, receiver);
|
|
108953
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
108954
|
+
},
|
|
108955
|
+
});
|
|
108956
|
+
var __hfRun = function() {
|
|
108957
|
+
try {
|
|
108958
|
+
(function(document, gsap) {
|
|
108959
|
+
${source2}
|
|
108960
|
+
}).call(window, __hfScopedDocument, __hfScopedGsap);
|
|
108961
|
+
} catch (_err) {
|
|
108962
|
+
console.error(__hfErrorLabel, __hfCompId, _err);
|
|
108963
|
+
}
|
|
108964
|
+
};
|
|
108965
|
+
__hfFindRoot();
|
|
108966
|
+
__hfRun();
|
|
108967
|
+
})()`;
|
|
108968
|
+
}
|
|
108662
108969
|
|
|
108663
108970
|
// src/utils/paths.ts
|
|
108664
108971
|
import { resolve as resolve9, basename as basename2, join as join13, relative as relative2, isAbsolute as isAbsolute4 } from "node:path";
|
|
@@ -109315,27 +109622,6 @@ function promoteCssImportsToLinkTags(html) {
|
|
|
109315
109622
|
}
|
|
109316
109623
|
return document2.toString();
|
|
109317
109624
|
}
|
|
109318
|
-
function scopeCssToComposition(css, compositionId) {
|
|
109319
|
-
const scope = `[data-composition-id="${compositionId}"]`;
|
|
109320
|
-
const globalAtRules = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
|
|
109321
|
-
const root = postcss.parse(css);
|
|
109322
|
-
root.walkRules((rule) => {
|
|
109323
|
-
let node = rule.parent;
|
|
109324
|
-
while (node) {
|
|
109325
|
-
if (node.type === "atrule" && globalAtRules.has(node.name.toLowerCase())) {
|
|
109326
|
-
return;
|
|
109327
|
-
}
|
|
109328
|
-
node = node.parent;
|
|
109329
|
-
}
|
|
109330
|
-
rule.selectors = rule.selectors.map((sel) => {
|
|
109331
|
-
if (!sel.trim()) return sel;
|
|
109332
|
-
if (/^(html|body|:root|\*)$/i.test(sel.trim())) return sel;
|
|
109333
|
-
if (sel.includes(`data-composition-id="${compositionId}"`)) return sel;
|
|
109334
|
-
return `${scope} ${sel}`;
|
|
109335
|
-
});
|
|
109336
|
-
});
|
|
109337
|
-
return root.toResult().css;
|
|
109338
|
-
}
|
|
109339
109625
|
function coalesceHeadStylesAndBodyScripts(html) {
|
|
109340
109626
|
const { document: document2 } = parseHTML(html);
|
|
109341
109627
|
const head = document2.querySelector("head");
|
|
@@ -109464,28 +109750,13 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
109464
109750
|
const content = (scriptEl.textContent || "").trim();
|
|
109465
109751
|
if (content) {
|
|
109466
109752
|
const scriptMountCompId = compId || inferredCompId || "";
|
|
109467
|
-
|
|
109468
|
-
|
|
109469
|
-
|
|
109470
|
-
|
|
109471
|
-
|
|
109472
|
-
|
|
109473
|
-
|
|
109474
|
-
console.error("[Compiler] Composition script failed", __compId, _err);
|
|
109475
|
-
}
|
|
109476
|
-
};
|
|
109477
|
-
if (!__compId) { __run(); return; }
|
|
109478
|
-
${COMPILER_MOUNT_BLOCK_START}
|
|
109479
|
-
var __selector = '[data-composition-id="' + (__compId + '').replace(/"/g, '\\\\"') + '"]';
|
|
109480
|
-
var __attempt = 0;
|
|
109481
|
-
var __tryRun = function() {
|
|
109482
|
-
if (document.querySelector(__selector)) { __run(); return; }
|
|
109483
|
-
if (++__attempt >= 8) { __run(); return; }
|
|
109484
|
-
requestAnimationFrame(__tryRun);
|
|
109485
|
-
};
|
|
109486
|
-
__tryRun();
|
|
109487
|
-
${COMPILER_MOUNT_BLOCK_END}
|
|
109488
|
-
})()`);
|
|
109753
|
+
collectedScripts.push(
|
|
109754
|
+
scriptMountCompId ? wrapScopedCompositionScript(
|
|
109755
|
+
content,
|
|
109756
|
+
scriptMountCompId,
|
|
109757
|
+
"[Compiler] Composition script failed"
|
|
109758
|
+
) : `(function(){ try { ${content} } catch (_err) { console.error("[Compiler] Composition script failed", _err); } })()`
|
|
109759
|
+
);
|
|
109489
109760
|
}
|
|
109490
109761
|
scriptEl.remove();
|
|
109491
109762
|
}
|
|
@@ -109502,23 +109773,12 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
109502
109773
|
if (innerW && !host.getAttribute("data-width")) host.setAttribute("data-width", innerW);
|
|
109503
109774
|
if (innerH && !host.getAttribute("data-height")) host.setAttribute("data-height", innerH);
|
|
109504
109775
|
innerRoot.querySelectorAll("style, script").forEach((el) => el.remove());
|
|
109505
|
-
|
|
109506
|
-
host.innerHTML = innerRoot.outerHTML || "";
|
|
109507
|
-
} else {
|
|
109508
|
-
host.innerHTML = innerRoot.innerHTML || "";
|
|
109509
|
-
}
|
|
109776
|
+
host.innerHTML = compId ? innerRoot.innerHTML || "" : innerRoot.outerHTML || "";
|
|
109510
109777
|
} else {
|
|
109511
109778
|
contentDoc.querySelectorAll("style, script").forEach((el) => el.remove());
|
|
109512
109779
|
host.innerHTML = contentDoc.toString();
|
|
109513
109780
|
}
|
|
109514
109781
|
host.removeAttribute("data-composition-src");
|
|
109515
|
-
const hostDataStart = host.getAttribute("data-start");
|
|
109516
|
-
if (hostDataStart != null) {
|
|
109517
|
-
const innerComp = host.querySelector("[data-composition-id]");
|
|
109518
|
-
if (innerComp && !innerComp.getAttribute("data-start")) {
|
|
109519
|
-
innerComp.setAttribute("data-start", hostDataStart);
|
|
109520
|
-
}
|
|
109521
|
-
}
|
|
109522
109782
|
const hostW = host.getAttribute("data-width");
|
|
109523
109783
|
const hostH = host.getAttribute("data-height");
|
|
109524
109784
|
if (hostW && hostH) {
|
|
@@ -110463,13 +110723,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
110463
110723
|
const outputFormat = job.config.format ?? "mp4";
|
|
110464
110724
|
const isWebm = outputFormat === "webm";
|
|
110465
110725
|
const isMov = outputFormat === "mov";
|
|
110466
|
-
const
|
|
110726
|
+
const isPngSequence = outputFormat === "png-sequence";
|
|
110727
|
+
const needsAlpha = isWebm || isMov || isPngSequence;
|
|
110467
110728
|
if (needsAlpha) {
|
|
110468
110729
|
cfg.forceScreenshot = true;
|
|
110469
110730
|
}
|
|
110470
110731
|
const enableChunkedEncode = cfg.enableChunkedEncode;
|
|
110471
110732
|
const chunkedEncodeSize = cfg.chunkSizeFrames;
|
|
110472
|
-
const enableStreamingEncode = cfg.enableStreamingEncode;
|
|
110733
|
+
const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
|
|
110473
110734
|
let peakRssBytes = 0;
|
|
110474
110735
|
let peakHeapUsedBytes = 0;
|
|
110475
110736
|
const sampleMemory = () => {
|
|
@@ -110857,7 +111118,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
110857
111118
|
}
|
|
110858
111119
|
if (effectiveHdr && outputFormat !== "mp4") {
|
|
110859
111120
|
log.warn(
|
|
110860
|
-
`[Render] HDR source detected but format is ${outputFormat} \u2014 falling back to SDR. Use --format mp4 for HDR10 output.`
|
|
111121
|
+
`[Render] HDR source detected but format is "${outputFormat}" \u2014 falling back to SDR. HDR + alpha is not supported. Use --format mp4 for HDR10 output.`
|
|
110861
111122
|
);
|
|
110862
111123
|
effectiveHdr = void 0;
|
|
110863
111124
|
}
|
|
@@ -110912,13 +111173,19 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
110912
111173
|
skipReadinessVideoIds: Array.from(nativeHdrVideoIds)
|
|
110913
111174
|
});
|
|
110914
111175
|
const workerCount = calculateOptimalWorkers(totalFrames, job.config.workers, cfg);
|
|
110915
|
-
const FORMAT_EXT = {
|
|
111176
|
+
const FORMAT_EXT = {
|
|
111177
|
+
mp4: ".mp4",
|
|
111178
|
+
webm: ".webm",
|
|
111179
|
+
mov: ".mov",
|
|
111180
|
+
"png-sequence": ""
|
|
111181
|
+
};
|
|
110916
111182
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
110917
111183
|
const videoOnlyPath = join16(workDir, `video-only${videoExt}`);
|
|
110918
111184
|
const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
|
|
110919
111185
|
const hasHdrContent = effectiveHdr && nativeHdrIds.size > 0;
|
|
110920
111186
|
const encoderHdr = hasHdrContent ? effectiveHdr : void 0;
|
|
110921
|
-
const
|
|
111187
|
+
const presetFormat = isPngSequence ? "mp4" : outputFormat;
|
|
111188
|
+
const preset = getEncoderPreset(job.config.quality, presetFormat, encoderHdr);
|
|
110922
111189
|
if (job.config.crf != null && job.config.videoBitrate) {
|
|
110923
111190
|
log.warn(
|
|
110924
111191
|
`[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.`
|
|
@@ -111568,41 +111835,64 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111568
111835
|
}
|
|
111569
111836
|
}
|
|
111570
111837
|
perfStages.captureMs = Date.now() - stage4Start;
|
|
111571
|
-
|
|
111572
|
-
|
|
111573
|
-
|
|
111574
|
-
|
|
111575
|
-
|
|
111576
|
-
|
|
111577
|
-
|
|
111578
|
-
|
|
111579
|
-
|
|
111580
|
-
|
|
111581
|
-
|
|
111582
|
-
|
|
111583
|
-
|
|
111584
|
-
|
|
111585
|
-
|
|
111586
|
-
|
|
111587
|
-
|
|
111588
|
-
|
|
111589
|
-
|
|
111590
|
-
|
|
111591
|
-
|
|
111592
|
-
|
|
111593
|
-
|
|
111594
|
-
|
|
111595
|
-
|
|
111596
|
-
framePattern
|
|
111597
|
-
|
|
111598
|
-
|
|
111599
|
-
|
|
111600
|
-
|
|
111601
|
-
|
|
111602
|
-
|
|
111603
|
-
|
|
111838
|
+
if (isPngSequence) {
|
|
111839
|
+
const stage5Start = Date.now();
|
|
111840
|
+
updateJobStatus(job, "encoding", "Writing PNG sequence", 75, onProgress);
|
|
111841
|
+
if (!existsSync16(outputPath)) mkdirSync11(outputPath, { recursive: true });
|
|
111842
|
+
const captured = readdirSync8(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
111843
|
+
if (captured.length === 0) {
|
|
111844
|
+
throw new Error(
|
|
111845
|
+
`[Render] png-sequence output requested but no PNGs were captured to ${framesDir}`
|
|
111846
|
+
);
|
|
111847
|
+
}
|
|
111848
|
+
captured.forEach((name, i) => {
|
|
111849
|
+
const dst = join16(outputPath, `frame_${String(i + 1).padStart(6, "0")}.png`);
|
|
111850
|
+
copyFileSync2(join16(framesDir, name), dst);
|
|
111851
|
+
});
|
|
111852
|
+
if (hasAudio && existsSync16(audioOutputPath)) {
|
|
111853
|
+
copyFileSync2(audioOutputPath, join16(outputPath, "audio.aac"));
|
|
111854
|
+
log.info(
|
|
111855
|
+
`[Render] png-sequence: audio.aac sidecar written to ${outputPath}/audio.aac`
|
|
111856
|
+
);
|
|
111857
|
+
}
|
|
111858
|
+
perfStages.encodeMs = Date.now() - stage5Start;
|
|
111859
|
+
} else {
|
|
111860
|
+
const stage5Start = Date.now();
|
|
111861
|
+
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
111862
|
+
const frameExt = needsAlpha ? "png" : "jpg";
|
|
111863
|
+
const framePattern = `frame_%06d.${frameExt}`;
|
|
111864
|
+
const encoderOpts = {
|
|
111865
|
+
fps: job.config.fps,
|
|
111866
|
+
width,
|
|
111867
|
+
height,
|
|
111868
|
+
codec: preset.codec,
|
|
111869
|
+
preset: preset.preset,
|
|
111870
|
+
quality: effectiveQuality,
|
|
111871
|
+
bitrate: effectiveBitrate,
|
|
111872
|
+
pixelFormat: preset.pixelFormat,
|
|
111873
|
+
useGpu: job.config.useGpu,
|
|
111874
|
+
hdr: preset.hdr
|
|
111875
|
+
};
|
|
111876
|
+
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
111877
|
+
framesDir,
|
|
111878
|
+
framePattern,
|
|
111879
|
+
videoOnlyPath,
|
|
111880
|
+
encoderOpts,
|
|
111881
|
+
chunkedEncodeSize,
|
|
111882
|
+
abortSignal
|
|
111883
|
+
) : await encodeFramesFromDir(
|
|
111884
|
+
framesDir,
|
|
111885
|
+
framePattern,
|
|
111886
|
+
videoOnlyPath,
|
|
111887
|
+
encoderOpts,
|
|
111888
|
+
abortSignal
|
|
111889
|
+
);
|
|
111890
|
+
assertNotAborted();
|
|
111891
|
+
if (!encodeResult.success) {
|
|
111892
|
+
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
111893
|
+
}
|
|
111894
|
+
perfStages.encodeMs = Date.now() - stage5Start;
|
|
111604
111895
|
}
|
|
111605
|
-
perfStages.encodeMs = Date.now() - stage5Start;
|
|
111606
111896
|
}
|
|
111607
111897
|
} finally {
|
|
111608
111898
|
if (streamingEncoder && !streamingEncoderClosed) {
|
|
@@ -111625,27 +111915,29 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111625
111915
|
if (frameLookup) frameLookup.cleanup();
|
|
111626
111916
|
fileServer.close();
|
|
111627
111917
|
fileServer = null;
|
|
111628
|
-
|
|
111629
|
-
|
|
111630
|
-
|
|
111631
|
-
|
|
111632
|
-
|
|
111633
|
-
|
|
111634
|
-
|
|
111635
|
-
|
|
111636
|
-
|
|
111637
|
-
|
|
111638
|
-
|
|
111639
|
-
|
|
111640
|
-
|
|
111641
|
-
|
|
111642
|
-
|
|
111643
|
-
|
|
111644
|
-
|
|
111645
|
-
|
|
111918
|
+
if (!isPngSequence) {
|
|
111919
|
+
const stage6Start = Date.now();
|
|
111920
|
+
updateJobStatus(job, "assembling", "Assembling final video", 90, onProgress);
|
|
111921
|
+
if (hasAudio) {
|
|
111922
|
+
const muxResult = await muxVideoWithAudio(
|
|
111923
|
+
videoOnlyPath,
|
|
111924
|
+
audioOutputPath,
|
|
111925
|
+
outputPath,
|
|
111926
|
+
abortSignal
|
|
111927
|
+
);
|
|
111928
|
+
assertNotAborted();
|
|
111929
|
+
if (!muxResult.success) {
|
|
111930
|
+
throw new Error(`Audio muxing failed: ${muxResult.error}`);
|
|
111931
|
+
}
|
|
111932
|
+
} else {
|
|
111933
|
+
const faststartResult = await applyFaststart(videoOnlyPath, outputPath, abortSignal);
|
|
111934
|
+
assertNotAborted();
|
|
111935
|
+
if (!faststartResult.success) {
|
|
111936
|
+
throw new Error(`Faststart failed: ${faststartResult.error}`);
|
|
111937
|
+
}
|
|
111646
111938
|
}
|
|
111939
|
+
perfStages.assembleMs = Date.now() - stage6Start;
|
|
111647
111940
|
}
|
|
111648
|
-
perfStages.assembleMs = Date.now() - stage6Start;
|
|
111649
111941
|
job.outputPath = outputPath;
|
|
111650
111942
|
updateJobStatus(job, "complete", "Render complete", 100, onProgress);
|
|
111651
111943
|
const totalElapsed = Date.now() - pipelineStart;
|
|
@@ -111684,7 +111976,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111684
111976
|
}
|
|
111685
111977
|
}
|
|
111686
111978
|
if (job.config.debug) {
|
|
111687
|
-
if (existsSync16(outputPath)) {
|
|
111979
|
+
if (!isPngSequence && existsSync16(outputPath)) {
|
|
111688
111980
|
const debugOutput = join16(workDir, `output${videoExt}`);
|
|
111689
111981
|
copyFileSync2(outputPath, debugOutput);
|
|
111690
111982
|
}
|