@hyperframes/producer 0.8.204 → 0.8.206
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 +762 -442
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +66 -64
- package/dist/index.d.ts +2 -0
- package/dist/index.js +835 -511
- package/dist/public-server.js +557 -235
- package/dist/services/animatedGifPrep.d.ts +6 -0
- package/dist/services/assetMediaType.d.ts +1 -0
- package/dist/services/heifImagePrep.d.ts +69 -0
- package/dist/services/heifRotation.d.ts +2 -0
- package/dist/services/htmlCompiler.d.ts +1 -4
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
3
3
|
import {
|
|
4
4
|
existsSync as existsSync16,
|
|
5
|
-
mkdirSync as
|
|
6
|
-
readFileSync as
|
|
5
|
+
mkdirSync as mkdirSync10,
|
|
6
|
+
readFileSync as readFileSync9,
|
|
7
7
|
readdirSync as readdirSync5,
|
|
8
|
-
rmSync as
|
|
9
|
-
statSync as
|
|
10
|
-
writeFileSync as
|
|
11
|
-
copyFileSync as
|
|
8
|
+
rmSync as rmSync12,
|
|
9
|
+
statSync as statSync8,
|
|
10
|
+
writeFileSync as writeFileSync6,
|
|
11
|
+
copyFileSync as copyFileSync5,
|
|
12
12
|
appendFileSync
|
|
13
13
|
} from "fs";
|
|
14
14
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
augmentProtocolTimeoutError,
|
|
57
57
|
augmentPageNavigationTimeoutError
|
|
58
58
|
} from "@hyperframes/engine";
|
|
59
|
-
import { join as
|
|
59
|
+
import { join as join27, dirname as dirname11, resolve as resolve8 } from "path";
|
|
60
60
|
import { totalmem as totalmem2 } from "node:os";
|
|
61
61
|
import { randomUUID } from "crypto";
|
|
62
62
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -80,6 +80,7 @@ import { Readable } from "node:stream";
|
|
|
80
80
|
import { join, extname, resolve as resolve2, sep } from "node:path";
|
|
81
81
|
import { injectScriptsAtHeadStart, injectScriptsIntoHtml } from "@hyperframes/core/compiler";
|
|
82
82
|
import { fpsToNumber } from "@hyperframes/core";
|
|
83
|
+
import { stripSdrPngColourTags } from "@hyperframes/core/compiler/png-colour-tags";
|
|
83
84
|
|
|
84
85
|
// src/services/hyperframeRuntimeLoader.ts
|
|
85
86
|
import { createHash } from "node:crypto";
|
|
@@ -196,11 +197,11 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
196
197
|
|
|
197
198
|
// src/services/fileServer.ts
|
|
198
199
|
var readFileAsync = promisify(readFile);
|
|
199
|
-
function isPathInside(
|
|
200
|
+
function isPathInside(child3, parent, options = {}) {
|
|
200
201
|
const { resolveSymlinks = false, pathModule } = options;
|
|
201
202
|
const resolveFn = pathModule?.resolve ?? resolve2;
|
|
202
203
|
const separator = pathModule?.sep ?? sep;
|
|
203
|
-
const resolvedChild = resolveFn(
|
|
204
|
+
const resolvedChild = resolveFn(child3);
|
|
204
205
|
const resolvedParent = resolveFn(parent);
|
|
205
206
|
const normalizedChild = resolveSymlinks && existsSync2(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
|
|
206
207
|
const normalizedParent = resolveSymlinks && existsSync2(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
|
|
@@ -720,6 +721,18 @@ function createFileServer(options) {
|
|
|
720
721
|
html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
|
|
721
722
|
return c.text(html, 200, { "Content-Type": contentType });
|
|
722
723
|
}
|
|
724
|
+
if (ext === ".png") {
|
|
725
|
+
const untagged = stripSdrPngColourTags(await readFileAsync(fd));
|
|
726
|
+
if (untagged) {
|
|
727
|
+
const headers = {
|
|
728
|
+
"Content-Type": contentType,
|
|
729
|
+
"Content-Length": String(untagged.length)
|
|
730
|
+
};
|
|
731
|
+
return new Response(c.req.method === "HEAD" ? null : new Uint8Array(untagged), {
|
|
732
|
+
headers
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
}
|
|
723
736
|
const totalSize = stat.size;
|
|
724
737
|
const rangeHeader = c.req.header("range");
|
|
725
738
|
const rangeRequest = parseRangeHeader(rangeHeader, totalSize);
|
|
@@ -747,7 +760,8 @@ function createFileServer(options) {
|
|
|
747
760
|
const stream = createReadStream(filePath, {
|
|
748
761
|
fd,
|
|
749
762
|
autoClose: true,
|
|
750
|
-
|
|
763
|
+
start: range?.start ?? 0,
|
|
764
|
+
...range ? { end: range.end } : {}
|
|
751
765
|
});
|
|
752
766
|
streamOwnsFd = true;
|
|
753
767
|
try {
|
|
@@ -761,10 +775,10 @@ function createFileServer(options) {
|
|
|
761
775
|
if (!streamOwnsFd) closeSync(fd);
|
|
762
776
|
}
|
|
763
777
|
});
|
|
764
|
-
return new Promise((
|
|
778
|
+
return new Promise((resolve13) => {
|
|
765
779
|
const connections = /* @__PURE__ */ new Set();
|
|
766
780
|
const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {
|
|
767
|
-
|
|
781
|
+
resolve13({
|
|
768
782
|
url: `http://localhost:${info.port}`,
|
|
769
783
|
port: info.port,
|
|
770
784
|
addPreHeadScript: (script) => {
|
|
@@ -1271,8 +1285,8 @@ var OrderedRenderEventPublisher = class {
|
|
|
1271
1285
|
let timer;
|
|
1272
1286
|
const drained = await Promise.race([
|
|
1273
1287
|
this.tail.then(() => true),
|
|
1274
|
-
new Promise((
|
|
1275
|
-
timer = setTimeout(() =>
|
|
1288
|
+
new Promise((resolve13) => {
|
|
1289
|
+
timer = setTimeout(() => resolve13(false), FLUSH_TIMEOUT_MS);
|
|
1276
1290
|
})
|
|
1277
1291
|
]);
|
|
1278
1292
|
clearTimeout(timer);
|
|
@@ -3616,7 +3630,7 @@ var SequentialCaptureStallError = class extends Error {
|
|
|
3616
3630
|
}
|
|
3617
3631
|
};
|
|
3618
3632
|
function raceAgainstStall(promise, deadlineMs, input, signal) {
|
|
3619
|
-
return new Promise((
|
|
3633
|
+
return new Promise((resolve13, reject) => {
|
|
3620
3634
|
const timer = setTimeout(
|
|
3621
3635
|
() => {
|
|
3622
3636
|
reject(
|
|
@@ -3635,7 +3649,7 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
|
|
|
3635
3649
|
promise.then(
|
|
3636
3650
|
(value) => {
|
|
3637
3651
|
clearTimeout(timer);
|
|
3638
|
-
|
|
3652
|
+
resolve13(value);
|
|
3639
3653
|
},
|
|
3640
3654
|
(error) => {
|
|
3641
3655
|
clearTimeout(timer);
|
|
@@ -5563,15 +5577,15 @@ function countAuthoredTimedClips(html) {
|
|
|
5563
5577
|
}
|
|
5564
5578
|
|
|
5565
5579
|
// src/services/render/stages/compileStage.ts
|
|
5566
|
-
import { join as
|
|
5580
|
+
import { join as join16 } from "node:path";
|
|
5567
5581
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
5568
5582
|
|
|
5569
5583
|
// src/services/htmlCompiler.ts
|
|
5570
5584
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5571
|
-
import { createHash as
|
|
5572
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as
|
|
5573
|
-
import { join as
|
|
5574
|
-
import { parseHTML as
|
|
5585
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
5586
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
|
|
5587
|
+
import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
5588
|
+
import { parseHTML as parseHTML5 } from "linkedom";
|
|
5575
5589
|
import {
|
|
5576
5590
|
COMPOSITION_SOURCE_URL,
|
|
5577
5591
|
compileTimingAttrs,
|
|
@@ -5609,6 +5623,8 @@ import {
|
|
|
5609
5623
|
readDeclaredDefaults,
|
|
5610
5624
|
parseHostVariableValues,
|
|
5611
5625
|
headStyleRuns,
|
|
5626
|
+
isAlwaysAppliedStyle,
|
|
5627
|
+
dedupeFontFaceRules,
|
|
5612
5628
|
inlineScriptRuns,
|
|
5613
5629
|
parsesAsScript,
|
|
5614
5630
|
styleElementsFor,
|
|
@@ -5845,7 +5861,7 @@ function readLoopOverride(el) {
|
|
|
5845
5861
|
}
|
|
5846
5862
|
return null;
|
|
5847
5863
|
}
|
|
5848
|
-
function
|
|
5864
|
+
function resolveLocalImageSourcePath(src, options) {
|
|
5849
5865
|
const trimmed = src.trim();
|
|
5850
5866
|
if (!trimmed || trimmed.startsWith("data:")) return null;
|
|
5851
5867
|
const { basePath } = splitUrlSuffix(trimmed);
|
|
@@ -5901,12 +5917,12 @@ function resolveClipWindowSeconds(img, compositionDuration) {
|
|
|
5901
5917
|
}
|
|
5902
5918
|
return null;
|
|
5903
5919
|
}
|
|
5904
|
-
function resolvePreparedPlayback(metadata,
|
|
5920
|
+
function resolvePreparedPlayback(metadata, loopOverride, windowSeconds) {
|
|
5905
5921
|
const gifDuration = metadata.durationSeconds > 0 ? metadata.durationSeconds : null;
|
|
5906
5922
|
let loopIterations = 1;
|
|
5907
|
-
if (
|
|
5923
|
+
if (resolveLoop(metadata, loopOverride)) {
|
|
5908
5924
|
loopIterations = windowSeconds != null && gifDuration != null ? Math.min(MAX_LOOP_ITERATIONS, Math.max(1, Math.ceil(windowSeconds / gifDuration))) : 1;
|
|
5909
|
-
} else if (metadata.loopCount != null && metadata.loopCount > 1) {
|
|
5925
|
+
} else if (loopOverride == null && metadata.loopCount != null && metadata.loopCount > 1) {
|
|
5910
5926
|
loopIterations = Math.min(MAX_LOOP_ITERATIONS, metadata.loopCount);
|
|
5911
5927
|
}
|
|
5912
5928
|
let padSeconds = 0;
|
|
@@ -6068,14 +6084,19 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
6068
6084
|
const img = images[i];
|
|
6069
6085
|
const src = (img.getAttribute("src") || "").trim();
|
|
6070
6086
|
if (!hasGifExtension(src)) continue;
|
|
6071
|
-
const sourcePath =
|
|
6087
|
+
const sourcePath = resolveLocalImageSourcePath(src, options);
|
|
6072
6088
|
if (!sourcePath) continue;
|
|
6073
6089
|
const bytes = readFileSync5(sourcePath);
|
|
6074
6090
|
const metadata = parseAnimatedGifMetadata(bytes);
|
|
6075
6091
|
if (!metadata?.animated) continue;
|
|
6076
|
-
const
|
|
6092
|
+
const loopOverride = readLoopOverride(img);
|
|
6093
|
+
const loop = resolveLoop(metadata, loopOverride);
|
|
6077
6094
|
const windowSeconds = resolveClipWindowSeconds(img, compositionDuration);
|
|
6078
|
-
const { loopIterations, padSeconds } = resolvePreparedPlayback(
|
|
6095
|
+
const { loopIterations, padSeconds } = resolvePreparedPlayback(
|
|
6096
|
+
metadata,
|
|
6097
|
+
loopOverride,
|
|
6098
|
+
windowSeconds
|
|
6099
|
+
);
|
|
6079
6100
|
const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
|
|
6080
6101
|
const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
|
|
6081
6102
|
const cachePath = join13(cacheDir, filename);
|
|
@@ -6111,19 +6132,74 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
6111
6132
|
};
|
|
6112
6133
|
}
|
|
6113
6134
|
|
|
6114
|
-
// src/services/
|
|
6115
|
-
import {
|
|
6116
|
-
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
6117
|
-
|
|
6118
|
-
// src/services/assetMediaType.ts
|
|
6119
|
-
import { existsSync as existsSync9 } from "node:fs";
|
|
6135
|
+
// src/services/heifImagePrep.ts
|
|
6136
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
6120
6137
|
import {
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6138
|
+
closeSync as closeSync4,
|
|
6139
|
+
copyFileSync as copyFileSync3,
|
|
6140
|
+
mkdirSync as mkdirSync6,
|
|
6141
|
+
openSync as openSync4,
|
|
6142
|
+
readFileSync as readFileSync6,
|
|
6143
|
+
readSync as readSync3,
|
|
6144
|
+
rmSync as rmSync8,
|
|
6145
|
+
statSync as statSync6,
|
|
6146
|
+
writeFileSync as writeFileSync4
|
|
6147
|
+
} from "node:fs";
|
|
6148
|
+
import { dirname as dirname6, join as join14, resolve as resolve6 } from "node:path";
|
|
6149
|
+
import { parseHTML as parseHTML4 } from "linkedom";
|
|
6150
|
+
import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
|
|
6151
|
+
|
|
6152
|
+
// src/services/heifRotation.ts
|
|
6153
|
+
function* boxes(buf, start, end) {
|
|
6154
|
+
let at = start;
|
|
6155
|
+
while (at + 8 <= end) {
|
|
6156
|
+
let size = buf.readUInt32BE(at);
|
|
6157
|
+
let header = 8;
|
|
6158
|
+
if (size === 1 && at + 16 <= end) {
|
|
6159
|
+
size = Number(buf.readBigUInt64BE(at + 8));
|
|
6160
|
+
header = 16;
|
|
6161
|
+
} else if (size === 0) {
|
|
6162
|
+
size = end - at;
|
|
6163
|
+
}
|
|
6164
|
+
if (size < header || at + size > end) return;
|
|
6165
|
+
yield { type: buf.toString("latin1", at + 4, at + 8), start: at + header, end: at + size };
|
|
6166
|
+
at += size;
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
function child(buf, parent, type, skip = 0) {
|
|
6170
|
+
return [...boxes(buf, parent.start + skip, parent.end)].filter((box2) => box2.type === type);
|
|
6171
|
+
}
|
|
6172
|
+
function readHeifPrimaryRotation(buf) {
|
|
6173
|
+
try {
|
|
6174
|
+
const root = { type: "", start: 0, end: buf.length };
|
|
6175
|
+
const meta = child(buf, root, "meta")[0];
|
|
6176
|
+
const pitm = meta && child(buf, meta, "pitm", 4)[0];
|
|
6177
|
+
const iprp = meta && child(buf, meta, "iprp", 4)[0];
|
|
6178
|
+
const ipco = iprp && child(buf, iprp, "ipco")[0];
|
|
6179
|
+
if (!pitm || !ipco || !iprp) return 0;
|
|
6180
|
+
const primaryId = buf[pitm.start] === 0 ? buf.readUInt16BE(pitm.start + 4) : buf.readUInt32BE(pitm.start + 4);
|
|
6181
|
+
const props = [...boxes(buf, ipco.start, ipco.end)];
|
|
6182
|
+
for (const ipma of child(buf, iprp, "ipma")) {
|
|
6183
|
+
const version = buf[ipma.start];
|
|
6184
|
+
const wide = (buf[ipma.start + 3] & 1) === 1;
|
|
6185
|
+
let at = ipma.start + 8;
|
|
6186
|
+
for (let n = buf.readUInt32BE(ipma.start + 4); n > 0 && at < ipma.end; n--) {
|
|
6187
|
+
const id = version < 1 ? buf.readUInt16BE(at) : buf.readUInt32BE(at);
|
|
6188
|
+
at += version < 1 ? 2 : 4;
|
|
6189
|
+
const count = buf[at++];
|
|
6190
|
+
for (let i = 0; i < count; i++) {
|
|
6191
|
+
const index = wide ? buf.readUInt16BE(at) & 32767 : buf[at] & 127;
|
|
6192
|
+
at += wide ? 2 : 1;
|
|
6193
|
+
const prop = props[index - 1];
|
|
6194
|
+
if (id === primaryId && prop?.type === "irot")
|
|
6195
|
+
return (buf[prop.start] & 3) * 90;
|
|
6196
|
+
}
|
|
6197
|
+
}
|
|
6198
|
+
}
|
|
6199
|
+
} catch {
|
|
6200
|
+
}
|
|
6201
|
+
return 0;
|
|
6202
|
+
}
|
|
6127
6203
|
|
|
6128
6204
|
// src/utils/semaphore.ts
|
|
6129
6205
|
var Semaphore = class {
|
|
@@ -6138,10 +6214,10 @@ var Semaphore = class {
|
|
|
6138
6214
|
this.active++;
|
|
6139
6215
|
return () => this.release();
|
|
6140
6216
|
}
|
|
6141
|
-
return new Promise((
|
|
6217
|
+
return new Promise((resolve13) => {
|
|
6142
6218
|
this.queue.push(() => {
|
|
6143
6219
|
this.active++;
|
|
6144
|
-
|
|
6220
|
+
resolve13(() => this.release());
|
|
6145
6221
|
});
|
|
6146
6222
|
});
|
|
6147
6223
|
}
|
|
@@ -6172,7 +6248,229 @@ async function withMediaProbeSlot(operation) {
|
|
|
6172
6248
|
}
|
|
6173
6249
|
}
|
|
6174
6250
|
|
|
6251
|
+
// src/services/heifImagePrep.ts
|
|
6252
|
+
var PREPARED_HEIF_SUBDIR = "_heif_images";
|
|
6253
|
+
var CACHE_SCHEMA2 = "hfheif-v1";
|
|
6254
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
6255
|
+
var HEIF_IMAGE_CONVERSION_FAILED = "HEIF_IMAGE_CONVERSION_FAILED";
|
|
6256
|
+
var HEIF_TOOL_UNAVAILABLE = "HEIF_TOOL_UNAVAILABLE";
|
|
6257
|
+
var HEIF_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "hevc", "hevx", "heim", "heis", "mif1", "msf1"]);
|
|
6258
|
+
var AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
|
|
6259
|
+
var DATA_URL_RE = /^data:image\/(?:heic|heif)(?:-sequence)?;base64,(.*)$/is;
|
|
6260
|
+
var HeifImageConversionError = class extends Error {
|
|
6261
|
+
code = HEIF_IMAGE_CONVERSION_FAILED;
|
|
6262
|
+
owner = "user";
|
|
6263
|
+
retryable = false;
|
|
6264
|
+
constructor(label, reason) {
|
|
6265
|
+
super(
|
|
6266
|
+
`Could not convert HEIC/HEIF image ${label} to JPEG: ${reason}. Re-export the photo as JPEG or PNG and update its src.`
|
|
6267
|
+
);
|
|
6268
|
+
this.name = "HeifImageConversionError";
|
|
6269
|
+
}
|
|
6270
|
+
};
|
|
6271
|
+
var HeifToolUnavailableError = class extends Error {
|
|
6272
|
+
code = HEIF_TOOL_UNAVAILABLE;
|
|
6273
|
+
owner = "system";
|
|
6274
|
+
retryable = false;
|
|
6275
|
+
constructor(label, reason) {
|
|
6276
|
+
super(
|
|
6277
|
+
`Could not convert HEIC/HEIF image ${label}: ${reason}. Converting HEIC needs ffmpeg and ffprobe 7.0 or newer with the hevc decoder.`
|
|
6278
|
+
);
|
|
6279
|
+
this.name = "HeifToolUnavailableError";
|
|
6280
|
+
}
|
|
6281
|
+
};
|
|
6282
|
+
var TOOL_FAILURE_RE = /(?:ffmpeg|ffprobe) not found|ENOENT|Option not found|Unrecognized option|Unknown decoder|Decoder \S+ not found/i;
|
|
6283
|
+
function isHeifBytes(head) {
|
|
6284
|
+
const bytes = Buffer.from(head.buffer, head.byteOffset, head.byteLength);
|
|
6285
|
+
if (bytes.length < 16 || bytes.toString("ascii", 4, 8) !== "ftyp") return false;
|
|
6286
|
+
const boxEnd = Math.min(bytes.readUInt32BE(0), bytes.length);
|
|
6287
|
+
if (boxEnd < 16) return false;
|
|
6288
|
+
const brands = [bytes.toString("ascii", 8, 12)];
|
|
6289
|
+
for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
|
|
6290
|
+
brands.push(bytes.toString("ascii", offset, offset + 4));
|
|
6291
|
+
}
|
|
6292
|
+
return !brands.some((b) => AVIF_BRANDS.has(b)) && brands.some((b) => HEIF_BRANDS.has(b));
|
|
6293
|
+
}
|
|
6294
|
+
function readHead(path) {
|
|
6295
|
+
let fd;
|
|
6296
|
+
try {
|
|
6297
|
+
fd = openSync4(path, "r");
|
|
6298
|
+
const buf = Buffer.alloc(4096);
|
|
6299
|
+
return buf.subarray(0, readSync3(fd, buf, 0, buf.length, 0));
|
|
6300
|
+
} catch {
|
|
6301
|
+
return null;
|
|
6302
|
+
} finally {
|
|
6303
|
+
if (fd !== void 0) closeSync4(fd);
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
async function probeJson(path, withGroups) {
|
|
6307
|
+
const stdout = await runFfprobe(
|
|
6308
|
+
path,
|
|
6309
|
+
withGroups ? ["-show_stream_groups", "-show_streams", "-of", "json"] : ["-show_streams", "-of", "json"]
|
|
6310
|
+
);
|
|
6311
|
+
return JSON.parse(stdout);
|
|
6312
|
+
}
|
|
6313
|
+
var ROTATION_FILTER = {
|
|
6314
|
+
0: "",
|
|
6315
|
+
90: "transpose=2",
|
|
6316
|
+
180: "hflip,vflip",
|
|
6317
|
+
270: "transpose=1"
|
|
6318
|
+
};
|
|
6319
|
+
function primaryGrid(probed) {
|
|
6320
|
+
const groups = probed.stream_groups ?? [];
|
|
6321
|
+
return (groups.find((g) => g.disposition?.default === 1) ?? groups[0])?.components?.[0];
|
|
6322
|
+
}
|
|
6323
|
+
async function convertHeifToJpeg(request) {
|
|
6324
|
+
const probed = await probeJson(request.inputPath, true);
|
|
6325
|
+
const grid = primaryGrid(probed);
|
|
6326
|
+
const rotation = readHeifPrimaryRotation(readFileSync6(request.inputPath));
|
|
6327
|
+
const filters = [];
|
|
6328
|
+
let inputs = [];
|
|
6329
|
+
let size = probed.streams?.[0];
|
|
6330
|
+
if (grid?.subcomponents?.length) {
|
|
6331
|
+
size = grid;
|
|
6332
|
+
const layout = grid.subcomponents.map(
|
|
6333
|
+
(t) => `${t.tile_horizontal_offset}_${t.tile_vertical_offset}`
|
|
6334
|
+
);
|
|
6335
|
+
inputs = grid.subcomponents.map((t) => `[0:${t.stream_index}]`);
|
|
6336
|
+
filters.push(
|
|
6337
|
+
`${inputs.join("")}xstack=inputs=${inputs.length}:layout=${layout.join("|")}`,
|
|
6338
|
+
`crop=${grid.width}:${grid.height}:${grid.horizontal_offset ?? 0}:${grid.vertical_offset ?? 0}`
|
|
6339
|
+
);
|
|
6340
|
+
}
|
|
6341
|
+
if (!size?.width || !size.height) throw new Error("no declared image size");
|
|
6342
|
+
if (rotation && grid) filters.push(ROTATION_FILTER[rotation]);
|
|
6343
|
+
const mapArgs = filters.length > 0 ? ["-filter_complex", `${filters.join(",")}[v]`, "-map", "[v]"] : [];
|
|
6344
|
+
const result = await runFfmpeg3(
|
|
6345
|
+
[
|
|
6346
|
+
"-hide_banner",
|
|
6347
|
+
"-loglevel",
|
|
6348
|
+
"error",
|
|
6349
|
+
"-i",
|
|
6350
|
+
request.inputPath,
|
|
6351
|
+
...mapArgs,
|
|
6352
|
+
"-frames:v",
|
|
6353
|
+
"1",
|
|
6354
|
+
"-q:v",
|
|
6355
|
+
"2",
|
|
6356
|
+
"-f",
|
|
6357
|
+
"mjpeg",
|
|
6358
|
+
"-y",
|
|
6359
|
+
request.outputPath
|
|
6360
|
+
],
|
|
6361
|
+
{ timeout: request.timeoutMs ?? DEFAULT_TIMEOUT_MS }
|
|
6362
|
+
);
|
|
6363
|
+
if (!result.success) {
|
|
6364
|
+
throw result.error ?? new Error(`ffmpeg exited ${result.exitCode}: ${result.stderr.slice(-300)}`);
|
|
6365
|
+
}
|
|
6366
|
+
const out = (await probeJson(request.outputPath, false)).streams?.[0];
|
|
6367
|
+
const same = out?.width === size.width && out.height === size.height;
|
|
6368
|
+
const swapped = out?.width === size.height && out.height === size.width;
|
|
6369
|
+
if (!same && !swapped) {
|
|
6370
|
+
throw new Error(
|
|
6371
|
+
`wrong size: decoded ${out?.width}x${out?.height}, file declares ${size.width}x${size.height}`
|
|
6372
|
+
);
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
function resolveImageSource(src, options) {
|
|
6376
|
+
const data = DATA_URL_RE.exec(src);
|
|
6377
|
+
if (data) {
|
|
6378
|
+
const bytes = Buffer.from(data[1], "base64");
|
|
6379
|
+
return isHeifBytes(bytes.subarray(0, 4096)) ? { path: null, bytes, label: '"inline image"' } : null;
|
|
6380
|
+
}
|
|
6381
|
+
const path = resolveLocalImageSourcePath(src, options);
|
|
6382
|
+
const head = path ? readHead(path) : null;
|
|
6383
|
+
if (!path || !head || !isHeifBytes(head)) return null;
|
|
6384
|
+
return { path, bytes: readFileSync6(path), label: `"${splitUrlSuffix(src).basePath}"` };
|
|
6385
|
+
}
|
|
6386
|
+
function isUsableFile2(path) {
|
|
6387
|
+
try {
|
|
6388
|
+
return statSync6(path).size > 0;
|
|
6389
|
+
} catch {
|
|
6390
|
+
return false;
|
|
6391
|
+
}
|
|
6392
|
+
}
|
|
6393
|
+
async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
6394
|
+
mkdirSync6(dirname6(cachePath), { recursive: true });
|
|
6395
|
+
mkdirSync6(dirname6(outputPath), { recursive: true });
|
|
6396
|
+
if (!isUsableFile2(cachePath)) {
|
|
6397
|
+
const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
6398
|
+
const inlinePath = `${tmpPath}.heic`;
|
|
6399
|
+
const inputPath = source.path ?? inlinePath;
|
|
6400
|
+
try {
|
|
6401
|
+
if (!source.path) writeFileSync4(inlinePath, source.bytes);
|
|
6402
|
+
await (options.transcode ?? convertHeifToJpeg)({
|
|
6403
|
+
inputPath,
|
|
6404
|
+
outputPath: tmpPath,
|
|
6405
|
+
timeoutMs: options.timeoutMs
|
|
6406
|
+
});
|
|
6407
|
+
if (!isUsableFile2(tmpPath)) throw new Error("conversion produced an empty file");
|
|
6408
|
+
renameWithRetry4(tmpPath, cachePath);
|
|
6409
|
+
} catch (error) {
|
|
6410
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
6411
|
+
throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
|
|
6412
|
+
} finally {
|
|
6413
|
+
rmSync8(tmpPath, { force: true });
|
|
6414
|
+
rmSync8(inlinePath, { force: true });
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
6417
|
+
if (resolve6(outputPath) !== resolve6(cachePath) && !isUsableFile2(outputPath)) {
|
|
6418
|
+
copyFileSync3(cachePath, outputPath);
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
async function prepareHeifImageInputs(html, options) {
|
|
6422
|
+
const outputDir = options.outputDir ?? join14(options.downloadDir, PREPARED_HEIF_SUBDIR);
|
|
6423
|
+
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
|
|
6424
|
+
const cacheDir = options.cacheDir ?? outputDir;
|
|
6425
|
+
const preparedAssets = /* @__PURE__ */ new Map();
|
|
6426
|
+
const preparedImages = [];
|
|
6427
|
+
if (!/<img\b/i.test(html)) return { html, preparedAssets, preparedImages };
|
|
6428
|
+
const { document: document2 } = parseHTML4(html);
|
|
6429
|
+
const found = [];
|
|
6430
|
+
for (const img of Array.from(document2.querySelectorAll("img[src]"))) {
|
|
6431
|
+
const src = (img.getAttribute("src") || "").trim();
|
|
6432
|
+
const source = src ? resolveImageSource(src, options) : null;
|
|
6433
|
+
if (!source) continue;
|
|
6434
|
+
const hash = createHash5("sha256").update(CACHE_SCHEMA2).update("\0").update(source.bytes).digest("hex");
|
|
6435
|
+
found.push({ img, src, source, filename: `${CACHE_SCHEMA2}-${hash.slice(0, 24)}.jpg` });
|
|
6436
|
+
}
|
|
6437
|
+
if (found.length === 0) return { html, preparedAssets, preparedImages };
|
|
6438
|
+
const unique = new Map(found.map((item) => [item.filename, item.source]));
|
|
6439
|
+
const failed = /* @__PURE__ */ new Set();
|
|
6440
|
+
await Promise.all(
|
|
6441
|
+
[...unique].map(
|
|
6442
|
+
([filename, source]) => withMediaProbeSlot(
|
|
6443
|
+
() => ensurePreparedJpeg(source, join14(cacheDir, filename), join14(outputDir, filename), options)
|
|
6444
|
+
).catch((error) => {
|
|
6445
|
+
if (!options.onFailure) throw error;
|
|
6446
|
+
failed.add(filename);
|
|
6447
|
+
options.onFailure(error);
|
|
6448
|
+
})
|
|
6449
|
+
)
|
|
6450
|
+
);
|
|
6451
|
+
for (const { img, src, filename } of found) {
|
|
6452
|
+
if (failed.has(filename)) continue;
|
|
6453
|
+
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
6454
|
+
img.setAttribute("src", outputSrc);
|
|
6455
|
+
preparedAssets.set(outputSrc, join14(outputDir, filename));
|
|
6456
|
+
preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join14(outputDir, filename) });
|
|
6457
|
+
}
|
|
6458
|
+
return { html: document2.toString(), preparedAssets, preparedImages };
|
|
6459
|
+
}
|
|
6460
|
+
|
|
6461
|
+
// src/services/htmlCompiler.ts
|
|
6462
|
+
import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
|
|
6463
|
+
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
6464
|
+
|
|
6175
6465
|
// src/services/assetMediaType.ts
|
|
6466
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
6467
|
+
import {
|
|
6468
|
+
fingerprintElementId,
|
|
6469
|
+
isNotMediaPayload,
|
|
6470
|
+
NotMediaPayloadError,
|
|
6471
|
+
probeMediaProfile,
|
|
6472
|
+
resolveProjectRelativeSrc as resolveProjectRelativeSrc2
|
|
6473
|
+
} from "@hyperframes/engine";
|
|
6176
6474
|
var ASSET_MEDIA_TYPE_MISMATCH = "ASSET_MEDIA_TYPE_MISMATCH";
|
|
6177
6475
|
var AssetMediaTypeMismatchError = class extends Error {
|
|
6178
6476
|
code = ASSET_MEDIA_TYPE_MISMATCH;
|
|
@@ -6282,7 +6580,7 @@ function logRemoteDownloadTelemetry(event) {
|
|
|
6282
6580
|
}
|
|
6283
6581
|
var INFERRED_MEDIA_DURATION_ATTR = "data-hf-inferred-duration";
|
|
6284
6582
|
function parseSubCompHtmlForValidity(html) {
|
|
6285
|
-
return
|
|
6583
|
+
return parseHTML5(html).document;
|
|
6286
6584
|
}
|
|
6287
6585
|
function endsWithSourceUrl(code) {
|
|
6288
6586
|
const trimmed = code.trimEnd();
|
|
@@ -6290,7 +6588,7 @@ function endsWithSourceUrl(code) {
|
|
|
6290
6588
|
return lastLine.startsWith("//# sourceURL=") || lastLine.startsWith("//@ sourceURL=");
|
|
6291
6589
|
}
|
|
6292
6590
|
function prepareCompositionScripts(html) {
|
|
6293
|
-
const { document: document2 } =
|
|
6591
|
+
const { document: document2 } = parseHTML5(html);
|
|
6294
6592
|
for (const el of document2.querySelectorAll("script:not([src])")) {
|
|
6295
6593
|
const isModule = (el.getAttribute("type") || "").trim().toLowerCase() === "module";
|
|
6296
6594
|
const runsAsScript = isModule || isJavaScriptType(el);
|
|
@@ -6327,14 +6625,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
|
|
|
6327
6625
|
}
|
|
6328
6626
|
};
|
|
6329
6627
|
function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
|
|
6330
|
-
const { document: document2 } =
|
|
6628
|
+
const { document: document2 } = parseHTML5(html);
|
|
6331
6629
|
const hosts = [...document2.querySelectorAll("[data-composition-src]")];
|
|
6332
6630
|
const problems = [];
|
|
6333
6631
|
for (const el of hosts) {
|
|
6334
6632
|
const srcPath = el.getAttribute("data-composition-src");
|
|
6335
6633
|
if (!srcPath) continue;
|
|
6336
6634
|
if (isUnresolvedAssetPlaceholder(srcPath)) continue;
|
|
6337
|
-
const filePath =
|
|
6635
|
+
const filePath = resolve7(projectDir, srcPath);
|
|
6338
6636
|
if (visited.has(filePath)) continue;
|
|
6339
6637
|
const read = readProjectFile(filePath);
|
|
6340
6638
|
if (read.kind === "missing") {
|
|
@@ -6390,7 +6688,7 @@ function runtimeRoot(document2) {
|
|
|
6390
6688
|
}
|
|
6391
6689
|
function detectRenderModeHints(html, pageMotionBlur) {
|
|
6392
6690
|
const reasons = [];
|
|
6393
|
-
const { document: document2 } =
|
|
6691
|
+
const { document: document2 } = parseHTML5(html);
|
|
6394
6692
|
if (document2.querySelector("canvas[layoutsubtree]")) {
|
|
6395
6693
|
reasons.push({
|
|
6396
6694
|
code: "htmlInCanvas",
|
|
@@ -6432,7 +6730,7 @@ function detectThreeDTransformUsage(html) {
|
|
|
6432
6730
|
}
|
|
6433
6731
|
var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
|
|
6434
6732
|
function detectAncestorBackgroundImage(html) {
|
|
6435
|
-
const { document: document2 } =
|
|
6733
|
+
const { document: document2 } = parseHTML5(html);
|
|
6436
6734
|
const root = document2.querySelector("[data-composition-id]");
|
|
6437
6735
|
if (!root) return false;
|
|
6438
6736
|
const ancestors = [];
|
|
@@ -6479,7 +6777,7 @@ function detectShaderTransitionUsage(html) {
|
|
|
6479
6777
|
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
|
|
6480
6778
|
let filePath = src;
|
|
6481
6779
|
if (isHttpUrl(src)) {
|
|
6482
|
-
if (!existsSync10(downloadDir))
|
|
6780
|
+
if (!existsSync10(downloadDir)) mkdirSync7(downloadDir, { recursive: true });
|
|
6483
6781
|
try {
|
|
6484
6782
|
filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
|
|
6485
6783
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -6489,7 +6787,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
6489
6787
|
}
|
|
6490
6788
|
} else {
|
|
6491
6789
|
filePath = decodedUrlPath(src);
|
|
6492
|
-
if (!filePath.startsWith("/")) filePath =
|
|
6790
|
+
if (!filePath.startsWith("/")) filePath = join15(baseDir, filePath);
|
|
6493
6791
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
6494
6792
|
}
|
|
6495
6793
|
if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
|
|
@@ -6546,7 +6844,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
6546
6844
|
function markInferredVariableMediaDurations(html, unresolvedMedia) {
|
|
6547
6845
|
const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
|
|
6548
6846
|
if (unresolvedIds.size === 0) return html;
|
|
6549
|
-
const { document: document2 } =
|
|
6847
|
+
const { document: document2 } = parseHTML5(html);
|
|
6550
6848
|
let changed = false;
|
|
6551
6849
|
for (const element of document2.querySelectorAll("video[data-var-src], audio[data-var-src]")) {
|
|
6552
6850
|
if (!unresolvedIds.has(element.id)) continue;
|
|
@@ -6617,13 +6915,13 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
|
|
|
6617
6915
|
}
|
|
6618
6916
|
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
|
|
6619
6917
|
const subCompositions = /* @__PURE__ */ new Map();
|
|
6620
|
-
const { document: document2 } =
|
|
6918
|
+
const { document: document2 } = parseHTML5(html);
|
|
6621
6919
|
const compEls = document2.querySelectorAll("[data-composition-src]");
|
|
6622
6920
|
const workItems = [];
|
|
6623
6921
|
for (const el of compEls) {
|
|
6624
6922
|
const srcPath = el.getAttribute("data-composition-src");
|
|
6625
6923
|
if (!srcPath) continue;
|
|
6626
|
-
const filePath =
|
|
6924
|
+
const filePath = resolve7(projectDir, srcPath);
|
|
6627
6925
|
if (visited.has(filePath)) {
|
|
6628
6926
|
continue;
|
|
6629
6927
|
}
|
|
@@ -6640,7 +6938,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
6640
6938
|
workItems.map(async (item) => {
|
|
6641
6939
|
const { html: compiledSub } = await compileHtmlFile(
|
|
6642
6940
|
rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
|
|
6643
|
-
|
|
6941
|
+
dirname7(item.filePath),
|
|
6644
6942
|
downloadDir,
|
|
6645
6943
|
void 0,
|
|
6646
6944
|
urlByAbsentLocalPath
|
|
@@ -6668,7 +6966,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
6668
6966
|
return { subCompositions };
|
|
6669
6967
|
}
|
|
6670
6968
|
function promoteCssImportsToLinkTags(html) {
|
|
6671
|
-
const { document: document2 } =
|
|
6969
|
+
const { document: document2 } = parseHTML5(html);
|
|
6672
6970
|
const head = document2.querySelector("head");
|
|
6673
6971
|
if (!head) return html;
|
|
6674
6972
|
const importRe = /@import\s+url\(\s*['"]?([^'")\s]+)['"]?\s*\)\s*;?/gi;
|
|
@@ -6737,10 +7035,15 @@ var ProducerHostIdentityMap = class extends Map {
|
|
|
6737
7035
|
}
|
|
6738
7036
|
};
|
|
6739
7037
|
function coalesceHeadStylesAndBodyScripts(html) {
|
|
6740
|
-
const { document: document2 } =
|
|
7038
|
+
const { document: document2 } = parseHTML5(html);
|
|
6741
7039
|
const head = document2.querySelector("head");
|
|
6742
7040
|
const body = document2.querySelector("body");
|
|
6743
7041
|
if (!head && !body) return html;
|
|
7042
|
+
const liveStyles = [...document2.querySelectorAll("style")].filter(isAlwaysAppliedStyle);
|
|
7043
|
+
const dedupedStyles = dedupeFontFaceRules(liveStyles.map((el) => el.textContent || ""));
|
|
7044
|
+
liveStyles.forEach((el, i) => {
|
|
7045
|
+
el.textContent = dedupedStyles[i] ?? "";
|
|
7046
|
+
});
|
|
6744
7047
|
const styleEls = head ? Array.from(head.querySelectorAll("style")) : [];
|
|
6745
7048
|
const importRe = /@import\s+url\([^)]*\)\s*;|@import\s+["'][^"']+["']\s*;/gi;
|
|
6746
7049
|
for (const run of styleEls.length > 1 ? headStyleRuns(styleEls) : []) {
|
|
@@ -6783,7 +7086,7 @@ function coalesceHeadStylesAndBodyScripts(html) {
|
|
|
6783
7086
|
return document2.toString();
|
|
6784
7087
|
}
|
|
6785
7088
|
function inlineSubCompositions(html, subCompositions, projectDir, variableOverrides = {}) {
|
|
6786
|
-
const { document: document2 } =
|
|
7089
|
+
const { document: document2 } = parseHTML5(html);
|
|
6787
7090
|
const head = document2.querySelector("head");
|
|
6788
7091
|
const body = document2.querySelector("body");
|
|
6789
7092
|
const hosts = Array.from(document2.querySelectorAll("[data-composition-src]"));
|
|
@@ -6812,17 +7115,17 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
6812
7115
|
resolveHtml: (srcPath) => {
|
|
6813
7116
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
6814
7117
|
if (!compHtml) {
|
|
6815
|
-
const read = readProjectFile(
|
|
7118
|
+
const read = readProjectFile(resolve7(projectDir, srcPath));
|
|
6816
7119
|
if (read.kind === "file") {
|
|
6817
7120
|
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
6818
7121
|
}
|
|
6819
7122
|
}
|
|
6820
7123
|
return compHtml;
|
|
6821
7124
|
},
|
|
6822
|
-
parseHtml: (htmlStr) =>
|
|
7125
|
+
parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
|
|
6823
7126
|
// Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
|
|
6824
7127
|
// against its own directory, project-root refs stay as authored.
|
|
6825
|
-
assetExists: (path) => existsSync10(
|
|
7128
|
+
assetExists: (path) => existsSync10(resolve7(projectDir, path)),
|
|
6826
7129
|
// Preserve the authored root wrapper as a child of the host, matching
|
|
6827
7130
|
// the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
|
|
6828
7131
|
// which the runtime compositionLoader mirrors with its own copy for the
|
|
@@ -6923,7 +7226,7 @@ ${html}
|
|
|
6923
7226
|
</html>`;
|
|
6924
7227
|
}
|
|
6925
7228
|
function injectTextRenderingRule(html) {
|
|
6926
|
-
const { document: document2 } =
|
|
7229
|
+
const { document: document2 } = parseHTML5(html);
|
|
6927
7230
|
const head = document2.querySelector("head");
|
|
6928
7231
|
if (!head) return html;
|
|
6929
7232
|
if (document2.querySelector("style[data-hyperframes-text-rendering]")) {
|
|
@@ -6947,7 +7250,7 @@ function matchesScriptIntegrity(bytes, metadata) {
|
|
|
6947
7250
|
(alg) => hashes.some((hash) => hash[1]?.toLowerCase() === alg)
|
|
6948
7251
|
);
|
|
6949
7252
|
if (!strongest) return true;
|
|
6950
|
-
const actual =
|
|
7253
|
+
const actual = createHash6(strongest).update(bytes).digest();
|
|
6951
7254
|
return hashes.some(
|
|
6952
7255
|
(hash) => hash[1]?.toLowerCase() === strongest && actual.equals(Buffer.from(hash[2], "base64"))
|
|
6953
7256
|
);
|
|
@@ -6955,7 +7258,7 @@ function matchesScriptIntegrity(bytes, metadata) {
|
|
|
6955
7258
|
async function inlineExternalScripts(html) {
|
|
6956
7259
|
const fullHtml = ensureFullDocument(html);
|
|
6957
7260
|
const wrappedFragment = fullHtml !== html;
|
|
6958
|
-
const { document: document2 } =
|
|
7261
|
+
const { document: document2 } = parseHTML5(fullHtml);
|
|
6959
7262
|
const scripts = document2.querySelectorAll("script[src]");
|
|
6960
7263
|
const externalScripts = [];
|
|
6961
7264
|
for (const el of scripts) {
|
|
@@ -7004,12 +7307,12 @@ ${safeText}
|
|
|
7004
7307
|
return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
|
|
7005
7308
|
}
|
|
7006
7309
|
function collectExternalAssets(html, projectDir) {
|
|
7007
|
-
const absProjectDir =
|
|
7310
|
+
const absProjectDir = resolve7(projectDir);
|
|
7008
7311
|
const externalAssets = /* @__PURE__ */ new Map();
|
|
7009
7312
|
function processPath(rawPath) {
|
|
7010
7313
|
const trimmed = rawPath.trim();
|
|
7011
7314
|
if (isNonRelativeUrl(trimmed)) return null;
|
|
7012
|
-
const absPath =
|
|
7315
|
+
const absPath = resolve7(absProjectDir, trimmed);
|
|
7013
7316
|
if (isPathInside2(absPath, absProjectDir)) {
|
|
7014
7317
|
return null;
|
|
7015
7318
|
}
|
|
@@ -7018,7 +7321,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
7018
7321
|
externalAssets.set(safeKey, absPath);
|
|
7019
7322
|
return safeKey;
|
|
7020
7323
|
}
|
|
7021
|
-
const { document: document2 } =
|
|
7324
|
+
const { document: document2 } = parseHTML5(html);
|
|
7022
7325
|
for (const el of document2.querySelectorAll("[src], [href]")) {
|
|
7023
7326
|
for (const attr of ["src", "href"]) {
|
|
7024
7327
|
const val = (el.getAttribute(attr) || "").trim();
|
|
@@ -7063,7 +7366,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
7063
7366
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
7064
7367
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
7065
7368
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
7066
|
-
if (!existsSync10(remoteDir))
|
|
7369
|
+
if (!existsSync10(remoteDir)) mkdirSync7(remoteDir, { recursive: true });
|
|
7067
7370
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
7068
7371
|
await Promise.all(
|
|
7069
7372
|
[...urlSet].map(async (url) => {
|
|
@@ -7110,7 +7413,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
7110
7413
|
return downloadAndRewriteUrls(
|
|
7111
7414
|
urlSet,
|
|
7112
7415
|
html,
|
|
7113
|
-
|
|
7416
|
+
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7114
7417
|
"Remote media download failed",
|
|
7115
7418
|
"Localized remote media source(s)"
|
|
7116
7419
|
);
|
|
@@ -7125,7 +7428,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
7125
7428
|
return downloadAndRewriteUrls(
|
|
7126
7429
|
urlSet,
|
|
7127
7430
|
html,
|
|
7128
|
-
|
|
7431
|
+
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7129
7432
|
"Remote image download failed",
|
|
7130
7433
|
"Localized remote image source(s)"
|
|
7131
7434
|
);
|
|
@@ -7141,7 +7444,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
7141
7444
|
return downloadAndRewriteUrls(
|
|
7142
7445
|
urlSet,
|
|
7143
7446
|
html,
|
|
7144
|
-
|
|
7447
|
+
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7145
7448
|
"Remote background-image download failed",
|
|
7146
7449
|
"Localized remote background-image(s)",
|
|
7147
7450
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -7276,7 +7579,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
|
|
|
7276
7579
|
return downloadAndRewriteUrls(
|
|
7277
7580
|
urlSet,
|
|
7278
7581
|
processed,
|
|
7279
|
-
|
|
7582
|
+
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7280
7583
|
"Remote font download failed",
|
|
7281
7584
|
"Localized remote font face(s)",
|
|
7282
7585
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -7321,7 +7624,7 @@ async function embedLocalFontFaces(html, projectDir) {
|
|
|
7321
7624
|
while ((urlMatch = urlRe.exec(block)) !== null) {
|
|
7322
7625
|
const localPath = urlMatch[1];
|
|
7323
7626
|
if (!localPath || embeddedPaths.has(localPath)) continue;
|
|
7324
|
-
const absPath = localPath.startsWith("/") ? localPath :
|
|
7627
|
+
const absPath = localPath.startsWith("/") ? localPath : resolve7(projectDir, localPath);
|
|
7325
7628
|
if (!isPathInside2(absPath, projectDir)) continue;
|
|
7326
7629
|
const ext = absPath.match(/\.(woff2?|ttf|otf|ttc)$/i)?.[1]?.toLowerCase() ?? "ttf";
|
|
7327
7630
|
try {
|
|
@@ -7370,7 +7673,7 @@ function renderGsapDist() {
|
|
|
7370
7673
|
}
|
|
7371
7674
|
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
7372
7675
|
let gsapDist;
|
|
7373
|
-
const exists = (path) => existsSync10(
|
|
7676
|
+
const exists = (path) => existsSync10(resolve7(projectDir, path));
|
|
7374
7677
|
return html.replace(
|
|
7375
7678
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
7376
7679
|
(full, prefix, src, file, suffix) => {
|
|
@@ -7386,8 +7689,8 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
7386
7689
|
if (!isPathInside2(htmlPath, projectDir)) return html;
|
|
7387
7690
|
const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
|
|
7388
7691
|
if (!entryPath.includes("/")) return html;
|
|
7389
|
-
const { document: document2 } =
|
|
7390
|
-
const assetExists = (path) => existsSync10(
|
|
7692
|
+
const { document: document2 } = parseHTML5(html);
|
|
7693
|
+
const assetExists = (path) => existsSync10(resolve7(projectDir, path));
|
|
7391
7694
|
rewriteAssetPaths(
|
|
7392
7695
|
document2.querySelectorAll("[src], [href]"),
|
|
7393
7696
|
entryPath,
|
|
@@ -7409,7 +7712,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
7409
7712
|
}
|
|
7410
7713
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
7411
7714
|
const entryHtml = rebaseDirectEntryAssetPaths(
|
|
7412
|
-
|
|
7715
|
+
readFileSync7(htmlPath, "utf-8"),
|
|
7413
7716
|
projectDir,
|
|
7414
7717
|
htmlPath
|
|
7415
7718
|
);
|
|
@@ -7479,12 +7782,26 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7479
7782
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
7480
7783
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
7481
7784
|
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
|
|
7785
|
+
const {
|
|
7786
|
+
html: htmlWithJpegs,
|
|
7787
|
+
preparedAssets: preparedHeifAssets,
|
|
7788
|
+
preparedImages: preparedHeifImages
|
|
7789
|
+
} = await prepareHeifImageInputs(htmlWithLocalizedFonts, {
|
|
7790
|
+
projectDir,
|
|
7791
|
+
downloadDir,
|
|
7792
|
+
cacheDir: options.animatedGifCacheDir,
|
|
7793
|
+
sourceAssets: remoteImageAssets,
|
|
7794
|
+
timeoutMs: options.ffmpegProcessTimeout
|
|
7795
|
+
});
|
|
7796
|
+
if (preparedHeifImages.length > 0) {
|
|
7797
|
+
defaultLogger.info(`[Compiler] Converted ${preparedHeifImages.length} HEIC image(s) to JPEG`);
|
|
7798
|
+
}
|
|
7482
7799
|
const gifSourceAssets = new Map(remoteImageAssets);
|
|
7483
7800
|
const {
|
|
7484
7801
|
html: htmlWithPreparedGifs,
|
|
7485
7802
|
preparedAssets: preparedGifAssets,
|
|
7486
7803
|
preparedGifs
|
|
7487
|
-
} = await prepareAnimatedGifInputs(
|
|
7804
|
+
} = await prepareAnimatedGifInputs(htmlWithJpegs, {
|
|
7488
7805
|
projectDir,
|
|
7489
7806
|
downloadDir,
|
|
7490
7807
|
cacheDir: options.animatedGifCacheDir,
|
|
@@ -7511,11 +7828,14 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7511
7828
|
for (const [relPath, absPath] of preparedGifAssets) {
|
|
7512
7829
|
externalAssets.set(relPath, absPath);
|
|
7513
7830
|
}
|
|
7831
|
+
for (const [relPath, absPath] of preparedHeifAssets) {
|
|
7832
|
+
externalAssets.set(relPath, absPath);
|
|
7833
|
+
}
|
|
7514
7834
|
const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
|
|
7515
7835
|
const masterFxChain = parseMasterFxChain(html);
|
|
7516
7836
|
for (const video of videos) {
|
|
7517
7837
|
if (isHttpUrl(video.src)) continue;
|
|
7518
|
-
const videoPath =
|
|
7838
|
+
const videoPath = resolve7(projectDir, video.src);
|
|
7519
7839
|
const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
|
|
7520
7840
|
(async () => {
|
|
7521
7841
|
const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
|
|
@@ -7535,7 +7855,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7535
7855
|
})().catch(() => {
|
|
7536
7856
|
});
|
|
7537
7857
|
}
|
|
7538
|
-
const { document: document2 } =
|
|
7858
|
+
const { document: document2 } = parseHTML5(html);
|
|
7539
7859
|
const rootEl = document2.querySelector("[data-composition-id]");
|
|
7540
7860
|
const width = rootEl ? parseInt(rootEl.getAttribute("data-width") || "1080", 10) : 1080;
|
|
7541
7861
|
const height = rootEl ? parseInt(rootEl.getAttribute("data-height") || "1920", 10) : 1920;
|
|
@@ -7942,7 +8262,7 @@ async function runCompileStage(input) {
|
|
|
7942
8262
|
abortSignal
|
|
7943
8263
|
} = input;
|
|
7944
8264
|
const compileStart = Date.now();
|
|
7945
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
8265
|
+
const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
|
|
7946
8266
|
log,
|
|
7947
8267
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
7948
8268
|
allowSystemFontCapture,
|
|
@@ -7950,7 +8270,7 @@ async function runCompileStage(input) {
|
|
|
7950
8270
|
variables: input.variables,
|
|
7951
8271
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
7952
8272
|
pageMotionBlur: input.pageMotionBlur,
|
|
7953
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
8273
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join16(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
7954
8274
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
7955
8275
|
});
|
|
7956
8276
|
assertNotAborted();
|
|
@@ -8006,7 +8326,7 @@ async function runCompileStage(input) {
|
|
|
8006
8326
|
};
|
|
8007
8327
|
await preflightCompositionAssetMediaTypes({
|
|
8008
8328
|
projectDir,
|
|
8009
|
-
compiledDir:
|
|
8329
|
+
compiledDir: join16(workDir, "compiled"),
|
|
8010
8330
|
composition,
|
|
8011
8331
|
signal: abortSignal
|
|
8012
8332
|
});
|
|
@@ -8051,8 +8371,8 @@ async function runCompileStage(input) {
|
|
|
8051
8371
|
}
|
|
8052
8372
|
|
|
8053
8373
|
// src/services/render/stages/probeStage.ts
|
|
8054
|
-
import { join as
|
|
8055
|
-
import { parseHTML as
|
|
8374
|
+
import { join as join17 } from "node:path";
|
|
8375
|
+
import { parseHTML as parseHTML6 } from "linkedom";
|
|
8056
8376
|
import {
|
|
8057
8377
|
closeCaptureSession as closeCaptureSession4,
|
|
8058
8378
|
compositionRequiresWebGpu,
|
|
@@ -8099,7 +8419,7 @@ function durationToFrameCount(duration, fps) {
|
|
|
8099
8419
|
}
|
|
8100
8420
|
function hasScriptedAudioVolumeAutomation(html, audioCount) {
|
|
8101
8421
|
if (audioCount <= 0) return false;
|
|
8102
|
-
const { document: document2 } =
|
|
8422
|
+
const { document: document2 } = parseHTML6(html);
|
|
8103
8423
|
const scriptBodies = [...document2.querySelectorAll("script")].map((script) => script.textContent ?? "").join("\n");
|
|
8104
8424
|
if (!scriptBodies) return false;
|
|
8105
8425
|
return /\.\s*volume\s*=/i.test(scriptBodies) || /\b(?:gsap|tl|timeline|tween)\s*\.\s*(?:to|fromTo|set)\s*\([\s\S]{0,2000}\bvolume\s*:/i.test(
|
|
@@ -8107,12 +8427,12 @@ function hasScriptedAudioVolumeAutomation(html, audioCount) {
|
|
|
8107
8427
|
);
|
|
8108
8428
|
}
|
|
8109
8429
|
function hasAutoStartVideos(html) {
|
|
8110
|
-
const { document: document2 } =
|
|
8430
|
+
const { document: document2 } = parseHTML6(html);
|
|
8111
8431
|
return document2.querySelector("video[data-hf-auto-start]") !== null;
|
|
8112
8432
|
}
|
|
8113
8433
|
function hasVariableBoundMedia(html, variables) {
|
|
8114
8434
|
if (!variables || Object.keys(variables).length === 0) return false;
|
|
8115
|
-
const { document: document2 } =
|
|
8435
|
+
const { document: document2 } = parseHTML6(html);
|
|
8116
8436
|
return Array.from(
|
|
8117
8437
|
document2.querySelectorAll(
|
|
8118
8438
|
"img[data-var-src], audio[data-var-src], video[data-var-src], source[data-var-src]"
|
|
@@ -8123,7 +8443,7 @@ function hasVariableBoundMedia(html, variables) {
|
|
|
8123
8443
|
});
|
|
8124
8444
|
}
|
|
8125
8445
|
function authoredMediaStarts(html) {
|
|
8126
|
-
const { document: document2 } =
|
|
8446
|
+
const { document: document2 } = parseHTML6(html);
|
|
8127
8447
|
const starts = /* @__PURE__ */ new Map();
|
|
8128
8448
|
for (const media of document2.querySelectorAll("video, audio")) {
|
|
8129
8449
|
const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
|
|
@@ -8160,7 +8480,7 @@ function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, dura
|
|
|
8160
8480
|
return existingEnd <= 0 ? projectedEnd : Math.min(existingEnd, projectedEnd);
|
|
8161
8481
|
}
|
|
8162
8482
|
function hasRuntimeMediaChanges(html) {
|
|
8163
|
-
const { document: document2 } =
|
|
8483
|
+
const { document: document2 } = parseHTML6(html);
|
|
8164
8484
|
const scriptBodies = [...document2.querySelectorAll("script")].map(
|
|
8165
8485
|
(script) => script.textContent ?? ""
|
|
8166
8486
|
);
|
|
@@ -8243,7 +8563,7 @@ async function runProbeStage(input) {
|
|
|
8243
8563
|
});
|
|
8244
8564
|
fileServer = await createFileServer({
|
|
8245
8565
|
projectDir,
|
|
8246
|
-
compiledDir:
|
|
8566
|
+
compiledDir: join17(workDir, "compiled"),
|
|
8247
8567
|
port: 0,
|
|
8248
8568
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
8249
8569
|
fps: job.config.fps
|
|
@@ -8272,7 +8592,7 @@ async function runProbeStage(input) {
|
|
|
8272
8592
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
8273
8593
|
probeSession = await createCaptureSession4(
|
|
8274
8594
|
fileServer.url,
|
|
8275
|
-
|
|
8595
|
+
join17(workDir, "probe"),
|
|
8276
8596
|
captureOpts,
|
|
8277
8597
|
null,
|
|
8278
8598
|
probeCfg
|
|
@@ -8357,7 +8677,7 @@ async function runProbeStage(input) {
|
|
|
8357
8677
|
});
|
|
8358
8678
|
probeSession = await createCaptureSession4(
|
|
8359
8679
|
fileServer.url,
|
|
8360
|
-
|
|
8680
|
+
join17(workDir, "probe-screenshot"),
|
|
8361
8681
|
captureOpts,
|
|
8362
8682
|
null,
|
|
8363
8683
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -8393,7 +8713,7 @@ async function runProbeStage(input) {
|
|
|
8393
8713
|
compiled,
|
|
8394
8714
|
resolutions,
|
|
8395
8715
|
projectDir,
|
|
8396
|
-
|
|
8716
|
+
join17(workDir, "downloads")
|
|
8397
8717
|
);
|
|
8398
8718
|
assertNotAborted();
|
|
8399
8719
|
composition.videos = compiled.videos;
|
|
@@ -8584,7 +8904,7 @@ async function runProbeStage(input) {
|
|
|
8584
8904
|
}
|
|
8585
8905
|
await preflightCompositionAssetMediaTypes({
|
|
8586
8906
|
projectDir,
|
|
8587
|
-
compiledDir:
|
|
8907
|
+
compiledDir: join17(workDir, "compiled"),
|
|
8588
8908
|
composition,
|
|
8589
8909
|
signal: abortSignal
|
|
8590
8910
|
});
|
|
@@ -8749,7 +9069,7 @@ function validateDistributedDuration(input) {
|
|
|
8749
9069
|
|
|
8750
9070
|
// src/services/render/stages/extractVideosStage.ts
|
|
8751
9071
|
import { existsSync as existsSync11 } from "node:fs";
|
|
8752
|
-
import { join as
|
|
9072
|
+
import { join as join18 } from "node:path";
|
|
8753
9073
|
import {
|
|
8754
9074
|
classifyVideoExtractionError,
|
|
8755
9075
|
createFrameLookupTable,
|
|
@@ -9069,14 +9389,14 @@ async function runExtractVideosStage(input) {
|
|
|
9069
9389
|
// because short boundary counts can differ by one frame.
|
|
9070
9390
|
{
|
|
9071
9391
|
fps: job.config.fps,
|
|
9072
|
-
outputDir:
|
|
9392
|
+
outputDir: join18(compiledDir, "__hyperframes_video_frames"),
|
|
9073
9393
|
format: job.config.videoFrameFormat ?? "auto",
|
|
9074
9394
|
toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
|
|
9075
9395
|
timelineEnd: composition.duration,
|
|
9076
9396
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
9077
9397
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
9078
9398
|
deferRangeExtraction,
|
|
9079
|
-
contentKeyedDirs: [
|
|
9399
|
+
contentKeyedDirs: [join18(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
9080
9400
|
urlByAbsentLocalPath
|
|
9081
9401
|
},
|
|
9082
9402
|
abortSignal,
|
|
@@ -9143,8 +9463,8 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
9143
9463
|
}
|
|
9144
9464
|
|
|
9145
9465
|
// src/services/render/stages/audioStage.ts
|
|
9146
|
-
import { join as
|
|
9147
|
-
import { rmSync as
|
|
9466
|
+
import { join as join20 } from "node:path";
|
|
9467
|
+
import { rmSync as rmSync10 } from "node:fs";
|
|
9148
9468
|
import {
|
|
9149
9469
|
MIXED_AUDIO_FILENAME,
|
|
9150
9470
|
audioFailureDiagnostics,
|
|
@@ -9153,8 +9473,8 @@ import {
|
|
|
9153
9473
|
|
|
9154
9474
|
// src/services/render/audioPadTrim.ts
|
|
9155
9475
|
import { spawn } from "node:child_process";
|
|
9156
|
-
import { mkdtempSync as mkdtempSync3, rmSync as
|
|
9157
|
-
import { dirname as
|
|
9476
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync9 } from "node:fs";
|
|
9477
|
+
import { dirname as dirname8, join as join19 } from "node:path";
|
|
9158
9478
|
import {
|
|
9159
9479
|
DEFAULT_CONFIG,
|
|
9160
9480
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -9162,8 +9482,8 @@ import {
|
|
|
9162
9482
|
extractAudioMetadata as extractAudioMetadata3,
|
|
9163
9483
|
getFfprobeBinary,
|
|
9164
9484
|
ManagedChildProcess,
|
|
9165
|
-
renameWithRetry as
|
|
9166
|
-
runFfmpeg as
|
|
9485
|
+
renameWithRetry as renameWithRetry5,
|
|
9486
|
+
runFfmpeg as runFfmpeg5,
|
|
9167
9487
|
trackChildProcess
|
|
9168
9488
|
} from "@hyperframes/engine";
|
|
9169
9489
|
import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
|
|
@@ -9173,7 +9493,7 @@ import {
|
|
|
9173
9493
|
aacSegmentCount,
|
|
9174
9494
|
describeFfmpegFailure,
|
|
9175
9495
|
extractAudioMetadata as extractAudioMetadata2,
|
|
9176
|
-
runFfmpeg as
|
|
9496
|
+
runFfmpeg as runFfmpeg4
|
|
9177
9497
|
} from "@hyperframes/engine";
|
|
9178
9498
|
var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
|
|
9179
9499
|
function checkedDbfs(value) {
|
|
@@ -9197,7 +9517,7 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
|
|
|
9197
9517
|
else laterBlocks = Math.max(laterBlocks, block);
|
|
9198
9518
|
}
|
|
9199
9519
|
};
|
|
9200
|
-
const result = await
|
|
9520
|
+
const result = await runFfmpeg4(
|
|
9201
9521
|
["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
|
|
9202
9522
|
{ signal, timeout: timeoutMs, onStderr: readLines }
|
|
9203
9523
|
);
|
|
@@ -9357,7 +9677,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
9357
9677
|
error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
|
|
9358
9678
|
};
|
|
9359
9679
|
} finally {
|
|
9360
|
-
for (const path of plan2.cleanupPaths)
|
|
9680
|
+
for (const path of plan2.cleanupPaths) rmSync9(path, { force: true });
|
|
9361
9681
|
}
|
|
9362
9682
|
let loweredDb = 0;
|
|
9363
9683
|
if (probeTruePeak) {
|
|
@@ -9404,8 +9724,8 @@ async function constrainAacTruePeak(input) {
|
|
|
9404
9724
|
async function enforceAacTruePeak(input) {
|
|
9405
9725
|
let scratchDir;
|
|
9406
9726
|
try {
|
|
9407
|
-
scratchDir = mkdtempSync3(
|
|
9408
|
-
const correctedPath =
|
|
9727
|
+
scratchDir = mkdtempSync3(join19(dirname8(input.audioPath), ".true-peak-"));
|
|
9728
|
+
const correctedPath = join19(scratchDir, "audio.m4a");
|
|
9409
9729
|
const measurements = [];
|
|
9410
9730
|
let attenuationDb = 0;
|
|
9411
9731
|
let sourcePeakDbfs;
|
|
@@ -9420,7 +9740,7 @@ async function enforceAacTruePeak(input) {
|
|
|
9420
9740
|
}
|
|
9421
9741
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
9422
9742
|
if (measuredPath !== correctedPath) return { success: true };
|
|
9423
|
-
|
|
9743
|
+
renameWithRetry5(correctedPath, input.audioPath);
|
|
9424
9744
|
return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
|
|
9425
9745
|
}
|
|
9426
9746
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
@@ -9461,7 +9781,7 @@ async function enforceAacTruePeak(input) {
|
|
|
9461
9781
|
)}`
|
|
9462
9782
|
};
|
|
9463
9783
|
} finally {
|
|
9464
|
-
if (scratchDir)
|
|
9784
|
+
if (scratchDir) rmSync9(scratchDir, { recursive: true, force: true });
|
|
9465
9785
|
}
|
|
9466
9786
|
}
|
|
9467
9787
|
function failResult(outputPath, target, source, error) {
|
|
@@ -9538,7 +9858,7 @@ async function defaultProbeAudioInfo(audioPath, signal) {
|
|
|
9538
9858
|
};
|
|
9539
9859
|
}
|
|
9540
9860
|
async function defaultRunFfmpeg(args, timeoutMs, signal) {
|
|
9541
|
-
return toStepResult(await
|
|
9861
|
+
return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
|
|
9542
9862
|
}
|
|
9543
9863
|
function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
|
|
9544
9864
|
return async (inputPath, outputPath, durationSeconds, filter) => {
|
|
@@ -9608,7 +9928,7 @@ function assertAudioNotInterrupted(failures) {
|
|
|
9608
9928
|
}
|
|
9609
9929
|
async function runAudioStage(input) {
|
|
9610
9930
|
const stageStart = Date.now();
|
|
9611
|
-
const pcmSourcePath =
|
|
9931
|
+
const pcmSourcePath = join20(input.workDir, "audio-source.wav");
|
|
9612
9932
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
9613
9933
|
try {
|
|
9614
9934
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
@@ -9628,7 +9948,7 @@ async function runAudioStage(input) {
|
|
|
9628
9948
|
}
|
|
9629
9949
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
9630
9950
|
} finally {
|
|
9631
|
-
|
|
9951
|
+
rmSync10(pcmSourcePath, { force: true });
|
|
9632
9952
|
}
|
|
9633
9953
|
}
|
|
9634
9954
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
@@ -9646,7 +9966,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9646
9966
|
assertNotAborted,
|
|
9647
9967
|
log
|
|
9648
9968
|
} = input;
|
|
9649
|
-
const audioOutputPath =
|
|
9969
|
+
const audioOutputPath = join20(workDir, MIXED_AUDIO_FILENAME);
|
|
9650
9970
|
let hasAudio = false;
|
|
9651
9971
|
let audioError;
|
|
9652
9972
|
let audioFailures;
|
|
@@ -9657,7 +9977,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9657
9977
|
audioResult = await processCompositionAudio(
|
|
9658
9978
|
audios,
|
|
9659
9979
|
projectDir,
|
|
9660
|
-
|
|
9980
|
+
join20(workDir, "audio-work"),
|
|
9661
9981
|
audioOutputPath,
|
|
9662
9982
|
duration,
|
|
9663
9983
|
abortSignal,
|
|
@@ -9726,7 +10046,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9726
10046
|
|
|
9727
10047
|
// src/services/render/stages/captureStage.ts
|
|
9728
10048
|
import { existsSync as existsSync12, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
9729
|
-
import { join as
|
|
10049
|
+
import { join as join21 } from "node:path";
|
|
9730
10050
|
import {
|
|
9731
10051
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
9732
10052
|
frameFileExtension,
|
|
@@ -9799,13 +10119,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
9799
10119
|
if (!existsSync12(workDir)) return total;
|
|
9800
10120
|
for (const attempt of readdirSync3(workDir)) {
|
|
9801
10121
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
9802
|
-
total += sampleDirectoryBytes(
|
|
10122
|
+
total += sampleDirectoryBytes(join21(workDir, attempt));
|
|
9803
10123
|
continue;
|
|
9804
10124
|
}
|
|
9805
10125
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
9806
|
-
const attemptDir =
|
|
10126
|
+
const attemptDir = join21(workDir, attempt);
|
|
9807
10127
|
for (const name of readdirSync3(attemptDir)) {
|
|
9808
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
10128
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join21(attemptDir, name));
|
|
9809
10129
|
}
|
|
9810
10130
|
}
|
|
9811
10131
|
return total;
|
|
@@ -10098,8 +10418,8 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
10098
10418
|
}
|
|
10099
10419
|
|
|
10100
10420
|
// src/services/render/stages/captureHdrStage.ts
|
|
10101
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
10102
|
-
import { join as
|
|
10421
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "node:fs";
|
|
10422
|
+
import { join as join25 } from "node:path";
|
|
10103
10423
|
import {
|
|
10104
10424
|
closeCaptureSession as closeCaptureSession7,
|
|
10105
10425
|
cloneCaptureWarnings as cloneCaptureWarnings2,
|
|
@@ -10171,7 +10491,7 @@ function createHdrImageTransferCache(options = {}) {
|
|
|
10171
10491
|
}
|
|
10172
10492
|
|
|
10173
10493
|
// src/services/render/stages/captureHdrSequentialLoop.ts
|
|
10174
|
-
import { join as
|
|
10494
|
+
import { join as join22 } from "node:path";
|
|
10175
10495
|
import {
|
|
10176
10496
|
TRANSITIONS,
|
|
10177
10497
|
crossfade
|
|
@@ -10296,7 +10616,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
10296
10616
|
);
|
|
10297
10617
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
10298
10618
|
writeFileExclusiveSync(
|
|
10299
|
-
|
|
10619
|
+
join22(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
10300
10620
|
normalCanvas
|
|
10301
10621
|
);
|
|
10302
10622
|
}
|
|
@@ -10328,7 +10648,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
10328
10648
|
}
|
|
10329
10649
|
|
|
10330
10650
|
// src/services/render/stages/captureHdrHybridLoop.ts
|
|
10331
|
-
import { join as
|
|
10651
|
+
import { join as join24 } from "node:path";
|
|
10332
10652
|
import {
|
|
10333
10653
|
TRANSITIONS as TRANSITIONS2,
|
|
10334
10654
|
closeCaptureSession as closeCaptureSession6,
|
|
@@ -10342,7 +10662,7 @@ import {
|
|
|
10342
10662
|
// src/services/shaderTransitionWorkerPool.ts
|
|
10343
10663
|
import { Worker } from "node:worker_threads";
|
|
10344
10664
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
10345
|
-
import { dirname as
|
|
10665
|
+
import { dirname as dirname9, join as join23 } from "node:path";
|
|
10346
10666
|
import { createRequire } from "node:module";
|
|
10347
10667
|
import { existsSync as existsSync13 } from "node:fs";
|
|
10348
10668
|
import { cpus as cpus2 } from "node:os";
|
|
@@ -10355,10 +10675,10 @@ function resolveWorkerEntry(explicit) {
|
|
|
10355
10675
|
const isTs = override.endsWith(".ts");
|
|
10356
10676
|
return { path: override, isTs };
|
|
10357
10677
|
}
|
|
10358
|
-
const moduleDir =
|
|
10359
|
-
const jsPath =
|
|
10678
|
+
const moduleDir = dirname9(fileURLToPath3(import.meta.url));
|
|
10679
|
+
const jsPath = join23(moduleDir, "shaderTransitionWorker.js");
|
|
10360
10680
|
if (existsSync13(jsPath)) return { path: jsPath, isTs: false };
|
|
10361
|
-
const tsPath =
|
|
10681
|
+
const tsPath = join23(moduleDir, "shaderTransitionWorker.ts");
|
|
10362
10682
|
return { path: tsPath, isTs: true };
|
|
10363
10683
|
}
|
|
10364
10684
|
function buildExecArgv(entryIsTs) {
|
|
@@ -10501,8 +10821,8 @@ async function createShaderTransitionWorkerPool(opts) {
|
|
|
10501
10821
|
if (terminated) {
|
|
10502
10822
|
throw new Error("shader-blend pool already terminated");
|
|
10503
10823
|
}
|
|
10504
|
-
return new Promise((
|
|
10505
|
-
const task = traceEnabled ? { req, resolve:
|
|
10824
|
+
return new Promise((resolve13, reject) => {
|
|
10825
|
+
const task = traceEnabled ? { req, resolve: resolve13, reject, enqueuedAtMs: Date.now(), traceId: ++nextTaskId } : { req, resolve: resolve13, reject };
|
|
10506
10826
|
const idle = slots.find((s) => !s.busy && !s.dead);
|
|
10507
10827
|
if (idle) {
|
|
10508
10828
|
queue.unshift(task);
|
|
@@ -10726,7 +11046,7 @@ async function runHybridLayeredFrameLoop(input) {
|
|
|
10726
11046
|
);
|
|
10727
11047
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
10728
11048
|
writeFileExclusiveSync(
|
|
10729
|
-
|
|
11049
|
+
join24(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
10730
11050
|
canvas
|
|
10731
11051
|
);
|
|
10732
11052
|
}
|
|
@@ -10914,9 +11234,9 @@ async function runCaptureHdrStage(input) {
|
|
|
10914
11234
|
if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
|
|
10915
11235
|
}
|
|
10916
11236
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
10917
|
-
const debugDumpDir = debugDumpEnabled ?
|
|
11237
|
+
const debugDumpDir = debugDumpEnabled ? join25(framesDir, "debug-composite") : null;
|
|
10918
11238
|
if (debugDumpDir && !existsSync14(debugDumpDir)) {
|
|
10919
|
-
|
|
11239
|
+
mkdirSync8(debugDumpDir, { recursive: true });
|
|
10920
11240
|
}
|
|
10921
11241
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
10922
11242
|
const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
|
|
@@ -11069,24 +11389,24 @@ async function runCaptureHdrStage(input) {
|
|
|
11069
11389
|
|
|
11070
11390
|
// src/services/render/stages/encodeStage.ts
|
|
11071
11391
|
import {
|
|
11072
|
-
copyFileSync as
|
|
11392
|
+
copyFileSync as copyFileSync4,
|
|
11073
11393
|
existsSync as existsSync15,
|
|
11074
|
-
mkdirSync as
|
|
11394
|
+
mkdirSync as mkdirSync9,
|
|
11075
11395
|
readdirSync as readdirSync4,
|
|
11076
|
-
readFileSync as
|
|
11077
|
-
rmSync as
|
|
11078
|
-
statSync as
|
|
11079
|
-
writeFileSync as
|
|
11396
|
+
readFileSync as readFileSync8,
|
|
11397
|
+
rmSync as rmSync11,
|
|
11398
|
+
statSync as statSync7,
|
|
11399
|
+
writeFileSync as writeFileSync5
|
|
11080
11400
|
} from "node:fs";
|
|
11081
11401
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
11082
|
-
import { dirname as
|
|
11402
|
+
import { dirname as dirname10, join as join26 } from "node:path";
|
|
11083
11403
|
import {
|
|
11084
11404
|
encodeFramesChunkedConcat,
|
|
11085
11405
|
encodeFramesFromDir,
|
|
11086
11406
|
formatFfmpegError,
|
|
11087
11407
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
|
|
11088
11408
|
resolveConfig,
|
|
11089
|
-
runFfmpeg as
|
|
11409
|
+
runFfmpeg as runFfmpeg6,
|
|
11090
11410
|
spawnStreamingEncoder as spawnStreamingEncoder4
|
|
11091
11411
|
} from "@hyperframes/engine";
|
|
11092
11412
|
import {
|
|
@@ -11162,17 +11482,17 @@ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
|
|
|
11162
11482
|
}
|
|
11163
11483
|
async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
11164
11484
|
const { outputPath } = argsInput;
|
|
11165
|
-
const result = await
|
|
11485
|
+
const result = await runFfmpeg6(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
|
|
11166
11486
|
if (!result.success) {
|
|
11167
11487
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
11168
11488
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
11169
11489
|
}
|
|
11170
|
-
const gif =
|
|
11490
|
+
const gif = readFileSync8(outputPath);
|
|
11171
11491
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
11172
11492
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
11173
11493
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
11174
11494
|
}
|
|
11175
|
-
|
|
11495
|
+
writeFileSync5(outputPath, gif);
|
|
11176
11496
|
return null;
|
|
11177
11497
|
}
|
|
11178
11498
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -11194,17 +11514,17 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
11194
11514
|
try {
|
|
11195
11515
|
const run = { signal: input.signal, timeout: input.timeout };
|
|
11196
11516
|
for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
|
|
11197
|
-
const result = await
|
|
11517
|
+
const result = await runFfmpeg6(args, run);
|
|
11198
11518
|
if (!result.success) {
|
|
11199
11519
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
11200
11520
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
11201
11521
|
}
|
|
11202
11522
|
}
|
|
11203
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
11523
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
|
|
11204
11524
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
11205
11525
|
if (failure) return failure;
|
|
11206
11526
|
}
|
|
11207
|
-
const fileSize = existsSync15(outputPath) ?
|
|
11527
|
+
const fileSize = existsSync15(outputPath) ? statSync7(outputPath).size : 0;
|
|
11208
11528
|
return {
|
|
11209
11529
|
success: true,
|
|
11210
11530
|
outputPath,
|
|
@@ -11213,7 +11533,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
11213
11533
|
fileSize
|
|
11214
11534
|
};
|
|
11215
11535
|
} finally {
|
|
11216
|
-
|
|
11536
|
+
rmSync11(input.palettePath, { force: true });
|
|
11217
11537
|
}
|
|
11218
11538
|
}
|
|
11219
11539
|
function startEncodeProgress(job, stage, onProgress) {
|
|
@@ -11285,10 +11605,10 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
11285
11605
|
const stream = await encoder;
|
|
11286
11606
|
for (let next = 0; next < totalFrames; next++) {
|
|
11287
11607
|
while (!landed.has(next) && !captureDone && !signal.aborted) {
|
|
11288
|
-
await new Promise((
|
|
11608
|
+
await new Promise((resolve13) => wake = resolve13);
|
|
11289
11609
|
}
|
|
11290
11610
|
if (signal.aborted) return;
|
|
11291
|
-
const mergedPath =
|
|
11611
|
+
const mergedPath = join26(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
11292
11612
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
11293
11613
|
landed.delete(next);
|
|
11294
11614
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -11363,7 +11683,7 @@ async function runEncodeStage(input) {
|
|
|
11363
11683
|
const stage5Start = Date.now();
|
|
11364
11684
|
if (isPngSequence) {
|
|
11365
11685
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
11366
|
-
if (!existsSync15(outputPath))
|
|
11686
|
+
if (!existsSync15(outputPath)) mkdirSync9(outputPath, { recursive: true });
|
|
11367
11687
|
const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
11368
11688
|
if (captured.length === 0) {
|
|
11369
11689
|
throw new Error(
|
|
@@ -11371,11 +11691,11 @@ async function runEncodeStage(input) {
|
|
|
11371
11691
|
);
|
|
11372
11692
|
}
|
|
11373
11693
|
captured.forEach((name, i) => {
|
|
11374
|
-
const dst =
|
|
11375
|
-
|
|
11694
|
+
const dst = join26(outputPath, formatExportFrameName(i, "png"));
|
|
11695
|
+
copyFileSync4(join26(framesDir, name), dst);
|
|
11376
11696
|
});
|
|
11377
11697
|
if (hasAudio && audioOutputPath && existsSync15(audioOutputPath)) {
|
|
11378
|
-
|
|
11698
|
+
copyFileSync4(audioOutputPath, join26(outputPath, MIXED_AUDIO_FILENAME2));
|
|
11379
11699
|
log.info(
|
|
11380
11700
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
11381
11701
|
);
|
|
@@ -11395,7 +11715,7 @@ async function runEncodeStage(input) {
|
|
|
11395
11715
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
11396
11716
|
fps: job.config.fps,
|
|
11397
11717
|
loop,
|
|
11398
|
-
palettePath:
|
|
11718
|
+
palettePath: join26(dirname10(videoOnlyPath), "gif-palette.png"),
|
|
11399
11719
|
preserveAlpha: needsAlpha,
|
|
11400
11720
|
signal: abortSignal,
|
|
11401
11721
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -11605,9 +11925,9 @@ function sampleDirectoryBytes(dir) {
|
|
|
11605
11925
|
continue;
|
|
11606
11926
|
}
|
|
11607
11927
|
for (const name of entries) {
|
|
11608
|
-
const full =
|
|
11928
|
+
const full = join27(current, name);
|
|
11609
11929
|
try {
|
|
11610
|
-
const st =
|
|
11930
|
+
const st = statSync8(full);
|
|
11611
11931
|
if (st.isDirectory()) {
|
|
11612
11932
|
stack.push(full);
|
|
11613
11933
|
} else if (st.isFile()) {
|
|
@@ -11754,8 +12074,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
11754
12074
|
const ranges = [];
|
|
11755
12075
|
let rangeStart = null;
|
|
11756
12076
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
11757
|
-
const framePath =
|
|
11758
|
-
const missing = !existsSync16(framePath) ||
|
|
12077
|
+
const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12078
|
+
const missing = !existsSync16(framePath) || statSync8(framePath).size <= 8;
|
|
11759
12079
|
if (missing && rangeStart === null) {
|
|
11760
12080
|
rangeStart = frameIndex;
|
|
11761
12081
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -11771,7 +12091,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
11771
12091
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
11772
12092
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
11773
12093
|
const batches = [];
|
|
11774
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
12094
|
+
const outputDirFor = (batchIndex, workerId) => join27(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
11775
12095
|
if (stride > 1) {
|
|
11776
12096
|
const spans = /* @__PURE__ */ new Map();
|
|
11777
12097
|
for (const range of ranges) {
|
|
@@ -11831,8 +12151,8 @@ function getNextRetryWorkerCount(currentWorkers) {
|
|
|
11831
12151
|
return Math.max(1, Math.floor(currentWorkers / 2));
|
|
11832
12152
|
}
|
|
11833
12153
|
function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
|
|
11834
|
-
if (platform2 === "win32" || platform2 === "darwin") return
|
|
11835
|
-
return
|
|
12154
|
+
if (platform2 === "win32" || platform2 === "darwin") return join27(systemTempDir, "hf-render-");
|
|
12155
|
+
return join27(dirname11(outputPath), `work-${jobId}-`);
|
|
11836
12156
|
}
|
|
11837
12157
|
var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
|
|
11838
12158
|
function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
|
|
@@ -11874,7 +12194,7 @@ The composition is too large for the available memory. To reduce memory pressure
|
|
|
11874
12194
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
11875
12195
|
let captured = 0;
|
|
11876
12196
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
11877
|
-
const framePath =
|
|
12197
|
+
const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
11878
12198
|
if (existsSync16(framePath)) captured++;
|
|
11879
12199
|
}
|
|
11880
12200
|
return captured;
|
|
@@ -11900,7 +12220,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
11900
12220
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
11901
12221
|
});
|
|
11902
12222
|
pendingTransientRetry = false;
|
|
11903
|
-
const attemptWorkDir =
|
|
12223
|
+
const attemptWorkDir = join27(options.workDir, `capture-attempt-${attempt}`);
|
|
11904
12224
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
11905
12225
|
missingRanges,
|
|
11906
12226
|
currentWorkers,
|
|
@@ -12506,18 +12826,18 @@ function deVerifyFallbackTelemetry(err) {
|
|
|
12506
12826
|
};
|
|
12507
12827
|
}
|
|
12508
12828
|
function resolveRenderDebugDir() {
|
|
12509
|
-
const producerRoot = process.env.PRODUCER_RENDERS_DIR ?
|
|
12510
|
-
return
|
|
12829
|
+
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(dirname11(fileURLToPath4(import.meta.url)), "../..");
|
|
12830
|
+
return join27(producerRoot, ".debug");
|
|
12511
12831
|
}
|
|
12512
12832
|
async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
|
|
12513
12833
|
validateHlsRenderConfig(job.config);
|
|
12514
12834
|
const debugDir = resolveRenderDebugDir();
|
|
12515
|
-
const outputDir =
|
|
12516
|
-
if (!existsSync16(outputDir))
|
|
12517
|
-
const workDir = job.config.debug ?
|
|
12835
|
+
const outputDir = dirname11(outputPath);
|
|
12836
|
+
if (!existsSync16(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
12837
|
+
const workDir = job.config.debug ? join27(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
12518
12838
|
const pipelineStart = Date.now();
|
|
12519
12839
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
12520
|
-
const logPath = job.config.debug ?
|
|
12840
|
+
const logPath = job.config.debug ? join27(workDir, "render.log") : null;
|
|
12521
12841
|
const execution = new RenderExecutionContext({
|
|
12522
12842
|
request: { renderJobId: job.id, projectDir, outputPath },
|
|
12523
12843
|
logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
|
|
@@ -12531,7 +12851,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
12531
12851
|
log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
|
|
12532
12852
|
return;
|
|
12533
12853
|
}
|
|
12534
|
-
|
|
12854
|
+
rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
12535
12855
|
});
|
|
12536
12856
|
try {
|
|
12537
12857
|
await executeRenderPipeline({
|
|
@@ -12577,7 +12897,7 @@ async function executeRenderPipeline(input) {
|
|
|
12577
12897
|
imageDecodeFailures: 0
|
|
12578
12898
|
};
|
|
12579
12899
|
let hdrPerf;
|
|
12580
|
-
const perfOutputPath =
|
|
12900
|
+
const perfOutputPath = join27(workDir, "perf-summary.json");
|
|
12581
12901
|
const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
|
|
12582
12902
|
const observability = new RenderObservabilityRecorder({
|
|
12583
12903
|
pipelineStartMs: pipelineStart,
|
|
@@ -12650,7 +12970,7 @@ async function executeRenderPipeline(input) {
|
|
|
12650
12970
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
12651
12971
|
assertNotAborted();
|
|
12652
12972
|
assertConfiguredFfmpegBinariesExist();
|
|
12653
|
-
if (!existsSync16(workDir))
|
|
12973
|
+
if (!existsSync16(workDir)) mkdirSync10(workDir, { recursive: true });
|
|
12654
12974
|
if (job.config.debug) {
|
|
12655
12975
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
12656
12976
|
}
|
|
@@ -12679,22 +12999,22 @@ async function executeRenderPipeline(input) {
|
|
|
12679
12999
|
requestedWorkers: job.config.workers ?? "auto"
|
|
12680
13000
|
});
|
|
12681
13001
|
const entryFile = job.config.entryFile || "index.html";
|
|
12682
|
-
let htmlPath =
|
|
13002
|
+
let htmlPath = join27(projectDir, entryFile);
|
|
12683
13003
|
if (!existsSync16(htmlPath)) {
|
|
12684
13004
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
12685
13005
|
}
|
|
12686
13006
|
assertNotAborted();
|
|
12687
|
-
const rawEntry =
|
|
13007
|
+
const rawEntry = readFileSync9(htmlPath, "utf-8");
|
|
12688
13008
|
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
12689
|
-
const wrapperPath =
|
|
12690
|
-
const projectIndexPath =
|
|
13009
|
+
const wrapperPath = join27(workDir, "standalone-entry.html");
|
|
13010
|
+
const projectIndexPath = join27(projectDir, "index.html");
|
|
12691
13011
|
if (!existsSync16(projectIndexPath)) {
|
|
12692
13012
|
throw new Error(
|
|
12693
13013
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
12694
13014
|
);
|
|
12695
13015
|
}
|
|
12696
13016
|
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
12697
|
-
|
|
13017
|
+
readFileSync9(projectIndexPath, "utf-8"),
|
|
12698
13018
|
entryFile,
|
|
12699
13019
|
rawEntry
|
|
12700
13020
|
);
|
|
@@ -12703,7 +13023,7 @@ async function executeRenderPipeline(input) {
|
|
|
12703
13023
|
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
12704
13024
|
);
|
|
12705
13025
|
}
|
|
12706
|
-
|
|
13026
|
+
writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
|
|
12707
13027
|
htmlPath = wrapperPath;
|
|
12708
13028
|
log.info("Extracted standalone entry from index.html host context", {
|
|
12709
13029
|
entryFile
|
|
@@ -12850,7 +13170,7 @@ async function executeRenderPipeline(input) {
|
|
|
12850
13170
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
|
|
12851
13171
|
code: "extract_video"
|
|
12852
13172
|
});
|
|
12853
|
-
const compiledDir =
|
|
13173
|
+
const compiledDir = join27(workDir, "compiled");
|
|
12854
13174
|
const extractResult = await observeRenderStage(
|
|
12855
13175
|
observability,
|
|
12856
13176
|
"video_extract",
|
|
@@ -12984,7 +13304,7 @@ async function executeRenderPipeline(input) {
|
|
|
12984
13304
|
try {
|
|
12985
13305
|
fileServer = await createFileServer({
|
|
12986
13306
|
projectDir,
|
|
12987
|
-
compiledDir:
|
|
13307
|
+
compiledDir: join27(workDir, "compiled"),
|
|
12988
13308
|
port: 0,
|
|
12989
13309
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
12990
13310
|
fps: job.config.fps
|
|
@@ -13002,8 +13322,8 @@ async function executeRenderPipeline(input) {
|
|
|
13002
13322
|
if (!activeFileServer) {
|
|
13003
13323
|
throw new Error("File server failed to initialize before frame capture");
|
|
13004
13324
|
}
|
|
13005
|
-
const framesDir =
|
|
13006
|
-
if (!existsSync16(framesDir))
|
|
13325
|
+
const framesDir = join27(workDir, "captured-frames");
|
|
13326
|
+
if (!existsSync16(framesDir)) mkdirSync10(framesDir, { recursive: true });
|
|
13007
13327
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
13008
13328
|
code: "start_browsers"
|
|
13009
13329
|
});
|
|
@@ -13474,7 +13794,7 @@ async function executeRenderPipeline(input) {
|
|
|
13474
13794
|
hls: ".mp4"
|
|
13475
13795
|
};
|
|
13476
13796
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
13477
|
-
const videoOnlyPath =
|
|
13797
|
+
const videoOnlyPath = join27(workDir, `video-only${videoExt}`);
|
|
13478
13798
|
const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
|
|
13479
13799
|
if (usePageSideCompositingForTransitions) {
|
|
13480
13800
|
activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
|
|
@@ -13727,7 +14047,7 @@ async function executeRenderPipeline(input) {
|
|
|
13727
14047
|
pageMotionBlur: job.config.pageMotionBlur,
|
|
13728
14048
|
captureQuality: captureOptions.quality
|
|
13729
14049
|
});
|
|
13730
|
-
const segmentDir = segmentDirFor(
|
|
14050
|
+
const segmentDir = segmentDirFor(join27(projectDir, "renders"), segmentPlanHash);
|
|
13731
14051
|
let completedSegments = /* @__PURE__ */ new Set();
|
|
13732
14052
|
if (job.config.resumeSegments === true) {
|
|
13733
14053
|
const existing = readSegmentManifest(segmentDir);
|
|
@@ -13742,10 +14062,10 @@ async function executeRenderPipeline(input) {
|
|
|
13742
14062
|
});
|
|
13743
14063
|
}
|
|
13744
14064
|
if (completedSegments.size === 0) {
|
|
13745
|
-
|
|
14065
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
13746
14066
|
}
|
|
13747
14067
|
} else {
|
|
13748
|
-
|
|
14068
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
13749
14069
|
}
|
|
13750
14070
|
const segmentManifest = readSegmentManifest(segmentDir) ?? {
|
|
13751
14071
|
version: 1,
|
|
@@ -13826,7 +14146,7 @@ async function executeRenderPipeline(input) {
|
|
|
13826
14146
|
);
|
|
13827
14147
|
updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
|
|
13828
14148
|
if (job.config.keepSegments !== true) {
|
|
13829
|
-
|
|
14149
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
13830
14150
|
}
|
|
13831
14151
|
} else {
|
|
13832
14152
|
capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
|
|
@@ -14098,8 +14418,8 @@ async function executeRenderPipeline(input) {
|
|
|
14098
14418
|
"capture_disk",
|
|
14099
14419
|
"drawElement capture failed; retrying with a fresh screenshot session"
|
|
14100
14420
|
);
|
|
14101
|
-
|
|
14102
|
-
|
|
14421
|
+
rmSync12(framesDir, { recursive: true, force: true });
|
|
14422
|
+
mkdirSync10(framesDir, { recursive: true });
|
|
14103
14423
|
resetCaptureAttemptProgress(job);
|
|
14104
14424
|
dedupPerfs.length = 0;
|
|
14105
14425
|
cfg.useDrawElement = false;
|
|
@@ -14275,7 +14595,7 @@ async function executeRenderPipeline(input) {
|
|
|
14275
14595
|
job.perfSummary = perfSummary;
|
|
14276
14596
|
if (job.config.debug) {
|
|
14277
14597
|
try {
|
|
14278
|
-
|
|
14598
|
+
writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
|
|
14279
14599
|
} catch (err) {
|
|
14280
14600
|
log.debug("Failed to write perf summary", {
|
|
14281
14601
|
perfOutputPath,
|
|
@@ -14285,8 +14605,8 @@ async function executeRenderPipeline(input) {
|
|
|
14285
14605
|
}
|
|
14286
14606
|
if (job.config.debug) {
|
|
14287
14607
|
if (!isPngSequence && !isHls && existsSync16(stagedOutputPath)) {
|
|
14288
|
-
const debugOutput =
|
|
14289
|
-
|
|
14608
|
+
const debugOutput = join27(workDir, `output${videoExt}`);
|
|
14609
|
+
copyFileSync5(stagedOutputPath, debugOutput);
|
|
14290
14610
|
}
|
|
14291
14611
|
}
|
|
14292
14612
|
await commitArtifactTransaction(artifactTransaction, () => {
|
|
@@ -14410,15 +14730,15 @@ import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
|
14410
14730
|
// src/services/distributed/plan.ts
|
|
14411
14731
|
import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
|
|
14412
14732
|
import {
|
|
14413
|
-
copyFileSync as
|
|
14733
|
+
copyFileSync as copyFileSync6,
|
|
14414
14734
|
cpSync as cpSync2,
|
|
14415
14735
|
existsSync as existsSync21,
|
|
14416
14736
|
linkSync,
|
|
14417
|
-
mkdirSync as
|
|
14418
|
-
rmSync as
|
|
14419
|
-
writeFileSync as
|
|
14737
|
+
mkdirSync as mkdirSync14,
|
|
14738
|
+
rmSync as rmSync14,
|
|
14739
|
+
writeFileSync as writeFileSync9
|
|
14420
14740
|
} from "node:fs";
|
|
14421
|
-
import { extname as extname6, isAbsolute as isAbsolute3, join as
|
|
14741
|
+
import { extname as extname6, isAbsolute as isAbsolute3, join as join35, relative as relative5, sep as sep2 } from "node:path";
|
|
14422
14742
|
import {
|
|
14423
14743
|
fpsToNumber as fpsToNumber10,
|
|
14424
14744
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -14428,12 +14748,12 @@ import {
|
|
|
14428
14748
|
normalizeVp9CpuUsed,
|
|
14429
14749
|
resolveConfig as resolveConfig3,
|
|
14430
14750
|
declareContentAddressedUrl,
|
|
14431
|
-
renameWithRetry as
|
|
14751
|
+
renameWithRetry as renameWithRetry7,
|
|
14432
14752
|
declareProbeFactsUrl
|
|
14433
14753
|
} from "@hyperframes/engine";
|
|
14434
14754
|
|
|
14435
14755
|
// src/services/distributed/planFrameCost.ts
|
|
14436
|
-
import { join as
|
|
14756
|
+
import { join as join30 } from "node:path";
|
|
14437
14757
|
import {
|
|
14438
14758
|
closeCaptureSession as closeCaptureSession9,
|
|
14439
14759
|
createCaptureSession as createCaptureSession9,
|
|
@@ -14443,7 +14763,7 @@ import {
|
|
|
14443
14763
|
|
|
14444
14764
|
// src/services/distributed/chunkFrames.ts
|
|
14445
14765
|
import { existsSync as existsSync18, readdirSync as readdirSync6 } from "node:fs";
|
|
14446
|
-
import { extname as extname4, join as
|
|
14766
|
+
import { extname as extname4, join as join29 } from "node:path";
|
|
14447
14767
|
import {
|
|
14448
14768
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
14449
14769
|
createFrameLookupTable as createFrameLookupTable2,
|
|
@@ -14453,8 +14773,8 @@ import {
|
|
|
14453
14773
|
|
|
14454
14774
|
// src/services/distributed/shared.ts
|
|
14455
14775
|
import { execFile as execFileCallback } from "node:child_process";
|
|
14456
|
-
import { dirname as
|
|
14457
|
-
import { existsSync as existsSync17, readFileSync as
|
|
14776
|
+
import { dirname as dirname12, join as join28 } from "node:path";
|
|
14777
|
+
import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
|
|
14458
14778
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
14459
14779
|
import { promisify as promisify2 } from "node:util";
|
|
14460
14780
|
import {
|
|
@@ -14469,7 +14789,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
14469
14789
|
}
|
|
14470
14790
|
function resolvePlanAudioPath(planDir) {
|
|
14471
14791
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
14472
|
-
const candidate =
|
|
14792
|
+
const candidate = join28(planDir, name);
|
|
14473
14793
|
if (existsSync17(candidate)) return candidate;
|
|
14474
14794
|
}
|
|
14475
14795
|
return null;
|
|
@@ -14726,13 +15046,13 @@ function buildSyntheticRenderJob(input) {
|
|
|
14726
15046
|
var cachedProducerVersion = null;
|
|
14727
15047
|
function readProducerVersion() {
|
|
14728
15048
|
if (cachedProducerVersion !== null) return cachedProducerVersion;
|
|
14729
|
-
const startDir =
|
|
15049
|
+
const startDir = dirname12(fileURLToPath5(import.meta.url));
|
|
14730
15050
|
let current = startDir;
|
|
14731
15051
|
for (let i = 0; i < 10; i++) {
|
|
14732
|
-
const candidate =
|
|
15052
|
+
const candidate = join28(current, "package.json");
|
|
14733
15053
|
if (existsSync17(candidate)) {
|
|
14734
15054
|
try {
|
|
14735
|
-
const pkg = JSON.parse(
|
|
15055
|
+
const pkg = JSON.parse(readFileSync10(candidate, "utf-8"));
|
|
14736
15056
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
14737
15057
|
cachedProducerVersion = pkg.version;
|
|
14738
15058
|
return pkg.version;
|
|
@@ -14740,7 +15060,7 @@ function readProducerVersion() {
|
|
|
14740
15060
|
} catch {
|
|
14741
15061
|
}
|
|
14742
15062
|
}
|
|
14743
|
-
const parent =
|
|
15063
|
+
const parent = dirname12(current);
|
|
14744
15064
|
if (parent === current) break;
|
|
14745
15065
|
current = parent;
|
|
14746
15066
|
}
|
|
@@ -14783,7 +15103,7 @@ function frameNumberFromFileName(name) {
|
|
|
14783
15103
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
14784
15104
|
const result = [];
|
|
14785
15105
|
for (const v of videos) {
|
|
14786
|
-
const outputDir =
|
|
15106
|
+
const outputDir = join29(planDir, "video-frames", v.videoId);
|
|
14787
15107
|
if (!existsSync18(outputDir)) {
|
|
14788
15108
|
throw new Error(
|
|
14789
15109
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
@@ -14802,7 +15122,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
14802
15122
|
if (!frameName) continue;
|
|
14803
15123
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
14804
15124
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
14805
|
-
framePaths.set(frameIndex,
|
|
15125
|
+
framePaths.set(frameIndex, join29(outputDir, frameName));
|
|
14806
15126
|
}
|
|
14807
15127
|
result.push({
|
|
14808
15128
|
videoId: v.videoId,
|
|
@@ -14892,14 +15212,14 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
14892
15212
|
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
14893
15213
|
return Promise.all(
|
|
14894
15214
|
deferred.map(async (video) => {
|
|
14895
|
-
const outputDir =
|
|
15215
|
+
const outputDir = join29(input.outputRoot, video.videoId);
|
|
14896
15216
|
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
14897
15217
|
if (indexes.length === 0) {
|
|
14898
15218
|
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
14899
15219
|
}
|
|
14900
15220
|
const range = video.deferredRange;
|
|
14901
15221
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
14902
|
-
const sourcePath = remoteUrl ??
|
|
15222
|
+
const sourcePath = remoteUrl ?? join29(input.planDir, ...range.sourcePath.split("/"));
|
|
14903
15223
|
if (remoteUrl === void 0 && !existsSync18(sourcePath)) {
|
|
14904
15224
|
throw new RenderChunkValidationError(
|
|
14905
15225
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -14948,12 +15268,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14948
15268
|
planVideos,
|
|
14949
15269
|
frames: sampled,
|
|
14950
15270
|
fps: fpsRatio,
|
|
14951
|
-
outputRoot:
|
|
15271
|
+
outputRoot: join30(workDir, "frame-cost-video-frames"),
|
|
14952
15272
|
cfg: input.cfg
|
|
14953
15273
|
})
|
|
14954
15274
|
];
|
|
14955
15275
|
const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
14956
|
-
const compiledDir =
|
|
15276
|
+
const compiledDir = join30(planDir, "compiled");
|
|
14957
15277
|
const fileServer = await createFileServer({
|
|
14958
15278
|
projectDir: compiledDir,
|
|
14959
15279
|
compiledDir,
|
|
@@ -14965,7 +15285,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14965
15285
|
try {
|
|
14966
15286
|
session = await createCaptureSession9(
|
|
14967
15287
|
fileServer.url,
|
|
14968
|
-
|
|
15288
|
+
join30(workDir, "frame-cost"),
|
|
14969
15289
|
buildChunkCaptureOptions({
|
|
14970
15290
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
14971
15291
|
encoder: input,
|
|
@@ -15062,8 +15382,8 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
15062
15382
|
}
|
|
15063
15383
|
|
|
15064
15384
|
// src/services/render/stages/freezePlan.ts
|
|
15065
|
-
import { existsSync as existsSync19, mkdirSync as
|
|
15066
|
-
import { join as
|
|
15385
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync11, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
15386
|
+
import { join as join31, relative as relative3, resolve as resolve9 } from "node:path";
|
|
15067
15387
|
|
|
15068
15388
|
// src/services/distributed/planProtocol.ts
|
|
15069
15389
|
var PLAN_SCHEMA_VERSION = 1;
|
|
@@ -15161,12 +15481,12 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
|
|
|
15161
15481
|
}
|
|
15162
15482
|
|
|
15163
15483
|
// src/services/render/stages/planHash.ts
|
|
15164
|
-
import { createHash as
|
|
15165
|
-
import { closeSync as
|
|
15484
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
15485
|
+
import { closeSync as closeSync5, createReadStream as createReadStream3, openSync as openSync5, readSync as readSync4 } from "node:fs";
|
|
15166
15486
|
var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
|
|
15167
15487
|
var FIELD_DELIMITER = Buffer.from([0]);
|
|
15168
15488
|
function computePlanHash(input) {
|
|
15169
|
-
const hash =
|
|
15489
|
+
const hash = createHash7("sha256");
|
|
15170
15490
|
hash.update(PLAN_HASH_SCHEMA_PREFIX, "utf8");
|
|
15171
15491
|
hash.update(input.compositionHtml);
|
|
15172
15492
|
hash.update(FIELD_DELIMITER);
|
|
@@ -15213,27 +15533,27 @@ function canonicalJsonStringify(value) {
|
|
|
15213
15533
|
throw new TypeError(`canonicalJsonStringify: unsupported value type ${typeof value}`);
|
|
15214
15534
|
}
|
|
15215
15535
|
function sha256Hex(bytes) {
|
|
15216
|
-
const h =
|
|
15536
|
+
const h = createHash7("sha256");
|
|
15217
15537
|
h.update(bytes);
|
|
15218
15538
|
return h.digest("hex");
|
|
15219
15539
|
}
|
|
15220
15540
|
function sha256File(path) {
|
|
15221
|
-
const hash =
|
|
15541
|
+
const hash = createHash7("sha256");
|
|
15222
15542
|
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
15223
|
-
const fd =
|
|
15543
|
+
const fd = openSync5(path, "r");
|
|
15224
15544
|
try {
|
|
15225
15545
|
let bytesRead = 0;
|
|
15226
15546
|
do {
|
|
15227
|
-
bytesRead =
|
|
15547
|
+
bytesRead = readSync4(fd, buffer, 0, buffer.byteLength, null);
|
|
15228
15548
|
if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
|
|
15229
15549
|
} while (bytesRead > 0);
|
|
15230
15550
|
} finally {
|
|
15231
|
-
|
|
15551
|
+
closeSync5(fd);
|
|
15232
15552
|
}
|
|
15233
15553
|
return hash.digest("hex");
|
|
15234
15554
|
}
|
|
15235
15555
|
async function sha256FileAsync(path) {
|
|
15236
|
-
const hash =
|
|
15556
|
+
const hash = createHash7("sha256");
|
|
15237
15557
|
for await (const chunk of createReadStream3(path, { highWaterMark: 1024 * 1024 })) {
|
|
15238
15558
|
hash.update(chunk);
|
|
15239
15559
|
}
|
|
@@ -15277,11 +15597,11 @@ function stripUndefined(value) {
|
|
|
15277
15597
|
}
|
|
15278
15598
|
function listPlanFiles(planDir) {
|
|
15279
15599
|
const results = [];
|
|
15280
|
-
const rootResolved =
|
|
15600
|
+
const rootResolved = resolve9(planDir);
|
|
15281
15601
|
function walk(dir) {
|
|
15282
15602
|
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
15283
15603
|
for (const entry of entries) {
|
|
15284
|
-
const full =
|
|
15604
|
+
const full = join31(dir, entry.name);
|
|
15285
15605
|
if (entry.isDirectory()) {
|
|
15286
15606
|
walk(full);
|
|
15287
15607
|
} else if (entry.isFile()) {
|
|
@@ -15302,7 +15622,7 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
15302
15622
|
const assets = [];
|
|
15303
15623
|
for (const file of files) {
|
|
15304
15624
|
if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
|
|
15305
|
-
compositionHtml =
|
|
15625
|
+
compositionHtml = readFileSync11(file.absolutePath);
|
|
15306
15626
|
continue;
|
|
15307
15627
|
}
|
|
15308
15628
|
if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
|
|
@@ -15322,23 +15642,23 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
15322
15642
|
function writePlanRemoteFiles(planDir, files) {
|
|
15323
15643
|
if (files.length === 0) return;
|
|
15324
15644
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
15325
|
-
|
|
15645
|
+
writeFileSync7(join31(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
15326
15646
|
}
|
|
15327
15647
|
function readPlanRemoteFiles(planDir) {
|
|
15328
|
-
const path =
|
|
15329
|
-
return existsSync19(path) ? JSON.parse(
|
|
15648
|
+
const path = join31(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
15649
|
+
return existsSync19(path) ? JSON.parse(readFileSync11(path, "utf-8")) : [];
|
|
15330
15650
|
}
|
|
15331
15651
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
15332
|
-
const planJsonPath =
|
|
15333
|
-
const encoderJsonPath =
|
|
15652
|
+
const planJsonPath = join31(planDir, "plan.json");
|
|
15653
|
+
const encoderJsonPath = join31(planDir, "meta", "encoder.json");
|
|
15334
15654
|
if (!existsSync19(planJsonPath)) {
|
|
15335
15655
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
15336
15656
|
}
|
|
15337
15657
|
if (!existsSync19(encoderJsonPath)) {
|
|
15338
15658
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
15339
15659
|
}
|
|
15340
|
-
const planJson = JSON.parse(
|
|
15341
|
-
const encoderConfigCanonicalJson =
|
|
15660
|
+
const planJson = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
|
|
15661
|
+
const encoderConfigCanonicalJson = readFileSync11(encoderJsonPath, "utf-8");
|
|
15342
15662
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
15343
15663
|
return computePlanHash({
|
|
15344
15664
|
compositionHtml,
|
|
@@ -15367,18 +15687,18 @@ async function freezePlan(input) {
|
|
|
15367
15687
|
if (!existsSync19(planDir)) {
|
|
15368
15688
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
15369
15689
|
}
|
|
15370
|
-
const metaDir =
|
|
15371
|
-
if (!existsSync19(metaDir))
|
|
15372
|
-
|
|
15373
|
-
|
|
15690
|
+
const metaDir = join31(planDir, "meta");
|
|
15691
|
+
if (!existsSync19(metaDir)) mkdirSync11(metaDir, { recursive: true });
|
|
15692
|
+
writeFileSync7(
|
|
15693
|
+
join31(metaDir, "composition.json"),
|
|
15374
15694
|
`${JSON.stringify(composition, null, 2)}
|
|
15375
15695
|
`,
|
|
15376
15696
|
"utf-8"
|
|
15377
15697
|
);
|
|
15378
15698
|
const encoderForCanonical = stripUndefined(encoder);
|
|
15379
15699
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
15380
|
-
|
|
15381
|
-
|
|
15700
|
+
writeFileSync7(join31(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
15701
|
+
writeFileSync7(join31(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
15382
15702
|
`, "utf-8");
|
|
15383
15703
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
15384
15704
|
const planHash = computePlanHash({
|
|
@@ -15402,8 +15722,8 @@ async function freezePlan(input) {
|
|
|
15402
15722
|
duration: durationSeconds,
|
|
15403
15723
|
hasAudio
|
|
15404
15724
|
};
|
|
15405
|
-
const planJsonPath =
|
|
15406
|
-
|
|
15725
|
+
const planJsonPath = join31(planDir, "plan.json");
|
|
15726
|
+
writeFileSync7(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
15407
15727
|
`, "utf-8");
|
|
15408
15728
|
return { planJsonPath, planHash };
|
|
15409
15729
|
}
|
|
@@ -15444,16 +15764,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
15444
15764
|
}
|
|
15445
15765
|
|
|
15446
15766
|
// src/services/distributed/chunkAudio.ts
|
|
15447
|
-
import { closeSync as
|
|
15448
|
-
import { writeFileSync as
|
|
15449
|
-
import { join as
|
|
15450
|
-
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as
|
|
15767
|
+
import { closeSync as closeSync6, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync12 } from "node:fs";
|
|
15768
|
+
import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
|
|
15769
|
+
import { join as join33 } from "node:path";
|
|
15770
|
+
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
15451
15771
|
|
|
15452
15772
|
// src/services/distributed/mp4Stitch.ts
|
|
15453
|
-
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as
|
|
15773
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, rmSync as rmSync13 } from "node:fs";
|
|
15454
15774
|
import { open } from "node:fs/promises";
|
|
15455
|
-
import { renameWithRetry as
|
|
15456
|
-
import { dirname as
|
|
15775
|
+
import { renameWithRetry as renameWithRetry6 } from "@hyperframes/engine";
|
|
15776
|
+
import { dirname as dirname13, join as join32 } from "node:path";
|
|
15457
15777
|
import { finished, pipeline } from "node:stream/promises";
|
|
15458
15778
|
function boxAt(src, off, at, limit) {
|
|
15459
15779
|
let size = src.readUInt32BE(off);
|
|
@@ -15466,16 +15786,16 @@ function boxAt(src, off, at, limit) {
|
|
|
15466
15786
|
return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
|
|
15467
15787
|
}
|
|
15468
15788
|
function childBoxes(buf, parent) {
|
|
15469
|
-
const
|
|
15470
|
-
for (let at = parent.bodyStart; at < parent.end; at =
|
|
15471
|
-
|
|
15789
|
+
const boxes2 = [];
|
|
15790
|
+
for (let at = parent.bodyStart; at < parent.end; at = boxes2.at(-1).end) {
|
|
15791
|
+
boxes2.push(boxAt(buf, at, at, parent.end));
|
|
15472
15792
|
}
|
|
15473
|
-
return
|
|
15793
|
+
return boxes2;
|
|
15474
15794
|
}
|
|
15475
15795
|
function findChild(buf, parent, type) {
|
|
15476
15796
|
return childBoxes(buf, parent).find((box2) => box2.type === type);
|
|
15477
15797
|
}
|
|
15478
|
-
function
|
|
15798
|
+
function child2(buf, parent, type) {
|
|
15479
15799
|
const found = findChild(buf, parent, type);
|
|
15480
15800
|
if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
|
|
15481
15801
|
return found;
|
|
@@ -15527,7 +15847,7 @@ function rows(buf, table, columns, countAt = 4) {
|
|
|
15527
15847
|
function readMediaTime(moov, trak) {
|
|
15528
15848
|
const edts = findChild(moov, trak, "edts");
|
|
15529
15849
|
if (!edts) return 0;
|
|
15530
|
-
const elst =
|
|
15850
|
+
const elst = child2(moov, edts, "elst");
|
|
15531
15851
|
const wide = moov[elst.bodyStart] === 1;
|
|
15532
15852
|
const entries = moov.readUInt32BE(elst.bodyStart + 4);
|
|
15533
15853
|
for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
|
|
@@ -15542,7 +15862,7 @@ function readSizes(moov, stsz) {
|
|
|
15542
15862
|
if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
|
|
15543
15863
|
return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
|
|
15544
15864
|
}
|
|
15545
|
-
function
|
|
15865
|
+
function readSync5(moov, stbl, sampleCount) {
|
|
15546
15866
|
const stss = findChild(moov, stbl, "stss");
|
|
15547
15867
|
if (!stss) return null;
|
|
15548
15868
|
const sync = Array(sampleCount).fill(false);
|
|
@@ -15552,10 +15872,10 @@ function readSync4(moov, stbl, sampleCount) {
|
|
|
15552
15872
|
function readChunkOffsets(moov, stbl) {
|
|
15553
15873
|
const stco = findChild(moov, stbl, "stco");
|
|
15554
15874
|
if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
|
|
15555
|
-
return rows(moov,
|
|
15875
|
+
return rows(moov, child2(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
|
|
15556
15876
|
}
|
|
15557
15877
|
function readSamplesPerChunk(moov, stbl, chunkCount) {
|
|
15558
|
-
const stsc = rows(moov,
|
|
15878
|
+
const stsc = rows(moov, child2(moov, stbl, "stsc"), 3);
|
|
15559
15879
|
return stsc.flatMap(([firstChunk, perChunk, description], i) => {
|
|
15560
15880
|
if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
|
|
15561
15881
|
const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
|
|
@@ -15563,28 +15883,28 @@ function readSamplesPerChunk(moov, stbl, chunkCount) {
|
|
|
15563
15883
|
});
|
|
15564
15884
|
}
|
|
15565
15885
|
function readTrack(moov, trak) {
|
|
15566
|
-
const mdia =
|
|
15567
|
-
const hdlr =
|
|
15568
|
-
const stbl =
|
|
15886
|
+
const mdia = child2(moov, trak, "mdia");
|
|
15887
|
+
const hdlr = child2(moov, mdia, "hdlr");
|
|
15888
|
+
const stbl = child2(moov, child2(moov, mdia, "minf"), "stbl");
|
|
15569
15889
|
if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
|
|
15570
|
-
const stts = rows(moov,
|
|
15890
|
+
const stts = rows(moov, child2(moov, stbl, "stts"), 2);
|
|
15571
15891
|
const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
|
|
15572
|
-
const sizes = readSizes(moov,
|
|
15892
|
+
const sizes = readSizes(moov, child2(moov, stbl, "stsz"));
|
|
15573
15893
|
const chunkOffsets = readChunkOffsets(moov, stbl);
|
|
15574
15894
|
const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
|
|
15575
15895
|
if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
|
|
15576
15896
|
throw new Error("[mp4Stitch] sample tables disagree on the sample count");
|
|
15577
15897
|
}
|
|
15578
|
-
const stsd =
|
|
15898
|
+
const stsd = child2(moov, stbl, "stsd");
|
|
15579
15899
|
return {
|
|
15580
15900
|
trak,
|
|
15581
15901
|
handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
|
|
15582
|
-
timescale: timescaleOf(moov,
|
|
15902
|
+
timescale: timescaleOf(moov, child2(moov, mdia, "mdhd")),
|
|
15583
15903
|
mediaTime: readMediaTime(moov, trak),
|
|
15584
15904
|
stsd: moov.subarray(stsd.start, stsd.end),
|
|
15585
15905
|
durations,
|
|
15586
15906
|
sizes,
|
|
15587
|
-
sync:
|
|
15907
|
+
sync: readSync5(moov, stbl, sizes.length),
|
|
15588
15908
|
samplesPerChunk,
|
|
15589
15909
|
chunkOffsets
|
|
15590
15910
|
};
|
|
@@ -15725,9 +16045,9 @@ function buildStitchedHeader(layouts, audio) {
|
|
|
15725
16045
|
}
|
|
15726
16046
|
const template = layouts[0].moov;
|
|
15727
16047
|
const root = boxAt(template, 0, 0, template.length);
|
|
15728
|
-
const mvhd =
|
|
16048
|
+
const mvhd = child2(template, root, "mvhd");
|
|
15729
16049
|
const movieTimescale = timescaleOf(template, mvhd);
|
|
15730
|
-
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template,
|
|
16050
|
+
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child2(template, track.trak, "tkhd")));
|
|
15731
16051
|
const audioTrackId = Math.max(...chunkTrackIds) + 1;
|
|
15732
16052
|
const buildMoov = (dataStart2) => {
|
|
15733
16053
|
const traks = plan2.map((track, t) => {
|
|
@@ -15817,18 +16137,18 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
15817
16137
|
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
15818
16138
|
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
15819
16139
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
15820
|
-
|
|
16140
|
+
mkdirSync12(dirname13(outputPath), { recursive: true });
|
|
15821
16141
|
const workDir = mkdtempSync4(`${outputPath}.stitch-`);
|
|
15822
|
-
const out = createWriteStream(
|
|
16142
|
+
const out = createWriteStream(join32(workDir, "out.mp4"));
|
|
15823
16143
|
try {
|
|
15824
16144
|
out.write(header);
|
|
15825
16145
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
15826
16146
|
out.end();
|
|
15827
16147
|
await finished(out);
|
|
15828
|
-
|
|
16148
|
+
renameWithRetry6(join32(workDir, "out.mp4"), outputPath);
|
|
15829
16149
|
} finally {
|
|
15830
16150
|
out.destroy();
|
|
15831
|
-
|
|
16151
|
+
rmSync13(workDir, { recursive: true, force: true });
|
|
15832
16152
|
}
|
|
15833
16153
|
}
|
|
15834
16154
|
|
|
@@ -15917,9 +16237,9 @@ async function writeChunkAudioSlices(input) {
|
|
|
15917
16237
|
offset += track.sizes[offsets.length - 1];
|
|
15918
16238
|
}
|
|
15919
16239
|
});
|
|
15920
|
-
const bytes =
|
|
16240
|
+
const bytes = readFileSync12(input.audioPath);
|
|
15921
16241
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
15922
|
-
|
|
16242
|
+
mkdirSync13(join33(input.planDir, SLICE_DIR), { recursive: true });
|
|
15923
16243
|
input.chunks.forEach((_, index) => {
|
|
15924
16244
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
15925
16245
|
if (!cut) return;
|
|
@@ -15927,18 +16247,18 @@ async function writeChunkAudioSlices(input) {
|
|
|
15927
16247
|
const start = offsets[cut.firstPacket];
|
|
15928
16248
|
layout.partStarts[index] = start;
|
|
15929
16249
|
const end = offsets[last] + track.sizes[last];
|
|
15930
|
-
|
|
16250
|
+
writeFileSync8(join33(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
15931
16251
|
});
|
|
15932
16252
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
15933
|
-
|
|
15934
|
-
|
|
16253
|
+
writeFileSync8(join33(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
16254
|
+
writeFileSync8(join33(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
15935
16255
|
}
|
|
15936
16256
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
15937
|
-
const layoutPath =
|
|
16257
|
+
const layoutPath = join33(planDir, SLICE_LAYOUT);
|
|
15938
16258
|
if (!existsSync20(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
15939
|
-
const layout = JSON.parse(
|
|
15940
|
-
const frame =
|
|
15941
|
-
const fd =
|
|
16259
|
+
const layout = JSON.parse(readFileSync12(layoutPath, "utf-8"));
|
|
16260
|
+
const frame = readFileSync12(join33(planDir, SLICE_FRAME));
|
|
16261
|
+
const fd = openSync6(scratchPath, "w");
|
|
15942
16262
|
try {
|
|
15943
16263
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
15944
16264
|
writeSync(
|
|
@@ -15950,12 +16270,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
15950
16270
|
);
|
|
15951
16271
|
const start = layout.partStarts[chunkIndex];
|
|
15952
16272
|
if (start !== void 0) {
|
|
15953
|
-
const part =
|
|
16273
|
+
const part = readFileSync12(join33(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
15954
16274
|
writeSync(fd, part, 0, part.length, start);
|
|
15955
16275
|
}
|
|
15956
16276
|
ftruncateSync(fd, layout.sizeBytes);
|
|
15957
16277
|
} finally {
|
|
15958
|
-
|
|
16278
|
+
closeSync6(fd);
|
|
15959
16279
|
}
|
|
15960
16280
|
return scratchPath;
|
|
15961
16281
|
}
|
|
@@ -15975,7 +16295,7 @@ async function muxChunkWithAudio(input) {
|
|
|
15975
16295
|
const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
|
|
15976
16296
|
appendRenderProvenanceArgs(args, input.outputPath);
|
|
15977
16297
|
args.push("-y", input.outputPath);
|
|
15978
|
-
const result = await
|
|
16298
|
+
const result = await runFfmpeg7(args, { signal: input.signal, timeout: input.timeoutMs });
|
|
15979
16299
|
if (!result.success) {
|
|
15980
16300
|
throw new Error(
|
|
15981
16301
|
`[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
|
|
@@ -16004,9 +16324,9 @@ async function muxChunkWithAudio(input) {
|
|
|
16004
16324
|
}
|
|
16005
16325
|
|
|
16006
16326
|
// src/services/distributed/planSize.ts
|
|
16007
|
-
import { createHash as
|
|
16327
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
16008
16328
|
import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
|
|
16009
|
-
import { extname as extname5, join as
|
|
16329
|
+
import { extname as extname5, join as join34, relative as relative4 } from "node:path";
|
|
16010
16330
|
var MAX_TOP_COMPONENTS = 10;
|
|
16011
16331
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
16012
16332
|
".aac",
|
|
@@ -16034,7 +16354,7 @@ var PLAN_ROOT_CATEGORY = {
|
|
|
16034
16354
|
"plan.json": "metadata"
|
|
16035
16355
|
};
|
|
16036
16356
|
function hashComponent(value) {
|
|
16037
|
-
return
|
|
16357
|
+
return createHash8("sha256").update(value).digest("hex").slice(0, 12);
|
|
16038
16358
|
}
|
|
16039
16359
|
function classifyFile(segments, rootKind) {
|
|
16040
16360
|
const rootName = segments[0] ?? "";
|
|
@@ -16118,7 +16438,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
16118
16438
|
return;
|
|
16119
16439
|
}
|
|
16120
16440
|
for (const entry of entries) {
|
|
16121
|
-
const full =
|
|
16441
|
+
const full = join34(dir, entry.name);
|
|
16122
16442
|
if (entry.isDirectory()) {
|
|
16123
16443
|
walkRegularFiles(full, visit);
|
|
16124
16444
|
} else if (entry.isFile()) {
|
|
@@ -16330,13 +16650,13 @@ function assertPositiveInteger2(name, value) {
|
|
|
16330
16650
|
}
|
|
16331
16651
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
16332
16652
|
"plan.json",
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16653
|
+
join35("meta", "composition.json"),
|
|
16654
|
+
join35("meta", "encoder.json"),
|
|
16655
|
+
join35("meta", "chunks.json")
|
|
16336
16656
|
];
|
|
16337
16657
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
16338
16658
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
16339
|
-
|
|
16659
|
+
rmSync14(join35(planDir, relativePath), { force: true });
|
|
16340
16660
|
}
|
|
16341
16661
|
}
|
|
16342
16662
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -16481,23 +16801,23 @@ function createVideoSourceShipper(input) {
|
|
|
16481
16801
|
const known = shipped.get(srcPath);
|
|
16482
16802
|
if (known) return known;
|
|
16483
16803
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
16484
|
-
const current = stagedRel === null ? srcPath :
|
|
16804
|
+
const current = stagedRel === null ? srcPath : join35(input.planDir, "video-frames", stagedRel);
|
|
16485
16805
|
let result = null;
|
|
16486
16806
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
16487
16807
|
const rel = pathInside(root, srcPath);
|
|
16488
|
-
if (rel !== null && (existsSync21(
|
|
16808
|
+
if (rel !== null && (existsSync21(join35(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
16489
16809
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
16490
16810
|
break;
|
|
16491
16811
|
}
|
|
16492
16812
|
}
|
|
16493
16813
|
if (result === null) {
|
|
16494
16814
|
result = `video-sources/${shipped.size}${extname6(current)}`;
|
|
16495
|
-
const destination =
|
|
16496
|
-
|
|
16815
|
+
const destination = join35(input.planDir, ...result.split("/"));
|
|
16816
|
+
mkdirSync14(join35(input.planDir, "video-sources"), { recursive: true });
|
|
16497
16817
|
try {
|
|
16498
16818
|
linkSync(current, destination);
|
|
16499
16819
|
} catch {
|
|
16500
|
-
|
|
16820
|
+
copyFileSync6(current, destination);
|
|
16501
16821
|
}
|
|
16502
16822
|
}
|
|
16503
16823
|
shipped.set(srcPath, result);
|
|
@@ -16513,7 +16833,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16513
16833
|
useGpu: false,
|
|
16514
16834
|
browserGpuMode: "software"
|
|
16515
16835
|
});
|
|
16516
|
-
if (!existsSync21(planDir))
|
|
16836
|
+
if (!existsSync21(planDir)) mkdirSync14(planDir, { recursive: true });
|
|
16517
16837
|
const log = config.logger ?? defaultLogger;
|
|
16518
16838
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
16519
16839
|
const abortSignal = config.abortSignal;
|
|
@@ -16542,15 +16862,15 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16542
16862
|
variables: config.variables
|
|
16543
16863
|
});
|
|
16544
16864
|
const entryFile = config.entryFile ?? "index.html";
|
|
16545
|
-
const htmlPath =
|
|
16865
|
+
const htmlPath = join35(projectDir, entryFile);
|
|
16546
16866
|
if (!existsSync21(htmlPath)) {
|
|
16547
16867
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
16548
16868
|
}
|
|
16549
|
-
const workDir =
|
|
16550
|
-
|
|
16551
|
-
|
|
16552
|
-
const compiledDir =
|
|
16553
|
-
|
|
16869
|
+
const workDir = join35(planDir, ".plan-work");
|
|
16870
|
+
rmSync14(workDir, { recursive: true, force: true });
|
|
16871
|
+
mkdirSync14(workDir, { recursive: true });
|
|
16872
|
+
const compiledDir = join35(workDir, "compiled");
|
|
16873
|
+
mkdirSync14(compiledDir, { recursive: true });
|
|
16554
16874
|
let stageStarted = Date.now();
|
|
16555
16875
|
cpSync2(projectDir, compiledDir, {
|
|
16556
16876
|
recursive: true,
|
|
@@ -16563,10 +16883,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16563
16883
|
}
|
|
16564
16884
|
});
|
|
16565
16885
|
logPlanStage(log, "copy", stageStarted);
|
|
16566
|
-
const finalCompiledDir =
|
|
16886
|
+
const finalCompiledDir = join35(planDir, "compiled");
|
|
16567
16887
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
16568
16888
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
16569
|
-
const remoteLocalPath = (rel) =>
|
|
16889
|
+
const remoteLocalPath = (rel) => join35(projectDir, ...rel.split("/"));
|
|
16570
16890
|
for (const [, file] of remoteFiles) {
|
|
16571
16891
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
16572
16892
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -16752,18 +17072,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16752
17072
|
let stageStarted = Date.now();
|
|
16753
17073
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
16754
17074
|
logPlanStage(log, "extract", stageStarted);
|
|
16755
|
-
const stagedVideoFrames =
|
|
16756
|
-
const videoFramesDst =
|
|
16757
|
-
if (existsSync21(videoFramesDst))
|
|
16758
|
-
const videoSourcesDir =
|
|
16759
|
-
|
|
17075
|
+
const stagedVideoFrames = join35(compiledDir, "__hyperframes_video_frames");
|
|
17076
|
+
const videoFramesDst = join35(planDir, "video-frames");
|
|
17077
|
+
if (existsSync21(videoFramesDst)) rmSync14(videoFramesDst, { recursive: true, force: true });
|
|
17078
|
+
const videoSourcesDir = join35(planDir, "video-sources");
|
|
17079
|
+
rmSync14(videoSourcesDir, { recursive: true, force: true });
|
|
16760
17080
|
if (existsSync21(stagedVideoFrames)) {
|
|
16761
|
-
|
|
17081
|
+
renameWithRetry7(stagedVideoFrames, videoFramesDst);
|
|
16762
17082
|
} else {
|
|
16763
|
-
|
|
17083
|
+
mkdirSync14(videoFramesDst, { recursive: true });
|
|
16764
17084
|
}
|
|
16765
|
-
if (existsSync21(finalCompiledDir))
|
|
16766
|
-
|
|
17085
|
+
if (existsSync21(finalCompiledDir)) rmSync14(finalCompiledDir, { recursive: true, force: true });
|
|
17086
|
+
renameWithRetry7(compiledDir, finalCompiledDir);
|
|
16767
17087
|
const shipVideoSource = createVideoSourceShipper({
|
|
16768
17088
|
planDir,
|
|
16769
17089
|
projectDir,
|
|
@@ -16792,7 +17112,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16792
17112
|
};
|
|
16793
17113
|
})
|
|
16794
17114
|
});
|
|
16795
|
-
|
|
17115
|
+
mkdirSync14(join35(planDir, "meta"), { recursive: true });
|
|
16796
17116
|
writePlanRemoteFiles(
|
|
16797
17117
|
planDir,
|
|
16798
17118
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -16801,8 +17121,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16801
17121
|
sizeBytes
|
|
16802
17122
|
}))
|
|
16803
17123
|
);
|
|
16804
|
-
|
|
16805
|
-
|
|
17124
|
+
writeFileSync9(
|
|
17125
|
+
join35(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
16806
17126
|
JSON.stringify(planVideosJson, null, 2),
|
|
16807
17127
|
"utf-8"
|
|
16808
17128
|
);
|
|
@@ -16881,9 +17201,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16881
17201
|
if (audioResult?.audioError) {
|
|
16882
17202
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
16883
17203
|
}
|
|
16884
|
-
const planAudioPath =
|
|
17204
|
+
const planAudioPath = join35(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
16885
17205
|
if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
|
|
16886
|
-
|
|
17206
|
+
renameWithRetry7(audioResult.audioOutputPath, planAudioPath);
|
|
16887
17207
|
}
|
|
16888
17208
|
if (chunksCarryAudio && existsSync21(planAudioPath)) {
|
|
16889
17209
|
if (options.sliceChunkAudio) {
|
|
@@ -16930,7 +17250,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16930
17250
|
format: config.format
|
|
16931
17251
|
};
|
|
16932
17252
|
try {
|
|
16933
|
-
|
|
17253
|
+
rmSync14(workDir, { recursive: true, force: true });
|
|
16934
17254
|
} catch (err) {
|
|
16935
17255
|
log.warn("[plan] failed to remove temp work dir", {
|
|
16936
17256
|
workDir,
|
|
@@ -17501,14 +17821,14 @@ import {
|
|
|
17501
17821
|
// src/server.ts
|
|
17502
17822
|
import {
|
|
17503
17823
|
existsSync as existsSync24,
|
|
17504
|
-
mkdirSync as
|
|
17505
|
-
statSync as
|
|
17824
|
+
mkdirSync as mkdirSync15,
|
|
17825
|
+
statSync as statSync10,
|
|
17506
17826
|
mkdtempSync as mkdtempSync5,
|
|
17507
|
-
writeFileSync as
|
|
17508
|
-
rmSync as
|
|
17827
|
+
writeFileSync as writeFileSync10,
|
|
17828
|
+
rmSync as rmSync15,
|
|
17509
17829
|
createReadStream as createReadStream5
|
|
17510
17830
|
} from "node:fs";
|
|
17511
|
-
import { resolve as
|
|
17831
|
+
import { resolve as resolve11, dirname as dirname15, join as join38 } from "node:path";
|
|
17512
17832
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
17513
17833
|
import { parseArgs } from "node:util";
|
|
17514
17834
|
import crypto from "node:crypto";
|
|
@@ -17520,14 +17840,14 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
|
|
|
17520
17840
|
// src/services/hyperframeLint.ts
|
|
17521
17841
|
import {
|
|
17522
17842
|
existsSync as existsSync22,
|
|
17523
|
-
readFileSync as
|
|
17524
|
-
statSync as
|
|
17525
|
-
openSync as
|
|
17843
|
+
readFileSync as readFileSync13,
|
|
17844
|
+
statSync as statSync9,
|
|
17845
|
+
openSync as openSync7,
|
|
17526
17846
|
fstatSync as fstatSync3,
|
|
17527
|
-
closeSync as
|
|
17847
|
+
closeSync as closeSync7,
|
|
17528
17848
|
constants as constants3
|
|
17529
17849
|
} from "node:fs";
|
|
17530
|
-
import { resolve as
|
|
17850
|
+
import { resolve as resolve10, join as join36, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
|
|
17531
17851
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
17532
17852
|
function isStringRecord(value) {
|
|
17533
17853
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -17557,30 +17877,30 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
17557
17877
|
function readEntryHtml(filePath) {
|
|
17558
17878
|
let fd;
|
|
17559
17879
|
try {
|
|
17560
|
-
fd =
|
|
17880
|
+
fd = openSync7(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
|
|
17561
17881
|
} catch (error) {
|
|
17562
17882
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
17563
17883
|
return null;
|
|
17564
|
-
if (!
|
|
17884
|
+
if (!statSync9(filePath, { throwIfNoEntry: false })?.isFile()) return null;
|
|
17565
17885
|
throw error;
|
|
17566
17886
|
}
|
|
17567
17887
|
try {
|
|
17568
17888
|
if (!fstatSync3(fd).isFile()) return null;
|
|
17569
|
-
return
|
|
17889
|
+
return readFileSync13(fd, "utf-8");
|
|
17570
17890
|
} finally {
|
|
17571
|
-
|
|
17891
|
+
closeSync7(fd);
|
|
17572
17892
|
}
|
|
17573
17893
|
}
|
|
17574
17894
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
17575
|
-
const absProjectDir =
|
|
17576
|
-
if (!existsSync22(absProjectDir) || !
|
|
17895
|
+
const absProjectDir = resolve10(projectDir);
|
|
17896
|
+
if (!existsSync22(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
17577
17897
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17578
17898
|
}
|
|
17579
17899
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
17580
17900
|
(value) => typeof value === "string" && value.trim().length > 0
|
|
17581
17901
|
);
|
|
17582
17902
|
for (const entryFile of entryCandidates) {
|
|
17583
|
-
const absoluteEntryPath =
|
|
17903
|
+
const absoluteEntryPath = resolve10(absProjectDir, entryFile);
|
|
17584
17904
|
const entryRelativePath = relative6(absProjectDir, absoluteEntryPath);
|
|
17585
17905
|
if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep3}`) || isAbsolute4(entryRelativePath)) {
|
|
17586
17906
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
@@ -17595,7 +17915,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
17595
17915
|
}
|
|
17596
17916
|
}
|
|
17597
17917
|
return {
|
|
17598
|
-
error: `No HTML entry file found in project directory: ${
|
|
17918
|
+
error: `No HTML entry file found in project directory: ${join36(absProjectDir, preferredEntryFile || "index.html")}`
|
|
17599
17919
|
};
|
|
17600
17920
|
}
|
|
17601
17921
|
function prepareHyperframeLintBody(body) {
|
|
@@ -17641,7 +17961,7 @@ async function runHyperframeLint(prepared) {
|
|
|
17641
17961
|
// src/services/healthWorker.ts
|
|
17642
17962
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
17643
17963
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
17644
|
-
import { dirname as
|
|
17964
|
+
import { dirname as dirname14, join as join37 } from "node:path";
|
|
17645
17965
|
import { existsSync as existsSync23 } from "node:fs";
|
|
17646
17966
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
17647
17967
|
async function startHealthWorker(options = {}) {
|
|
@@ -17660,12 +17980,12 @@ async function startHealthWorker(options = {}) {
|
|
|
17660
17980
|
stdout: false,
|
|
17661
17981
|
stderr: false
|
|
17662
17982
|
});
|
|
17663
|
-
await new Promise((
|
|
17983
|
+
await new Promise((resolve13, reject) => {
|
|
17664
17984
|
const onMessage = (msg) => {
|
|
17665
17985
|
if (msg?.type === "listening") {
|
|
17666
17986
|
worker.off("error", onError);
|
|
17667
17987
|
worker.off("message", onMessage);
|
|
17668
|
-
|
|
17988
|
+
resolve13();
|
|
17669
17989
|
} else if (msg?.type === "listen-error") {
|
|
17670
17990
|
worker.off("error", onError);
|
|
17671
17991
|
worker.off("message", onMessage);
|
|
@@ -17716,8 +18036,8 @@ function defaultLogger2() {
|
|
|
17716
18036
|
};
|
|
17717
18037
|
}
|
|
17718
18038
|
function resolveWorkerEntry2() {
|
|
17719
|
-
const here =
|
|
17720
|
-
const candidates = [
|
|
18039
|
+
const here = dirname14(fileURLToPath6(import.meta.url));
|
|
18040
|
+
const candidates = [join37(here, "healthWorkerThread.js"), join37(here, "healthWorkerThread.ts")];
|
|
17721
18041
|
for (const candidate of candidates) {
|
|
17722
18042
|
if (existsSync23(candidate)) return candidate;
|
|
17723
18043
|
}
|
|
@@ -17735,6 +18055,8 @@ var DEFAULT_SERVER_FPS = { num: 30, den: 1 };
|
|
|
17735
18055
|
var SSE_KEEPALIVE_MS = 3e4;
|
|
17736
18056
|
var SAFE_RENDER_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
17737
18057
|
"ASSET_MEDIA_TYPE_MISMATCH",
|
|
18058
|
+
"HEIF_IMAGE_CONVERSION_FAILED",
|
|
18059
|
+
"HEIF_TOOL_UNAVAILABLE",
|
|
17738
18060
|
"NOT_MEDIA_PAYLOAD",
|
|
17739
18061
|
"INVALID_VIDEO_METADATA",
|
|
17740
18062
|
"VIDEO_SOURCE_UNRENDERABLE",
|
|
@@ -17870,8 +18192,8 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
17870
18192
|
function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
17871
18193
|
const { input, cleanupProjectDir } = prepared;
|
|
17872
18194
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
17873
|
-
const outputDir =
|
|
17874
|
-
if (!existsSync24(outputDir))
|
|
18195
|
+
const outputDir = dirname15(absoluteOutputPath);
|
|
18196
|
+
if (!existsSync24(outputDir)) mkdirSync15(outputDir, { recursive: true });
|
|
17875
18197
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
17876
18198
|
}
|
|
17877
18199
|
function validateRenderOverrides(body) {
|
|
@@ -17911,12 +18233,12 @@ var MAX_PREVIEW_HTML_BYTES = 20 * 1024 * 1024;
|
|
|
17911
18233
|
function prepareProjectDirectory(projectDir, options) {
|
|
17912
18234
|
const candidate = nonEmptyString(projectDir);
|
|
17913
18235
|
if (!candidate) return null;
|
|
17914
|
-
const absProjectDir =
|
|
17915
|
-
if (!existsSync24(absProjectDir) || !
|
|
18236
|
+
const absProjectDir = resolve11(candidate);
|
|
18237
|
+
if (!existsSync24(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
|
|
17916
18238
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17917
18239
|
}
|
|
17918
18240
|
const entry = options.entryFile || "index.html";
|
|
17919
|
-
if (!existsSync24(
|
|
18241
|
+
if (!existsSync24(resolve11(absProjectDir, entry))) {
|
|
17920
18242
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
17921
18243
|
}
|
|
17922
18244
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -17937,8 +18259,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
17937
18259
|
}
|
|
17938
18260
|
function materializeInlineProject(html, options) {
|
|
17939
18261
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
17940
|
-
const tempProjectDir = mkdtempSync5(
|
|
17941
|
-
|
|
18262
|
+
const tempProjectDir = mkdtempSync5(join38(tempRoot, "producer-project-"));
|
|
18263
|
+
writeFileSync10(join38(tempProjectDir, "index.html"), html, "utf-8");
|
|
17942
18264
|
return {
|
|
17943
18265
|
prepared: {
|
|
17944
18266
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -17963,7 +18285,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
|
|
|
17963
18285
|
try {
|
|
17964
18286
|
return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
|
|
17965
18287
|
} catch (error) {
|
|
17966
|
-
const fallbackPath =
|
|
18288
|
+
const fallbackPath = resolve11(rendersDir, `producer-fallback-${Date.now()}.mp4`);
|
|
17967
18289
|
log.warn("Failed to resolve output path, using fallback", {
|
|
17968
18290
|
fallback: fallbackPath,
|
|
17969
18291
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -17999,7 +18321,7 @@ function createArtifactStore(ttlMs) {
|
|
|
17999
18321
|
function cleanupTempDir(dir, log) {
|
|
18000
18322
|
if (!dir) return;
|
|
18001
18323
|
try {
|
|
18002
|
-
|
|
18324
|
+
rmSync15(dir, { recursive: true, force: true });
|
|
18003
18325
|
} catch (error) {
|
|
18004
18326
|
log.warn("Failed to cleanup temp project dir", {
|
|
18005
18327
|
cleanupProjectDir: dir,
|
|
@@ -18008,7 +18330,7 @@ function cleanupTempDir(dir, log) {
|
|
|
18008
18330
|
}
|
|
18009
18331
|
}
|
|
18010
18332
|
function outputFileSize(path) {
|
|
18011
|
-
return existsSync24(path) ?
|
|
18333
|
+
return existsSync24(path) ? statSync10(path).size : 0;
|
|
18012
18334
|
}
|
|
18013
18335
|
function createBlockingProgressReporter(log, requestId) {
|
|
18014
18336
|
let lastLoggedPct = -10;
|
|
@@ -18314,7 +18636,7 @@ function createRenderHandlers(options = {}) {
|
|
|
18314
18636
|
store.delete(token);
|
|
18315
18637
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
18316
18638
|
}
|
|
18317
|
-
const stats =
|
|
18639
|
+
const stats = statSync10(artifact.path);
|
|
18318
18640
|
return new Response(createReadStream5(artifact.path), {
|
|
18319
18641
|
headers: {
|
|
18320
18642
|
"content-type": "video/mp4",
|
|
@@ -18381,7 +18703,7 @@ function startServer(options = {}) {
|
|
|
18381
18703
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
18382
18704
|
return server;
|
|
18383
18705
|
}
|
|
18384
|
-
var entryScript = process.argv[1] ?
|
|
18706
|
+
var entryScript = process.argv[1] ? resolve11(process.argv[1]) : "";
|
|
18385
18707
|
var isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
|
|
18386
18708
|
if (isPublicServerEntry) {
|
|
18387
18709
|
const { values } = parseArgs({
|
|
@@ -18406,35 +18728,35 @@ import { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes
|
|
|
18406
18728
|
|
|
18407
18729
|
// src/services/distributed/planV2.ts
|
|
18408
18730
|
import {
|
|
18409
|
-
copyFileSync as
|
|
18731
|
+
copyFileSync as copyFileSync8,
|
|
18410
18732
|
existsSync as existsSync26,
|
|
18411
18733
|
lstatSync as lstatSync3,
|
|
18412
|
-
mkdirSync as
|
|
18734
|
+
mkdirSync as mkdirSync17,
|
|
18413
18735
|
mkdtempSync as mkdtempSync7,
|
|
18414
|
-
readFileSync as
|
|
18736
|
+
readFileSync as readFileSync14,
|
|
18415
18737
|
readdirSync as readdirSync9,
|
|
18416
|
-
rmSync as
|
|
18417
|
-
statSync as
|
|
18418
|
-
writeFileSync as
|
|
18738
|
+
rmSync as rmSync17,
|
|
18739
|
+
statSync as statSync12,
|
|
18740
|
+
writeFileSync as writeFileSync12
|
|
18419
18741
|
} from "node:fs";
|
|
18420
18742
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
18421
|
-
import { dirname as
|
|
18743
|
+
import { dirname as dirname17, isAbsolute as isAbsolute5, join as join41, relative as relative7, resolve as resolve12, sep as sep4 } from "node:path";
|
|
18422
18744
|
import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
|
|
18423
|
-
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as
|
|
18745
|
+
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
18424
18746
|
|
|
18425
18747
|
// src/services/distributed/planV2Publisher.ts
|
|
18426
18748
|
import {
|
|
18427
|
-
copyFileSync as
|
|
18749
|
+
copyFileSync as copyFileSync7,
|
|
18428
18750
|
existsSync as existsSync25,
|
|
18429
18751
|
linkSync as linkSync2,
|
|
18430
|
-
mkdirSync as
|
|
18752
|
+
mkdirSync as mkdirSync16,
|
|
18431
18753
|
mkdtempSync as mkdtempSync6,
|
|
18432
|
-
rmSync as
|
|
18433
|
-
statSync as
|
|
18434
|
-
writeFileSync as
|
|
18754
|
+
rmSync as rmSync16,
|
|
18755
|
+
statSync as statSync11,
|
|
18756
|
+
writeFileSync as writeFileSync11
|
|
18435
18757
|
} from "node:fs";
|
|
18436
|
-
import { renameWithRetry as
|
|
18437
|
-
import { dirname as
|
|
18758
|
+
import { renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
18759
|
+
import { dirname as dirname16, join as join40 } from "node:path";
|
|
18438
18760
|
|
|
18439
18761
|
// src/services/distributed/planV2Errors.ts
|
|
18440
18762
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -18449,7 +18771,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
18449
18771
|
};
|
|
18450
18772
|
|
|
18451
18773
|
// src/services/distributed/planV2Layout.ts
|
|
18452
|
-
import { join as
|
|
18774
|
+
import { join as join39 } from "node:path";
|
|
18453
18775
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
18454
18776
|
function assertPlanV2Sha256(value, field) {
|
|
18455
18777
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -18459,7 +18781,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
18459
18781
|
}
|
|
18460
18782
|
function planV2BlobPath(root, sha256) {
|
|
18461
18783
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
18462
|
-
return
|
|
18784
|
+
return join39(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
18463
18785
|
}
|
|
18464
18786
|
|
|
18465
18787
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -18499,12 +18821,12 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18499
18821
|
}
|
|
18500
18822
|
this.destinationDir = destinationDir;
|
|
18501
18823
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
18502
|
-
|
|
18503
|
-
this.temporaryDir = mkdtempSync6(
|
|
18824
|
+
mkdirSync16(dirname16(destinationDir), { recursive: true });
|
|
18825
|
+
this.temporaryDir = mkdtempSync6(join40(dirname16(destinationDir), ".plan-v2-publish-"));
|
|
18504
18826
|
}
|
|
18505
18827
|
async putBlob(blob) {
|
|
18506
18828
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
18507
|
-
const sourceSize =
|
|
18829
|
+
const sourceSize = statSync11(blob.sourcePath).size;
|
|
18508
18830
|
if (sourceSize !== blob.sizeBytes) {
|
|
18509
18831
|
throw new PlanV2IntegrityError(
|
|
18510
18832
|
`published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
|
|
@@ -18512,19 +18834,19 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18512
18834
|
}
|
|
18513
18835
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
18514
18836
|
if (existsSync25(destinationPath)) return;
|
|
18515
|
-
|
|
18516
|
-
const stagingDir = mkdtempSync6(
|
|
18517
|
-
const temporaryPath =
|
|
18837
|
+
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18838
|
+
const stagingDir = mkdtempSync6(join40(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18839
|
+
const temporaryPath = join40(stagingDir, "blob");
|
|
18518
18840
|
try {
|
|
18519
18841
|
try {
|
|
18520
18842
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
18521
18843
|
} catch (error) {
|
|
18522
18844
|
if (!canFallbackToCopy(error)) throw error;
|
|
18523
|
-
|
|
18845
|
+
copyFileSync7(blob.sourcePath, temporaryPath);
|
|
18524
18846
|
}
|
|
18525
|
-
|
|
18847
|
+
renameWithRetry8(temporaryPath, destinationPath);
|
|
18526
18848
|
} finally {
|
|
18527
|
-
|
|
18849
|
+
rmSync16(stagingDir, { recursive: true, force: true });
|
|
18528
18850
|
}
|
|
18529
18851
|
}
|
|
18530
18852
|
async commitManifest(manifestBytes) {
|
|
@@ -18535,13 +18857,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18535
18857
|
);
|
|
18536
18858
|
}
|
|
18537
18859
|
}
|
|
18538
|
-
|
|
18539
|
-
|
|
18860
|
+
writeFileSync11(join40(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
18861
|
+
renameWithRetry8(this.temporaryDir, this.destinationDir);
|
|
18540
18862
|
this.#committed = true;
|
|
18541
18863
|
}
|
|
18542
18864
|
async abort() {
|
|
18543
18865
|
if (!this.#committed) {
|
|
18544
|
-
|
|
18866
|
+
rmSync16(this.temporaryDir, { recursive: true, force: true });
|
|
18545
18867
|
}
|
|
18546
18868
|
}
|
|
18547
18869
|
};
|
|
@@ -18560,7 +18882,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
18560
18882
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
18561
18883
|
}
|
|
18562
18884
|
function readJsonFile(path, label) {
|
|
18563
|
-
const contents =
|
|
18885
|
+
const contents = readFileSync14(path, "utf-8");
|
|
18564
18886
|
try {
|
|
18565
18887
|
const parsed = JSON.parse(contents);
|
|
18566
18888
|
return parsed;
|
|
@@ -18577,10 +18899,10 @@ function assertSafeRelativePath(path) {
|
|
|
18577
18899
|
}
|
|
18578
18900
|
function listFiles(root) {
|
|
18579
18901
|
const files = [];
|
|
18580
|
-
const rootResolved =
|
|
18902
|
+
const rootResolved = resolve12(root);
|
|
18581
18903
|
function walk(dir) {
|
|
18582
18904
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
18583
|
-
const absolutePath =
|
|
18905
|
+
const absolutePath = join41(dir, entry.name);
|
|
18584
18906
|
if (entry.isDirectory()) {
|
|
18585
18907
|
walk(absolutePath);
|
|
18586
18908
|
} else if (entry.isFile()) {
|
|
@@ -18607,13 +18929,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18607
18929
|
}
|
|
18608
18930
|
}
|
|
18609
18931
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18610
|
-
const videoRoot =
|
|
18932
|
+
const videoRoot = join41(executionPlanDir, "video-frames");
|
|
18611
18933
|
if (!existsSync26(videoRoot)) return;
|
|
18612
18934
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
18613
18935
|
if (!videoEntry.isDirectory()) continue;
|
|
18614
|
-
const videoDir =
|
|
18936
|
+
const videoDir = join41(videoRoot, videoEntry.name);
|
|
18615
18937
|
if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
18616
|
-
const sentinelPath =
|
|
18938
|
+
const sentinelPath = join41(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
18617
18939
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
18618
18940
|
}
|
|
18619
18941
|
}
|
|
@@ -18623,8 +18945,8 @@ function isExtractionCacheCompleteSentinelPath(path) {
|
|
|
18623
18945
|
return segments.length === 3 && segments[0] === "video-frames" && segments[1] !== "" && segments[2] === EXTRACTION_CACHE_COMPLETE_SENTINEL;
|
|
18624
18946
|
}
|
|
18625
18947
|
function resolveExtractedVideoOutputDir(planDir, videoId) {
|
|
18626
|
-
const videoRoot =
|
|
18627
|
-
const outputDir =
|
|
18948
|
+
const videoRoot = resolve12(planDir, "video-frames");
|
|
18949
|
+
const outputDir = resolve12(videoRoot, videoId);
|
|
18628
18950
|
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep4}`)) {
|
|
18629
18951
|
throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
|
|
18630
18952
|
}
|
|
@@ -18660,7 +18982,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18660
18982
|
if (video.deferredRange) {
|
|
18661
18983
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
18662
18984
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
18663
|
-
framePaths2.set(index,
|
|
18985
|
+
framePaths2.set(index, join41(outputDir, String(index)));
|
|
18664
18986
|
}
|
|
18665
18987
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
18666
18988
|
}
|
|
@@ -18668,7 +18990,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18668
18990
|
const framePaths = /* @__PURE__ */ new Map();
|
|
18669
18991
|
for (const frameName of frameNames) {
|
|
18670
18992
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
18671
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
18993
|
+
assertValidExtractionCacheCompleteSentinel(join41(outputDir, frameName));
|
|
18672
18994
|
continue;
|
|
18673
18995
|
}
|
|
18674
18996
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -18687,7 +19009,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18687
19009
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
18688
19010
|
);
|
|
18689
19011
|
}
|
|
18690
|
-
framePaths.set(frameIndex,
|
|
19012
|
+
framePaths.set(frameIndex, join41(outputDir, frameName));
|
|
18691
19013
|
}
|
|
18692
19014
|
return {
|
|
18693
19015
|
...video,
|
|
@@ -18708,11 +19030,11 @@ function parsePlanVideosJson2(value) {
|
|
|
18708
19030
|
}
|
|
18709
19031
|
}
|
|
18710
19032
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18711
|
-
const videosPath =
|
|
19033
|
+
const videosPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18712
19034
|
if (!existsSync26(videosPath)) return;
|
|
18713
19035
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18714
19036
|
for (const video of videos.extracted) {
|
|
18715
|
-
|
|
19037
|
+
mkdirSync17(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
18716
19038
|
}
|
|
18717
19039
|
}
|
|
18718
19040
|
function parseChunkSlices(value) {
|
|
@@ -18742,13 +19064,13 @@ function parseChunkSlices(value) {
|
|
|
18742
19064
|
});
|
|
18743
19065
|
}
|
|
18744
19066
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18745
|
-
const videoRoot =
|
|
19067
|
+
const videoRoot = join41(executionPlanDir, "video-frames");
|
|
18746
19068
|
const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18747
|
-
const videosPath =
|
|
19069
|
+
const videosPath = join41(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18748
19070
|
if (!existsSync26(videosPath)) {
|
|
18749
19071
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18750
19072
|
}
|
|
18751
|
-
const chunksPath =
|
|
19073
|
+
const chunksPath = join41(executionPlanDir, "meta", "chunks.json");
|
|
18752
19074
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18753
19075
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
18754
19076
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -18785,7 +19107,7 @@ function collectChunkVideoDependencies(input) {
|
|
|
18785
19107
|
for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
|
|
18786
19108
|
const globalTime = frame * input.fpsDen / input.fpsNum;
|
|
18787
19109
|
for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
|
|
18788
|
-
const path = input.deferredSources.get(videoId) ?? relative7(
|
|
19110
|
+
const path = input.deferredSources.get(videoId) ?? relative7(resolve12(input.executionPlanDir), payload.framePath).split(sep4).join("/");
|
|
18789
19111
|
assertSafeRelativePath(path);
|
|
18790
19112
|
const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
|
|
18791
19113
|
indexes.add(chunk.index);
|
|
@@ -18803,21 +19125,21 @@ function computeManifestHash(manifest) {
|
|
|
18803
19125
|
}
|
|
18804
19126
|
function writeBlob(sourcePath, destinationPath) {
|
|
18805
19127
|
if (existsSync26(destinationPath)) return;
|
|
18806
|
-
|
|
18807
|
-
const temporaryDir = mkdtempSync7(
|
|
18808
|
-
const temporaryPath =
|
|
19128
|
+
mkdirSync17(dirname17(destinationPath), { recursive: true });
|
|
19129
|
+
const temporaryDir = mkdtempSync7(join41(dirname17(destinationPath), ".plan-v2-blob-"));
|
|
19130
|
+
const temporaryPath = join41(temporaryDir, "blob");
|
|
18809
19131
|
try {
|
|
18810
|
-
|
|
18811
|
-
|
|
19132
|
+
copyFileSync8(sourcePath, temporaryPath);
|
|
19133
|
+
renameWithRetry9(temporaryPath, destinationPath);
|
|
18812
19134
|
} finally {
|
|
18813
|
-
|
|
19135
|
+
rmSync17(temporaryDir, { recursive: true, force: true });
|
|
18814
19136
|
}
|
|
18815
19137
|
}
|
|
18816
19138
|
function publishBlobFor(absolutePath, digests) {
|
|
18817
19139
|
return {
|
|
18818
19140
|
sourcePath: absolutePath,
|
|
18819
19141
|
sha256: planFileSha256(absolutePath, digests),
|
|
18820
|
-
sizeBytes:
|
|
19142
|
+
sizeBytes: statSync12(absolutePath).size
|
|
18821
19143
|
};
|
|
18822
19144
|
}
|
|
18823
19145
|
async function putBlobs(publisher, blobs) {
|
|
@@ -18841,7 +19163,7 @@ async function putTasks(tasks) {
|
|
|
18841
19163
|
if (failure) throw failure.reason;
|
|
18842
19164
|
}
|
|
18843
19165
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18844
|
-
const executionPlanPath =
|
|
19166
|
+
const executionPlanPath = join41(executionPlanDir, "plan.json");
|
|
18845
19167
|
if (!existsSync26(executionPlanPath)) {
|
|
18846
19168
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18847
19169
|
}
|
|
@@ -18869,7 +19191,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18869
19191
|
}
|
|
18870
19192
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
18871
19193
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
18872
|
-
const encoder = readJsonFile(
|
|
19194
|
+
const encoder = readJsonFile(join41(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
18873
19195
|
const files = listFiles(executionPlanDir);
|
|
18874
19196
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
18875
19197
|
for (const file of files) {
|
|
@@ -18929,21 +19251,21 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
18929
19251
|
if (publication.storedBlobs.length > 0) {
|
|
18930
19252
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
18931
19253
|
}
|
|
18932
|
-
|
|
18933
|
-
const tempDir = mkdtempSync7(
|
|
19254
|
+
mkdirSync17(dirname17(planV2Dir), { recursive: true });
|
|
19255
|
+
const tempDir = mkdtempSync7(join41(dirname17(planV2Dir), ".plan-v2-build-"));
|
|
18934
19256
|
try {
|
|
18935
19257
|
for (const blob of publication.blobs) {
|
|
18936
19258
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
18937
19259
|
}
|
|
18938
|
-
|
|
18939
|
-
|
|
19260
|
+
writeFileSync12(
|
|
19261
|
+
join41(tempDir, "plan.json"),
|
|
18940
19262
|
canonicalJsonStringify(publication.manifest),
|
|
18941
19263
|
"utf-8"
|
|
18942
19264
|
);
|
|
18943
|
-
|
|
19265
|
+
renameWithRetry9(tempDir, planV2Dir);
|
|
18944
19266
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
18945
19267
|
} catch (error) {
|
|
18946
|
-
|
|
19268
|
+
rmSync17(tempDir, { recursive: true, force: true });
|
|
18947
19269
|
throw error;
|
|
18948
19270
|
}
|
|
18949
19271
|
}
|
|
@@ -18983,7 +19305,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
|
18983
19305
|
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
18984
19306
|
}
|
|
18985
19307
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
18986
|
-
const stagingRoot = mkdtempSync7(
|
|
19308
|
+
const stagingRoot = mkdtempSync7(join41(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
|
|
18987
19309
|
const digests = /* @__PURE__ */ new Map();
|
|
18988
19310
|
try {
|
|
18989
19311
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -18994,7 +19316,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18994
19316
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
18995
19317
|
whileAudioMixes: () => putBlobs(
|
|
18996
19318
|
publisher,
|
|
18997
|
-
listFiles(
|
|
19319
|
+
listFiles(join41(stagingRoot, "compiled")).map(
|
|
18998
19320
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
18999
19321
|
)
|
|
19000
19322
|
),
|
|
@@ -19010,7 +19332,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
19010
19332
|
}
|
|
19011
19333
|
throw error;
|
|
19012
19334
|
} finally {
|
|
19013
|
-
|
|
19335
|
+
rmSync17(stagingRoot, { recursive: true, force: true });
|
|
19014
19336
|
}
|
|
19015
19337
|
}
|
|
19016
19338
|
async function planV2(projectDir, config, planV2Dir) {
|
|
@@ -19019,14 +19341,14 @@ async function planV2(projectDir, config, planV2Dir) {
|
|
|
19019
19341
|
}
|
|
19020
19342
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
19021
19343
|
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
19022
|
-
stagingParentDir:
|
|
19344
|
+
stagingParentDir: dirname17(planV2Dir)
|
|
19023
19345
|
});
|
|
19024
19346
|
return resultFromManifest(planV2Dir, manifest);
|
|
19025
19347
|
}
|
|
19026
19348
|
function resultFromManifest(planV2Dir, manifest) {
|
|
19027
19349
|
return {
|
|
19028
19350
|
planDir: planV2Dir,
|
|
19029
|
-
manifestPath:
|
|
19351
|
+
manifestPath: join41(planV2Dir, "plan.json"),
|
|
19030
19352
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
19031
19353
|
planHash: manifest.planHash,
|
|
19032
19354
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -19162,7 +19484,7 @@ function parsePlanV2Manifest(value) {
|
|
|
19162
19484
|
return parsed;
|
|
19163
19485
|
}
|
|
19164
19486
|
function readPlanV2Manifest(planV2Dir) {
|
|
19165
|
-
const manifestPath =
|
|
19487
|
+
const manifestPath = join41(planV2Dir, "plan.json");
|
|
19166
19488
|
if (!existsSync26(manifestPath)) {
|
|
19167
19489
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
19168
19490
|
}
|
|
@@ -19185,7 +19507,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
19185
19507
|
if (!existsSync26(sourcePath)) {
|
|
19186
19508
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
19187
19509
|
}
|
|
19188
|
-
const stats =
|
|
19510
|
+
const stats = statSync12(sourcePath);
|
|
19189
19511
|
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
19190
19512
|
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
19191
19513
|
}
|
|
@@ -19207,17 +19529,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19207
19529
|
artifact,
|
|
19208
19530
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
19209
19531
|
}));
|
|
19210
|
-
|
|
19211
|
-
const tempDir = mkdtempSync7(
|
|
19532
|
+
mkdirSync17(dirname17(destinationDir), { recursive: true });
|
|
19533
|
+
const tempDir = mkdtempSync7(join41(dirname17(destinationDir), ".plan-v2-materialize-"));
|
|
19212
19534
|
try {
|
|
19213
19535
|
for (const { artifact, sourcePath } of verified) {
|
|
19214
|
-
const destinationPath =
|
|
19215
|
-
|
|
19216
|
-
|
|
19536
|
+
const destinationPath = join41(tempDir, ...artifact.path.split("/"));
|
|
19537
|
+
mkdirSync17(dirname17(destinationPath), { recursive: true });
|
|
19538
|
+
copyFileSync8(sourcePath, destinationPath);
|
|
19217
19539
|
}
|
|
19218
19540
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
19219
|
-
|
|
19220
|
-
|
|
19541
|
+
writeFileSync12(
|
|
19542
|
+
join41(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
19221
19543
|
canonicalJsonStringify({
|
|
19222
19544
|
manifest,
|
|
19223
19545
|
target,
|
|
@@ -19225,9 +19547,9 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19225
19547
|
}),
|
|
19226
19548
|
"utf-8"
|
|
19227
19549
|
);
|
|
19228
|
-
|
|
19550
|
+
renameWithRetry9(tempDir, destinationDir);
|
|
19229
19551
|
} catch (error) {
|
|
19230
|
-
|
|
19552
|
+
rmSync17(tempDir, { recursive: true, force: true });
|
|
19231
19553
|
throw error;
|
|
19232
19554
|
}
|
|
19233
19555
|
return {
|
|
@@ -19240,7 +19562,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19240
19562
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
19241
19563
|
// before the container change carries the legacy name, and materializing it
|
|
19242
19564
|
// under the new one would point at a file that was never written.
|
|
19243
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
19565
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join41(destinationDir, audioArtifact.path) : null : null
|
|
19244
19566
|
};
|
|
19245
19567
|
}
|
|
19246
19568
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -19259,7 +19581,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
19259
19581
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
19260
19582
|
}
|
|
19261
19583
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
19262
|
-
const markerPath =
|
|
19584
|
+
const markerPath = join41(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
19263
19585
|
if (!existsSync26(markerPath)) return null;
|
|
19264
19586
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
19265
19587
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
@@ -19277,8 +19599,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
19277
19599
|
assertRemoteSources(remoteSources, required);
|
|
19278
19600
|
for (const artifact of required) {
|
|
19279
19601
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
19280
|
-
const path =
|
|
19281
|
-
if (!existsSync26(path) ||
|
|
19602
|
+
const path = join41(planDir, ...artifact.path.split("/"));
|
|
19603
|
+
if (!existsSync26(path) || statSync12(path).size !== artifact.sizeBytes) {
|
|
19282
19604
|
throw new PlanV2IntegrityError(
|
|
19283
19605
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
19284
19606
|
);
|
|
@@ -19291,23 +19613,23 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
19291
19613
|
}
|
|
19292
19614
|
|
|
19293
19615
|
// src/services/distributed/planV2Execution.ts
|
|
19294
|
-
import { mkdtempSync as mkdtempSync9, rmSync as
|
|
19616
|
+
import { mkdtempSync as mkdtempSync9, rmSync as rmSync20 } from "node:fs";
|
|
19295
19617
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
19296
|
-
import { join as
|
|
19618
|
+
import { join as join44 } from "node:path";
|
|
19297
19619
|
|
|
19298
19620
|
// src/services/distributed/assemble.ts
|
|
19299
19621
|
import {
|
|
19300
19622
|
cpSync as cpSync3,
|
|
19301
19623
|
existsSync as existsSync27,
|
|
19302
|
-
mkdirSync as
|
|
19624
|
+
mkdirSync as mkdirSync18,
|
|
19303
19625
|
mkdtempSync as mkdtempSync8,
|
|
19304
|
-
readFileSync as
|
|
19626
|
+
readFileSync as readFileSync15,
|
|
19305
19627
|
readdirSync as readdirSync10,
|
|
19306
|
-
rmSync as
|
|
19307
|
-
statSync as
|
|
19308
|
-
writeFileSync as
|
|
19628
|
+
rmSync as rmSync18,
|
|
19629
|
+
statSync as statSync13,
|
|
19630
|
+
writeFileSync as writeFileSync13
|
|
19309
19631
|
} from "node:fs";
|
|
19310
|
-
import { dirname as
|
|
19632
|
+
import { dirname as dirname18, join as join42 } from "node:path";
|
|
19311
19633
|
import {
|
|
19312
19634
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
19313
19635
|
describeFfmpegFailure as describeFfmpegFailure4,
|
|
@@ -19315,9 +19637,9 @@ import {
|
|
|
19315
19637
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
|
|
19316
19638
|
muxVideoWithAudio as muxVideoWithAudio2,
|
|
19317
19639
|
resolveConfig as resolveConfig6,
|
|
19318
|
-
runFfmpeg as
|
|
19640
|
+
runFfmpeg as runFfmpeg8,
|
|
19319
19641
|
appendSoftwareEncoderThreadsArg,
|
|
19320
|
-
renameWithRetry as
|
|
19642
|
+
renameWithRetry as renameWithRetry10
|
|
19321
19643
|
} from "@hyperframes/engine";
|
|
19322
19644
|
import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
|
|
19323
19645
|
function cfrRateControlArgs(locked) {
|
|
@@ -19343,13 +19665,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19343
19665
|
outputPath,
|
|
19344
19666
|
durationMs: Date.now() - start,
|
|
19345
19667
|
framesEncoded: plan2.totalFrames,
|
|
19346
|
-
fileSize:
|
|
19668
|
+
fileSize: statSync13(outputPath).size
|
|
19347
19669
|
};
|
|
19348
19670
|
}
|
|
19349
19671
|
const format = plan2.dimensions.format;
|
|
19350
19672
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
19351
|
-
if (!existsSync27(
|
|
19352
|
-
|
|
19673
|
+
if (!existsSync27(dirname18(outputPath))) {
|
|
19674
|
+
mkdirSync18(dirname18(outputPath), { recursive: true });
|
|
19353
19675
|
}
|
|
19354
19676
|
const workDir = mkdtempSync8(`${outputPath}.assemble-work-`);
|
|
19355
19677
|
const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -19371,10 +19693,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19371
19693
|
};
|
|
19372
19694
|
try {
|
|
19373
19695
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
19374
|
-
const finalPath =
|
|
19696
|
+
const finalPath = join42(workDir, `output.${format}`);
|
|
19375
19697
|
const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
|
|
19376
19698
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
19377
|
-
|
|
19699
|
+
renameWithRetry10(finalPath, outputPath);
|
|
19378
19700
|
log.info("[assemble] stage timings", {
|
|
19379
19701
|
...stageMs,
|
|
19380
19702
|
chunks: chunkPaths.length,
|
|
@@ -19382,7 +19704,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19382
19704
|
});
|
|
19383
19705
|
} finally {
|
|
19384
19706
|
try {
|
|
19385
|
-
|
|
19707
|
+
rmSync18(workDir, { recursive: true, force: true });
|
|
19386
19708
|
} catch (err) {
|
|
19387
19709
|
log.warn("[assemble] failed to remove work dir", {
|
|
19388
19710
|
workDir,
|
|
@@ -19390,7 +19712,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19390
19712
|
});
|
|
19391
19713
|
}
|
|
19392
19714
|
}
|
|
19393
|
-
const fileSize = existsSync27(outputPath) ?
|
|
19715
|
+
const fileSize = existsSync27(outputPath) ? statSync13(outputPath).size : 0;
|
|
19394
19716
|
return {
|
|
19395
19717
|
outputPath,
|
|
19396
19718
|
durationMs: Date.now() - start,
|
|
@@ -19399,18 +19721,18 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19399
19721
|
};
|
|
19400
19722
|
}
|
|
19401
19723
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
19402
|
-
const planJsonPath =
|
|
19403
|
-
const chunksJsonPath =
|
|
19724
|
+
const planJsonPath = join42(planDir, "plan.json");
|
|
19725
|
+
const chunksJsonPath = join42(planDir, "meta", "chunks.json");
|
|
19404
19726
|
if (!existsSync27(planJsonPath)) {
|
|
19405
19727
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
19406
19728
|
}
|
|
19407
|
-
const plan2 = JSON.parse(
|
|
19729
|
+
const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
|
|
19408
19730
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
19409
19731
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
19410
19732
|
if (!existsSync27(chunksJsonPath)) {
|
|
19411
19733
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
19412
19734
|
}
|
|
19413
|
-
const chunks = JSON.parse(
|
|
19735
|
+
const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
|
|
19414
19736
|
if (chunkPaths.length !== chunks.length) {
|
|
19415
19737
|
throw new Error(
|
|
19416
19738
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
@@ -19448,7 +19770,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
19448
19770
|
return args;
|
|
19449
19771
|
}
|
|
19450
19772
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
19451
|
-
const cfrOutputPath =
|
|
19773
|
+
const cfrOutputPath = join42(ctx.workDir, `cfr.${ctx.format}`);
|
|
19452
19774
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
19453
19775
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
19454
19776
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -19475,7 +19797,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
19475
19797
|
);
|
|
19476
19798
|
return;
|
|
19477
19799
|
}
|
|
19478
|
-
const paddedAudioPath =
|
|
19800
|
+
const paddedAudioPath = join42(ctx.workDir, "audio-padded.m4a");
|
|
19479
19801
|
const padTrimResult = await ctx.timed(
|
|
19480
19802
|
"audioMs",
|
|
19481
19803
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -19515,9 +19837,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
19515
19837
|
}
|
|
19516
19838
|
}
|
|
19517
19839
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
19518
|
-
const listPath =
|
|
19840
|
+
const listPath = join42(workDir, "concat-list.txt");
|
|
19519
19841
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
19520
|
-
|
|
19842
|
+
writeFileSync13(listPath, `${body}
|
|
19521
19843
|
`, "utf-8");
|
|
19522
19844
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
19523
19845
|
}
|
|
@@ -19532,7 +19854,7 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
|
|
|
19532
19854
|
};
|
|
19533
19855
|
}
|
|
19534
19856
|
async function runAssembleFfmpeg(args, step, signal, timeout) {
|
|
19535
|
-
const result = await
|
|
19857
|
+
const result = await runFfmpeg8(args, { signal, timeout });
|
|
19536
19858
|
if (!result.success) {
|
|
19537
19859
|
throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
|
|
19538
19860
|
error: describeFfmpegFailure4(result, timeout),
|
|
@@ -19553,13 +19875,13 @@ function stitchesSeparateAudio(planDir) {
|
|
|
19553
19875
|
return lockedAudio(planDir).separateAudio === true;
|
|
19554
19876
|
}
|
|
19555
19877
|
function planFormat(planDir) {
|
|
19556
|
-
const plan2 = JSON.parse(
|
|
19878
|
+
const plan2 = JSON.parse(readFileSync15(join42(planDir, "plan.json"), "utf-8"));
|
|
19557
19879
|
return plan2.dimensions.format;
|
|
19558
19880
|
}
|
|
19559
19881
|
function lockedAudio(planDir) {
|
|
19560
|
-
const encoderJsonPath =
|
|
19882
|
+
const encoderJsonPath = join42(planDir, "meta", "encoder.json");
|
|
19561
19883
|
if (!existsSync27(encoderJsonPath)) return {};
|
|
19562
|
-
return JSON.parse(
|
|
19884
|
+
return JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
19563
19885
|
}
|
|
19564
19886
|
function readCfrEncoder(planDir, format) {
|
|
19565
19887
|
if (format !== "mp4") {
|
|
@@ -19567,11 +19889,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
19567
19889
|
`[assemble] cfr=true is only supported for format="mp4" (got "${format}"). Stream-copy paths for webm and mov already produce exact avg_frame_rate; cfr re-encode is not needed.`
|
|
19568
19890
|
);
|
|
19569
19891
|
}
|
|
19570
|
-
const encoderJsonPath =
|
|
19892
|
+
const encoderJsonPath = join42(planDir, "meta", "encoder.json");
|
|
19571
19893
|
if (!existsSync27(encoderJsonPath)) {
|
|
19572
19894
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
19573
19895
|
}
|
|
19574
|
-
const encoderJson = JSON.parse(
|
|
19896
|
+
const encoderJson = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
19575
19897
|
if (encoderJson.encoder === "libx265-software") {
|
|
19576
19898
|
throw new Error(
|
|
19577
19899
|
`[assemble] cfr=true is not yet supported with codec: "h265". The cfr re-encode pass uses libx264 and would silently transcode the h265 chunks. Either disable cfr or render with codec: "h264".`
|
|
@@ -19580,11 +19902,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
19580
19902
|
return encoderJson;
|
|
19581
19903
|
}
|
|
19582
19904
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
19583
|
-
if (existsSync27(outputPath))
|
|
19584
|
-
|
|
19905
|
+
if (existsSync27(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
|
|
19906
|
+
mkdirSync18(outputPath, { recursive: true });
|
|
19585
19907
|
let globalIdx = 0;
|
|
19586
19908
|
for (const chunkDir of chunkPaths) {
|
|
19587
|
-
if (!
|
|
19909
|
+
if (!statSync13(chunkDir).isDirectory()) {
|
|
19588
19910
|
throw new Error(
|
|
19589
19911
|
`[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
|
|
19590
19912
|
);
|
|
@@ -19594,8 +19916,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19594
19916
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
19595
19917
|
}
|
|
19596
19918
|
for (const frame of frames) {
|
|
19597
|
-
const dst =
|
|
19598
|
-
cpSync3(
|
|
19919
|
+
const dst = join42(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
19920
|
+
cpSync3(join42(chunkDir, frame), dst);
|
|
19599
19921
|
globalIdx += 1;
|
|
19600
19922
|
}
|
|
19601
19923
|
}
|
|
@@ -19605,13 +19927,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19605
19927
|
);
|
|
19606
19928
|
}
|
|
19607
19929
|
if (audioPath !== null && existsSync27(audioPath)) {
|
|
19608
|
-
const sidecar =
|
|
19930
|
+
const sidecar = join42(outputPath, MIXED_AUDIO_FILENAME4);
|
|
19609
19931
|
cpSync3(audioPath, sidecar);
|
|
19610
19932
|
}
|
|
19611
19933
|
let fileSize = 0;
|
|
19612
19934
|
for (const name of readdirSync10(outputPath)) {
|
|
19613
19935
|
try {
|
|
19614
|
-
fileSize +=
|
|
19936
|
+
fileSize += statSync13(join42(outputPath, name)).size;
|
|
19615
19937
|
} catch {
|
|
19616
19938
|
}
|
|
19617
19939
|
}
|
|
@@ -19625,9 +19947,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19625
19947
|
|
|
19626
19948
|
// src/services/distributed/renderChunk.ts
|
|
19627
19949
|
import { randomBytes } from "node:crypto";
|
|
19628
|
-
import { existsSync as existsSync28, mkdirSync as
|
|
19950
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync16, readdirSync as readdirSync11, rmSync as rmSync19, writeFileSync as writeFileSync14 } from "node:fs";
|
|
19629
19951
|
import { cpus as cpus3 } from "node:os";
|
|
19630
|
-
import { join as
|
|
19952
|
+
import { join as join43 } from "node:path";
|
|
19631
19953
|
import {
|
|
19632
19954
|
assertSwiftShader,
|
|
19633
19955
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -19746,10 +20068,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
19746
20068
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
19747
20069
|
}
|
|
19748
20070
|
function hashChunkOutput(outputPath, kind) {
|
|
19749
|
-
if (kind === "file") return sha256Hex(
|
|
20071
|
+
if (kind === "file") return sha256Hex(readFileSync16(outputPath));
|
|
19750
20072
|
const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
19751
20073
|
const lines = entries.map(
|
|
19752
|
-
(name) => `${name}\0${sha256Hex(
|
|
20074
|
+
(name) => `${name}\0${sha256Hex(readFileSync16(join43(outputPath, name)))}`
|
|
19753
20075
|
);
|
|
19754
20076
|
return sha256Hex(lines.join("\0"));
|
|
19755
20077
|
}
|
|
@@ -19766,16 +20088,16 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
19766
20088
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
19767
20089
|
const start = Date.now();
|
|
19768
20090
|
const log = defaultLogger;
|
|
19769
|
-
const planJsonPath =
|
|
19770
|
-
const encoderJsonPath =
|
|
19771
|
-
const chunksJsonPath =
|
|
20091
|
+
const planJsonPath = join43(planDir, "plan.json");
|
|
20092
|
+
const encoderJsonPath = join43(planDir, "meta", "encoder.json");
|
|
20093
|
+
const chunksJsonPath = join43(planDir, "meta", "chunks.json");
|
|
19772
20094
|
if (!existsSync28(planJsonPath)) {
|
|
19773
20095
|
throw new RenderChunkValidationError(
|
|
19774
20096
|
MISSING_PLAN_ARTIFACT,
|
|
19775
20097
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
19776
20098
|
);
|
|
19777
20099
|
}
|
|
19778
|
-
const plan2 = JSON.parse(
|
|
20100
|
+
const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
|
|
19779
20101
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
19780
20102
|
const planHashStarted = Date.now();
|
|
19781
20103
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -19792,13 +20114,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19792
20114
|
);
|
|
19793
20115
|
}
|
|
19794
20116
|
}
|
|
19795
|
-
const encoder = JSON.parse(
|
|
19796
|
-
const chunks = JSON.parse(
|
|
19797
|
-
const videosJsonPath =
|
|
20117
|
+
const encoder = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
20118
|
+
const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
|
|
20119
|
+
const videosJsonPath = join43(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19798
20120
|
let planVideos = null;
|
|
19799
20121
|
if (existsSync28(videosJsonPath)) {
|
|
19800
20122
|
try {
|
|
19801
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
20123
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync16(videosJsonPath, "utf-8")));
|
|
19802
20124
|
} catch (err) {
|
|
19803
20125
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
19804
20126
|
throw new RenderChunkValidationError(
|
|
@@ -19826,7 +20148,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19826
20148
|
}
|
|
19827
20149
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
19828
20150
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
19829
|
-
const compiledDir =
|
|
20151
|
+
const compiledDir = join43(planDir, "compiled");
|
|
19830
20152
|
if (!existsSync28(compiledDir)) {
|
|
19831
20153
|
throw new RenderChunkValidationError(
|
|
19832
20154
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19883,9 +20205,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19883
20205
|
forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
|
|
19884
20206
|
};
|
|
19885
20207
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
19886
|
-
|
|
19887
|
-
const framesDir =
|
|
19888
|
-
|
|
20208
|
+
mkdirSync19(workDir, { recursive: true });
|
|
20209
|
+
const framesDir = join43(workDir, "captured-frames");
|
|
20210
|
+
mkdirSync19(framesDir, { recursive: true });
|
|
19889
20211
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
19890
20212
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
19891
20213
|
const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -19909,7 +20231,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19909
20231
|
dimensions: plan2.dimensions,
|
|
19910
20232
|
encoder,
|
|
19911
20233
|
videoCount: planVideos?.videos.length ?? 0,
|
|
19912
|
-
html:
|
|
20234
|
+
html: readFileSync16(join43(compiledDir, "index.html"), "utf-8"),
|
|
19913
20235
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
19914
20236
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
19915
20237
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -20038,10 +20360,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20038
20360
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
20039
20361
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
20040
20362
|
if (isPngSequence) {
|
|
20041
|
-
if (!existsSync28(outputChunkPath))
|
|
20363
|
+
if (!existsSync28(outputChunkPath)) mkdirSync19(outputChunkPath, { recursive: true });
|
|
20042
20364
|
} else {
|
|
20043
|
-
const outDir =
|
|
20044
|
-
if (!existsSync28(outDir))
|
|
20365
|
+
const outDir = join43(outputChunkPath, "..");
|
|
20366
|
+
if (!existsSync28(outDir)) mkdirSync19(outDir, { recursive: true });
|
|
20045
20367
|
}
|
|
20046
20368
|
const encodeInput = {
|
|
20047
20369
|
job,
|
|
@@ -20152,8 +20474,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20152
20474
|
return observedChunkCaptureMode(capturePerfs);
|
|
20153
20475
|
},
|
|
20154
20476
|
resetForScreenshotRetry: () => {
|
|
20155
|
-
|
|
20156
|
-
|
|
20477
|
+
rmSync19(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20478
|
+
mkdirSync19(framesDir, { recursive: true });
|
|
20157
20479
|
capturePerfs.length = 0;
|
|
20158
20480
|
captureAttempts.length = 0;
|
|
20159
20481
|
job.framesRendered = 0;
|
|
@@ -20195,8 +20517,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20195
20517
|
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
20196
20518
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
20197
20519
|
});
|
|
20198
|
-
|
|
20199
|
-
|
|
20520
|
+
rmSync19(videoOnlyPath, { force: true });
|
|
20521
|
+
rmSync19(audioScratchPath, { force: true });
|
|
20200
20522
|
}
|
|
20201
20523
|
encodeStageMs = Date.now() - encodeStarted;
|
|
20202
20524
|
} finally {
|
|
@@ -20243,11 +20565,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20243
20565
|
producerVersion: plan2.producerVersion,
|
|
20244
20566
|
ffmpegVersion
|
|
20245
20567
|
};
|
|
20246
|
-
|
|
20568
|
+
writeFileSync14(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
20247
20569
|
`, "utf-8");
|
|
20248
20570
|
try {
|
|
20249
|
-
|
|
20250
|
-
|
|
20571
|
+
rmSync19(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20572
|
+
rmSync19(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20251
20573
|
} catch (err) {
|
|
20252
20574
|
log.warn("[renderChunk] failed to remove work dir", {
|
|
20253
20575
|
workDir,
|
|
@@ -20276,18 +20598,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20276
20598
|
|
|
20277
20599
|
// src/services/distributed/planV2Execution.ts
|
|
20278
20600
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
20279
|
-
const workRoot = mkdtempSync9(
|
|
20280
|
-
const materializedPlanDir =
|
|
20601
|
+
const workRoot = mkdtempSync9(join44(tmpdir5(), "hf-plan-v2-chunk-"));
|
|
20602
|
+
const materializedPlanDir = join44(workRoot, "plan");
|
|
20281
20603
|
try {
|
|
20282
20604
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
20283
20605
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
20284
20606
|
} finally {
|
|
20285
|
-
|
|
20607
|
+
rmSync20(workRoot, { recursive: true, force: true });
|
|
20286
20608
|
}
|
|
20287
20609
|
}
|
|
20288
20610
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
20289
|
-
const workRoot = mkdtempSync9(
|
|
20290
|
-
const materializedPlanDir =
|
|
20611
|
+
const workRoot = mkdtempSync9(join44(tmpdir5(), "hf-plan-v2-assembler-"));
|
|
20612
|
+
const materializedPlanDir = join44(workRoot, "plan");
|
|
20291
20613
|
try {
|
|
20292
20614
|
const materialized = materializePlanV2Target(
|
|
20293
20615
|
planV2Dir,
|
|
@@ -20302,7 +20624,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
20302
20624
|
options
|
|
20303
20625
|
);
|
|
20304
20626
|
} finally {
|
|
20305
|
-
|
|
20627
|
+
rmSync20(workRoot, { recursive: true, force: true });
|
|
20306
20628
|
}
|
|
20307
20629
|
}
|
|
20308
20630
|
|
|
@@ -20310,7 +20632,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
20310
20632
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
20311
20633
|
|
|
20312
20634
|
// src/services/distributed/audioSidecar.ts
|
|
20313
|
-
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as
|
|
20635
|
+
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg9 } from "@hyperframes/engine";
|
|
20314
20636
|
var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
|
|
20315
20637
|
|
|
20316
20638
|
// src/services/distributed/probeFacts.ts
|
|
@@ -20374,6 +20696,7 @@ export {
|
|
|
20374
20696
|
localizeRemoteImageSources,
|
|
20375
20697
|
localizeRemoteMediaSources,
|
|
20376
20698
|
materializePlanV2Target,
|
|
20699
|
+
mediaProfileMatchesElementType,
|
|
20377
20700
|
normalizeErrorMessage,
|
|
20378
20701
|
parseRenderRequest,
|
|
20379
20702
|
plan,
|
|
@@ -20381,6 +20704,7 @@ export {
|
|
|
20381
20704
|
planV2WithPublisher,
|
|
20382
20705
|
prepareAnimatedGifInputs,
|
|
20383
20706
|
prepareCaptureSessionForReuse4 as prepareCaptureSessionForReuse,
|
|
20707
|
+
prepareHeifImageInputs,
|
|
20384
20708
|
prepareHyperframeLintBody,
|
|
20385
20709
|
publishPlanV2FromExecutionPlan,
|
|
20386
20710
|
publishPlanV2FromV1,
|