@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/distributed.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// src/services/distributed/plan.ts
|
|
2
2
|
import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
|
|
3
3
|
import {
|
|
4
|
-
copyFileSync as
|
|
4
|
+
copyFileSync as copyFileSync6,
|
|
5
5
|
cpSync as cpSync2,
|
|
6
6
|
existsSync as existsSync15,
|
|
7
7
|
linkSync,
|
|
8
|
-
mkdirSync as
|
|
9
|
-
rmSync as
|
|
10
|
-
writeFileSync as
|
|
8
|
+
mkdirSync as mkdirSync10,
|
|
9
|
+
rmSync as rmSync9,
|
|
10
|
+
writeFileSync as writeFileSync7
|
|
11
11
|
} from "node:fs";
|
|
12
|
-
import { extname as extname4, isAbsolute as isAbsolute3, join as
|
|
12
|
+
import { extname as extname4, isAbsolute as isAbsolute3, join as join22, relative as relative5, sep as sep2 } from "node:path";
|
|
13
13
|
import {
|
|
14
14
|
fpsToNumber as fpsToNumber10,
|
|
15
15
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
normalizeVp9CpuUsed,
|
|
20
20
|
resolveConfig as resolveConfig3,
|
|
21
21
|
declareContentAddressedUrl,
|
|
22
|
-
renameWithRetry as
|
|
22
|
+
renameWithRetry as renameWithRetry7,
|
|
23
23
|
declareProbeFactsUrl
|
|
24
24
|
} from "@hyperframes/engine";
|
|
25
25
|
|
|
@@ -66,13 +66,13 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
66
66
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
67
67
|
import {
|
|
68
68
|
existsSync as existsSync10,
|
|
69
|
-
mkdirSync as
|
|
70
|
-
readFileSync as
|
|
69
|
+
mkdirSync as mkdirSync6,
|
|
70
|
+
readFileSync as readFileSync6,
|
|
71
71
|
readdirSync as readdirSync3,
|
|
72
|
-
rmSync as
|
|
73
|
-
statSync as
|
|
74
|
-
writeFileSync as
|
|
75
|
-
copyFileSync as
|
|
72
|
+
rmSync as rmSync7,
|
|
73
|
+
statSync as statSync5,
|
|
74
|
+
writeFileSync as writeFileSync4,
|
|
75
|
+
copyFileSync as copyFileSync5,
|
|
76
76
|
appendFileSync
|
|
77
77
|
} from "fs";
|
|
78
78
|
import {
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
augmentProtocolTimeoutError,
|
|
120
120
|
augmentPageNavigationTimeoutError
|
|
121
121
|
} from "@hyperframes/engine";
|
|
122
|
-
import { join as
|
|
122
|
+
import { join as join14, dirname as dirname8, resolve as resolve7 } from "path";
|
|
123
123
|
import { randomUUID } from "crypto";
|
|
124
124
|
|
|
125
125
|
// src/services/fileServer.ts
|
|
@@ -141,6 +141,7 @@ import { Readable } from "node:stream";
|
|
|
141
141
|
import { join, extname, resolve as resolve2, sep } from "node:path";
|
|
142
142
|
import { injectScriptsAtHeadStart, injectScriptsIntoHtml } from "@hyperframes/core/compiler";
|
|
143
143
|
import { fpsToNumber } from "@hyperframes/core";
|
|
144
|
+
import { stripSdrPngColourTags } from "@hyperframes/core/compiler/png-colour-tags";
|
|
144
145
|
|
|
145
146
|
// src/services/hyperframeRuntimeLoader.ts
|
|
146
147
|
import { createHash } from "node:crypto";
|
|
@@ -218,11 +219,11 @@ function getHfEarlyStub() {
|
|
|
218
219
|
|
|
219
220
|
// src/services/fileServer.ts
|
|
220
221
|
var readFileAsync = promisify(readFile);
|
|
221
|
-
function isPathInside(
|
|
222
|
+
function isPathInside(child3, parent, options = {}) {
|
|
222
223
|
const { resolveSymlinks = false, pathModule } = options;
|
|
223
224
|
const resolveFn = pathModule?.resolve ?? resolve2;
|
|
224
225
|
const separator = pathModule?.sep ?? sep;
|
|
225
|
-
const resolvedChild = resolveFn(
|
|
226
|
+
const resolvedChild = resolveFn(child3);
|
|
226
227
|
const resolvedParent = resolveFn(parent);
|
|
227
228
|
const normalizedChild = resolveSymlinks && existsSync2(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
|
|
228
229
|
const normalizedParent = resolveSymlinks && existsSync2(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
|
|
@@ -738,6 +739,18 @@ function createFileServer(options) {
|
|
|
738
739
|
html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
|
|
739
740
|
return c.text(html, 200, { "Content-Type": contentType });
|
|
740
741
|
}
|
|
742
|
+
if (ext === ".png") {
|
|
743
|
+
const untagged = stripSdrPngColourTags(await readFileAsync(fd));
|
|
744
|
+
if (untagged) {
|
|
745
|
+
const headers = {
|
|
746
|
+
"Content-Type": contentType,
|
|
747
|
+
"Content-Length": String(untagged.length)
|
|
748
|
+
};
|
|
749
|
+
return new Response(c.req.method === "HEAD" ? null : new Uint8Array(untagged), {
|
|
750
|
+
headers
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
741
754
|
const totalSize = stat.size;
|
|
742
755
|
const rangeHeader = c.req.header("range");
|
|
743
756
|
const rangeRequest = parseRangeHeader(rangeHeader, totalSize);
|
|
@@ -765,7 +778,8 @@ function createFileServer(options) {
|
|
|
765
778
|
const stream = createReadStream(filePath, {
|
|
766
779
|
fd,
|
|
767
780
|
autoClose: true,
|
|
768
|
-
|
|
781
|
+
start: range?.start ?? 0,
|
|
782
|
+
...range ? { end: range.end } : {}
|
|
769
783
|
});
|
|
770
784
|
streamOwnsFd = true;
|
|
771
785
|
try {
|
|
@@ -779,10 +793,10 @@ function createFileServer(options) {
|
|
|
779
793
|
if (!streamOwnsFd) closeSync(fd);
|
|
780
794
|
}
|
|
781
795
|
});
|
|
782
|
-
return new Promise((
|
|
796
|
+
return new Promise((resolve10) => {
|
|
783
797
|
const connections = /* @__PURE__ */ new Set();
|
|
784
798
|
const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {
|
|
785
|
-
|
|
799
|
+
resolve10({
|
|
786
800
|
url: `http://localhost:${info.port}`,
|
|
787
801
|
port: info.port,
|
|
788
802
|
addPreHeadScript: (script) => {
|
|
@@ -1782,15 +1796,15 @@ import {
|
|
|
1782
1796
|
} from "@hyperframes/engine";
|
|
1783
1797
|
|
|
1784
1798
|
// src/services/render/stages/compileStage.ts
|
|
1785
|
-
import { join as
|
|
1799
|
+
import { join as join7 } from "node:path";
|
|
1786
1800
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
1787
1801
|
|
|
1788
1802
|
// src/services/htmlCompiler.ts
|
|
1789
1803
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
1790
|
-
import { createHash as
|
|
1791
|
-
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as
|
|
1792
|
-
import { join as
|
|
1793
|
-
import { parseHTML as
|
|
1804
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
1805
|
+
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
1806
|
+
import { join as join6, dirname as dirname5, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
|
|
1807
|
+
import { parseHTML as parseHTML5 } from "linkedom";
|
|
1794
1808
|
import {
|
|
1795
1809
|
COMPOSITION_SOURCE_URL,
|
|
1796
1810
|
compileTimingAttrs,
|
|
@@ -1828,6 +1842,8 @@ import {
|
|
|
1828
1842
|
readDeclaredDefaults,
|
|
1829
1843
|
parseHostVariableValues,
|
|
1830
1844
|
headStyleRuns,
|
|
1845
|
+
isAlwaysAppliedStyle,
|
|
1846
|
+
dedupeFontFaceRules,
|
|
1831
1847
|
inlineScriptRuns,
|
|
1832
1848
|
parsesAsScript,
|
|
1833
1849
|
styleElementsFor,
|
|
@@ -2064,7 +2080,7 @@ function readLoopOverride(el) {
|
|
|
2064
2080
|
}
|
|
2065
2081
|
return null;
|
|
2066
2082
|
}
|
|
2067
|
-
function
|
|
2083
|
+
function resolveLocalImageSourcePath(src, options) {
|
|
2068
2084
|
const trimmed = src.trim();
|
|
2069
2085
|
if (!trimmed || trimmed.startsWith("data:")) return null;
|
|
2070
2086
|
const { basePath } = splitUrlSuffix(trimmed);
|
|
@@ -2120,12 +2136,12 @@ function resolveClipWindowSeconds(img, compositionDuration) {
|
|
|
2120
2136
|
}
|
|
2121
2137
|
return null;
|
|
2122
2138
|
}
|
|
2123
|
-
function resolvePreparedPlayback(metadata,
|
|
2139
|
+
function resolvePreparedPlayback(metadata, loopOverride, windowSeconds) {
|
|
2124
2140
|
const gifDuration = metadata.durationSeconds > 0 ? metadata.durationSeconds : null;
|
|
2125
2141
|
let loopIterations = 1;
|
|
2126
|
-
if (
|
|
2142
|
+
if (resolveLoop(metadata, loopOverride)) {
|
|
2127
2143
|
loopIterations = windowSeconds != null && gifDuration != null ? Math.min(MAX_LOOP_ITERATIONS, Math.max(1, Math.ceil(windowSeconds / gifDuration))) : 1;
|
|
2128
|
-
} else if (metadata.loopCount != null && metadata.loopCount > 1) {
|
|
2144
|
+
} else if (loopOverride == null && metadata.loopCount != null && metadata.loopCount > 1) {
|
|
2129
2145
|
loopIterations = Math.min(MAX_LOOP_ITERATIONS, metadata.loopCount);
|
|
2130
2146
|
}
|
|
2131
2147
|
let padSeconds = 0;
|
|
@@ -2287,14 +2303,19 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
2287
2303
|
const img = images[i];
|
|
2288
2304
|
const src = (img.getAttribute("src") || "").trim();
|
|
2289
2305
|
if (!hasGifExtension(src)) continue;
|
|
2290
|
-
const sourcePath =
|
|
2306
|
+
const sourcePath = resolveLocalImageSourcePath(src, options);
|
|
2291
2307
|
if (!sourcePath) continue;
|
|
2292
2308
|
const bytes = readFileSync2(sourcePath);
|
|
2293
2309
|
const metadata = parseAnimatedGifMetadata(bytes);
|
|
2294
2310
|
if (!metadata?.animated) continue;
|
|
2295
|
-
const
|
|
2311
|
+
const loopOverride = readLoopOverride(img);
|
|
2312
|
+
const loop = resolveLoop(metadata, loopOverride);
|
|
2296
2313
|
const windowSeconds = resolveClipWindowSeconds(img, compositionDuration);
|
|
2297
|
-
const { loopIterations, padSeconds } = resolvePreparedPlayback(
|
|
2314
|
+
const { loopIterations, padSeconds } = resolvePreparedPlayback(
|
|
2315
|
+
metadata,
|
|
2316
|
+
loopOverride,
|
|
2317
|
+
windowSeconds
|
|
2318
|
+
);
|
|
2298
2319
|
const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
|
|
2299
2320
|
const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
|
|
2300
2321
|
const cachePath = join4(cacheDir, filename);
|
|
@@ -2330,19 +2351,74 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
2330
2351
|
};
|
|
2331
2352
|
}
|
|
2332
2353
|
|
|
2333
|
-
// src/services/
|
|
2334
|
-
import {
|
|
2335
|
-
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
2336
|
-
|
|
2337
|
-
// src/services/assetMediaType.ts
|
|
2338
|
-
import { existsSync as existsSync5 } from "node:fs";
|
|
2354
|
+
// src/services/heifImagePrep.ts
|
|
2355
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2339
2356
|
import {
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2357
|
+
closeSync as closeSync2,
|
|
2358
|
+
copyFileSync as copyFileSync3,
|
|
2359
|
+
mkdirSync as mkdirSync3,
|
|
2360
|
+
openSync as openSync2,
|
|
2361
|
+
readFileSync as readFileSync3,
|
|
2362
|
+
readSync,
|
|
2363
|
+
rmSync as rmSync3,
|
|
2364
|
+
statSync as statSync3,
|
|
2365
|
+
writeFileSync as writeFileSync2
|
|
2366
|
+
} from "node:fs";
|
|
2367
|
+
import { dirname as dirname4, join as join5, resolve as resolve5 } from "node:path";
|
|
2368
|
+
import { parseHTML as parseHTML4 } from "linkedom";
|
|
2369
|
+
import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
|
|
2370
|
+
|
|
2371
|
+
// src/services/heifRotation.ts
|
|
2372
|
+
function* boxes(buf, start, end) {
|
|
2373
|
+
let at = start;
|
|
2374
|
+
while (at + 8 <= end) {
|
|
2375
|
+
let size = buf.readUInt32BE(at);
|
|
2376
|
+
let header = 8;
|
|
2377
|
+
if (size === 1 && at + 16 <= end) {
|
|
2378
|
+
size = Number(buf.readBigUInt64BE(at + 8));
|
|
2379
|
+
header = 16;
|
|
2380
|
+
} else if (size === 0) {
|
|
2381
|
+
size = end - at;
|
|
2382
|
+
}
|
|
2383
|
+
if (size < header || at + size > end) return;
|
|
2384
|
+
yield { type: buf.toString("latin1", at + 4, at + 8), start: at + header, end: at + size };
|
|
2385
|
+
at += size;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
function child(buf, parent, type, skip = 0) {
|
|
2389
|
+
return [...boxes(buf, parent.start + skip, parent.end)].filter((box2) => box2.type === type);
|
|
2390
|
+
}
|
|
2391
|
+
function readHeifPrimaryRotation(buf) {
|
|
2392
|
+
try {
|
|
2393
|
+
const root = { type: "", start: 0, end: buf.length };
|
|
2394
|
+
const meta = child(buf, root, "meta")[0];
|
|
2395
|
+
const pitm = meta && child(buf, meta, "pitm", 4)[0];
|
|
2396
|
+
const iprp = meta && child(buf, meta, "iprp", 4)[0];
|
|
2397
|
+
const ipco = iprp && child(buf, iprp, "ipco")[0];
|
|
2398
|
+
if (!pitm || !ipco || !iprp) return 0;
|
|
2399
|
+
const primaryId = buf[pitm.start] === 0 ? buf.readUInt16BE(pitm.start + 4) : buf.readUInt32BE(pitm.start + 4);
|
|
2400
|
+
const props = [...boxes(buf, ipco.start, ipco.end)];
|
|
2401
|
+
for (const ipma of child(buf, iprp, "ipma")) {
|
|
2402
|
+
const version = buf[ipma.start];
|
|
2403
|
+
const wide = (buf[ipma.start + 3] & 1) === 1;
|
|
2404
|
+
let at = ipma.start + 8;
|
|
2405
|
+
for (let n = buf.readUInt32BE(ipma.start + 4); n > 0 && at < ipma.end; n--) {
|
|
2406
|
+
const id = version < 1 ? buf.readUInt16BE(at) : buf.readUInt32BE(at);
|
|
2407
|
+
at += version < 1 ? 2 : 4;
|
|
2408
|
+
const count = buf[at++];
|
|
2409
|
+
for (let i = 0; i < count; i++) {
|
|
2410
|
+
const index = wide ? buf.readUInt16BE(at) & 32767 : buf[at] & 127;
|
|
2411
|
+
at += wide ? 2 : 1;
|
|
2412
|
+
const prop = props[index - 1];
|
|
2413
|
+
if (id === primaryId && prop?.type === "irot")
|
|
2414
|
+
return (buf[prop.start] & 3) * 90;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
} catch {
|
|
2419
|
+
}
|
|
2420
|
+
return 0;
|
|
2421
|
+
}
|
|
2346
2422
|
|
|
2347
2423
|
// src/utils/semaphore.ts
|
|
2348
2424
|
var Semaphore = class {
|
|
@@ -2357,10 +2433,10 @@ var Semaphore = class {
|
|
|
2357
2433
|
this.active++;
|
|
2358
2434
|
return () => this.release();
|
|
2359
2435
|
}
|
|
2360
|
-
return new Promise((
|
|
2436
|
+
return new Promise((resolve10) => {
|
|
2361
2437
|
this.queue.push(() => {
|
|
2362
2438
|
this.active++;
|
|
2363
|
-
|
|
2439
|
+
resolve10(() => this.release());
|
|
2364
2440
|
});
|
|
2365
2441
|
});
|
|
2366
2442
|
}
|
|
@@ -2391,7 +2467,229 @@ async function withMediaProbeSlot(operation) {
|
|
|
2391
2467
|
}
|
|
2392
2468
|
}
|
|
2393
2469
|
|
|
2470
|
+
// src/services/heifImagePrep.ts
|
|
2471
|
+
var PREPARED_HEIF_SUBDIR = "_heif_images";
|
|
2472
|
+
var CACHE_SCHEMA2 = "hfheif-v1";
|
|
2473
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
2474
|
+
var HEIF_IMAGE_CONVERSION_FAILED = "HEIF_IMAGE_CONVERSION_FAILED";
|
|
2475
|
+
var HEIF_TOOL_UNAVAILABLE = "HEIF_TOOL_UNAVAILABLE";
|
|
2476
|
+
var HEIF_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "hevc", "hevx", "heim", "heis", "mif1", "msf1"]);
|
|
2477
|
+
var AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
|
|
2478
|
+
var DATA_URL_RE = /^data:image\/(?:heic|heif)(?:-sequence)?;base64,(.*)$/is;
|
|
2479
|
+
var HeifImageConversionError = class extends Error {
|
|
2480
|
+
code = HEIF_IMAGE_CONVERSION_FAILED;
|
|
2481
|
+
owner = "user";
|
|
2482
|
+
retryable = false;
|
|
2483
|
+
constructor(label, reason) {
|
|
2484
|
+
super(
|
|
2485
|
+
`Could not convert HEIC/HEIF image ${label} to JPEG: ${reason}. Re-export the photo as JPEG or PNG and update its src.`
|
|
2486
|
+
);
|
|
2487
|
+
this.name = "HeifImageConversionError";
|
|
2488
|
+
}
|
|
2489
|
+
};
|
|
2490
|
+
var HeifToolUnavailableError = class extends Error {
|
|
2491
|
+
code = HEIF_TOOL_UNAVAILABLE;
|
|
2492
|
+
owner = "system";
|
|
2493
|
+
retryable = false;
|
|
2494
|
+
constructor(label, reason) {
|
|
2495
|
+
super(
|
|
2496
|
+
`Could not convert HEIC/HEIF image ${label}: ${reason}. Converting HEIC needs ffmpeg and ffprobe 7.0 or newer with the hevc decoder.`
|
|
2497
|
+
);
|
|
2498
|
+
this.name = "HeifToolUnavailableError";
|
|
2499
|
+
}
|
|
2500
|
+
};
|
|
2501
|
+
var TOOL_FAILURE_RE = /(?:ffmpeg|ffprobe) not found|ENOENT|Option not found|Unrecognized option|Unknown decoder|Decoder \S+ not found/i;
|
|
2502
|
+
function isHeifBytes(head) {
|
|
2503
|
+
const bytes = Buffer.from(head.buffer, head.byteOffset, head.byteLength);
|
|
2504
|
+
if (bytes.length < 16 || bytes.toString("ascii", 4, 8) !== "ftyp") return false;
|
|
2505
|
+
const boxEnd = Math.min(bytes.readUInt32BE(0), bytes.length);
|
|
2506
|
+
if (boxEnd < 16) return false;
|
|
2507
|
+
const brands = [bytes.toString("ascii", 8, 12)];
|
|
2508
|
+
for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
|
|
2509
|
+
brands.push(bytes.toString("ascii", offset, offset + 4));
|
|
2510
|
+
}
|
|
2511
|
+
return !brands.some((b) => AVIF_BRANDS.has(b)) && brands.some((b) => HEIF_BRANDS.has(b));
|
|
2512
|
+
}
|
|
2513
|
+
function readHead(path) {
|
|
2514
|
+
let fd;
|
|
2515
|
+
try {
|
|
2516
|
+
fd = openSync2(path, "r");
|
|
2517
|
+
const buf = Buffer.alloc(4096);
|
|
2518
|
+
return buf.subarray(0, readSync(fd, buf, 0, buf.length, 0));
|
|
2519
|
+
} catch {
|
|
2520
|
+
return null;
|
|
2521
|
+
} finally {
|
|
2522
|
+
if (fd !== void 0) closeSync2(fd);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
async function probeJson(path, withGroups) {
|
|
2526
|
+
const stdout = await runFfprobe(
|
|
2527
|
+
path,
|
|
2528
|
+
withGroups ? ["-show_stream_groups", "-show_streams", "-of", "json"] : ["-show_streams", "-of", "json"]
|
|
2529
|
+
);
|
|
2530
|
+
return JSON.parse(stdout);
|
|
2531
|
+
}
|
|
2532
|
+
var ROTATION_FILTER = {
|
|
2533
|
+
0: "",
|
|
2534
|
+
90: "transpose=2",
|
|
2535
|
+
180: "hflip,vflip",
|
|
2536
|
+
270: "transpose=1"
|
|
2537
|
+
};
|
|
2538
|
+
function primaryGrid(probed) {
|
|
2539
|
+
const groups = probed.stream_groups ?? [];
|
|
2540
|
+
return (groups.find((g) => g.disposition?.default === 1) ?? groups[0])?.components?.[0];
|
|
2541
|
+
}
|
|
2542
|
+
async function convertHeifToJpeg(request) {
|
|
2543
|
+
const probed = await probeJson(request.inputPath, true);
|
|
2544
|
+
const grid = primaryGrid(probed);
|
|
2545
|
+
const rotation = readHeifPrimaryRotation(readFileSync3(request.inputPath));
|
|
2546
|
+
const filters = [];
|
|
2547
|
+
let inputs = [];
|
|
2548
|
+
let size = probed.streams?.[0];
|
|
2549
|
+
if (grid?.subcomponents?.length) {
|
|
2550
|
+
size = grid;
|
|
2551
|
+
const layout = grid.subcomponents.map(
|
|
2552
|
+
(t) => `${t.tile_horizontal_offset}_${t.tile_vertical_offset}`
|
|
2553
|
+
);
|
|
2554
|
+
inputs = grid.subcomponents.map((t) => `[0:${t.stream_index}]`);
|
|
2555
|
+
filters.push(
|
|
2556
|
+
`${inputs.join("")}xstack=inputs=${inputs.length}:layout=${layout.join("|")}`,
|
|
2557
|
+
`crop=${grid.width}:${grid.height}:${grid.horizontal_offset ?? 0}:${grid.vertical_offset ?? 0}`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
if (!size?.width || !size.height) throw new Error("no declared image size");
|
|
2561
|
+
if (rotation && grid) filters.push(ROTATION_FILTER[rotation]);
|
|
2562
|
+
const mapArgs = filters.length > 0 ? ["-filter_complex", `${filters.join(",")}[v]`, "-map", "[v]"] : [];
|
|
2563
|
+
const result = await runFfmpeg3(
|
|
2564
|
+
[
|
|
2565
|
+
"-hide_banner",
|
|
2566
|
+
"-loglevel",
|
|
2567
|
+
"error",
|
|
2568
|
+
"-i",
|
|
2569
|
+
request.inputPath,
|
|
2570
|
+
...mapArgs,
|
|
2571
|
+
"-frames:v",
|
|
2572
|
+
"1",
|
|
2573
|
+
"-q:v",
|
|
2574
|
+
"2",
|
|
2575
|
+
"-f",
|
|
2576
|
+
"mjpeg",
|
|
2577
|
+
"-y",
|
|
2578
|
+
request.outputPath
|
|
2579
|
+
],
|
|
2580
|
+
{ timeout: request.timeoutMs ?? DEFAULT_TIMEOUT_MS }
|
|
2581
|
+
);
|
|
2582
|
+
if (!result.success) {
|
|
2583
|
+
throw result.error ?? new Error(`ffmpeg exited ${result.exitCode}: ${result.stderr.slice(-300)}`);
|
|
2584
|
+
}
|
|
2585
|
+
const out = (await probeJson(request.outputPath, false)).streams?.[0];
|
|
2586
|
+
const same = out?.width === size.width && out.height === size.height;
|
|
2587
|
+
const swapped = out?.width === size.height && out.height === size.width;
|
|
2588
|
+
if (!same && !swapped) {
|
|
2589
|
+
throw new Error(
|
|
2590
|
+
`wrong size: decoded ${out?.width}x${out?.height}, file declares ${size.width}x${size.height}`
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
function resolveImageSource(src, options) {
|
|
2595
|
+
const data = DATA_URL_RE.exec(src);
|
|
2596
|
+
if (data) {
|
|
2597
|
+
const bytes = Buffer.from(data[1], "base64");
|
|
2598
|
+
return isHeifBytes(bytes.subarray(0, 4096)) ? { path: null, bytes, label: '"inline image"' } : null;
|
|
2599
|
+
}
|
|
2600
|
+
const path = resolveLocalImageSourcePath(src, options);
|
|
2601
|
+
const head = path ? readHead(path) : null;
|
|
2602
|
+
if (!path || !head || !isHeifBytes(head)) return null;
|
|
2603
|
+
return { path, bytes: readFileSync3(path), label: `"${splitUrlSuffix(src).basePath}"` };
|
|
2604
|
+
}
|
|
2605
|
+
function isUsableFile2(path) {
|
|
2606
|
+
try {
|
|
2607
|
+
return statSync3(path).size > 0;
|
|
2608
|
+
} catch {
|
|
2609
|
+
return false;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
2613
|
+
mkdirSync3(dirname4(cachePath), { recursive: true });
|
|
2614
|
+
mkdirSync3(dirname4(outputPath), { recursive: true });
|
|
2615
|
+
if (!isUsableFile2(cachePath)) {
|
|
2616
|
+
const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
2617
|
+
const inlinePath = `${tmpPath}.heic`;
|
|
2618
|
+
const inputPath = source.path ?? inlinePath;
|
|
2619
|
+
try {
|
|
2620
|
+
if (!source.path) writeFileSync2(inlinePath, source.bytes);
|
|
2621
|
+
await (options.transcode ?? convertHeifToJpeg)({
|
|
2622
|
+
inputPath,
|
|
2623
|
+
outputPath: tmpPath,
|
|
2624
|
+
timeoutMs: options.timeoutMs
|
|
2625
|
+
});
|
|
2626
|
+
if (!isUsableFile2(tmpPath)) throw new Error("conversion produced an empty file");
|
|
2627
|
+
renameWithRetry4(tmpPath, cachePath);
|
|
2628
|
+
} catch (error) {
|
|
2629
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2630
|
+
throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
|
|
2631
|
+
} finally {
|
|
2632
|
+
rmSync3(tmpPath, { force: true });
|
|
2633
|
+
rmSync3(inlinePath, { force: true });
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
if (resolve5(outputPath) !== resolve5(cachePath) && !isUsableFile2(outputPath)) {
|
|
2637
|
+
copyFileSync3(cachePath, outputPath);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
async function prepareHeifImageInputs(html, options) {
|
|
2641
|
+
const outputDir = options.outputDir ?? join5(options.downloadDir, PREPARED_HEIF_SUBDIR);
|
|
2642
|
+
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
|
|
2643
|
+
const cacheDir = options.cacheDir ?? outputDir;
|
|
2644
|
+
const preparedAssets = /* @__PURE__ */ new Map();
|
|
2645
|
+
const preparedImages = [];
|
|
2646
|
+
if (!/<img\b/i.test(html)) return { html, preparedAssets, preparedImages };
|
|
2647
|
+
const { document: document2 } = parseHTML4(html);
|
|
2648
|
+
const found = [];
|
|
2649
|
+
for (const img of Array.from(document2.querySelectorAll("img[src]"))) {
|
|
2650
|
+
const src = (img.getAttribute("src") || "").trim();
|
|
2651
|
+
const source = src ? resolveImageSource(src, options) : null;
|
|
2652
|
+
if (!source) continue;
|
|
2653
|
+
const hash = createHash3("sha256").update(CACHE_SCHEMA2).update("\0").update(source.bytes).digest("hex");
|
|
2654
|
+
found.push({ img, src, source, filename: `${CACHE_SCHEMA2}-${hash.slice(0, 24)}.jpg` });
|
|
2655
|
+
}
|
|
2656
|
+
if (found.length === 0) return { html, preparedAssets, preparedImages };
|
|
2657
|
+
const unique = new Map(found.map((item) => [item.filename, item.source]));
|
|
2658
|
+
const failed = /* @__PURE__ */ new Set();
|
|
2659
|
+
await Promise.all(
|
|
2660
|
+
[...unique].map(
|
|
2661
|
+
([filename, source]) => withMediaProbeSlot(
|
|
2662
|
+
() => ensurePreparedJpeg(source, join5(cacheDir, filename), join5(outputDir, filename), options)
|
|
2663
|
+
).catch((error) => {
|
|
2664
|
+
if (!options.onFailure) throw error;
|
|
2665
|
+
failed.add(filename);
|
|
2666
|
+
options.onFailure(error);
|
|
2667
|
+
})
|
|
2668
|
+
)
|
|
2669
|
+
);
|
|
2670
|
+
for (const { img, src, filename } of found) {
|
|
2671
|
+
if (failed.has(filename)) continue;
|
|
2672
|
+
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
2673
|
+
img.setAttribute("src", outputSrc);
|
|
2674
|
+
preparedAssets.set(outputSrc, join5(outputDir, filename));
|
|
2675
|
+
preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join5(outputDir, filename) });
|
|
2676
|
+
}
|
|
2677
|
+
return { html: document2.toString(), preparedAssets, preparedImages };
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
// src/services/htmlCompiler.ts
|
|
2681
|
+
import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
|
|
2682
|
+
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
2683
|
+
|
|
2394
2684
|
// src/services/assetMediaType.ts
|
|
2685
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2686
|
+
import {
|
|
2687
|
+
fingerprintElementId,
|
|
2688
|
+
isNotMediaPayload,
|
|
2689
|
+
NotMediaPayloadError,
|
|
2690
|
+
probeMediaProfile,
|
|
2691
|
+
resolveProjectRelativeSrc as resolveProjectRelativeSrc2
|
|
2692
|
+
} from "@hyperframes/engine";
|
|
2395
2693
|
var ASSET_MEDIA_TYPE_MISMATCH = "ASSET_MEDIA_TYPE_MISMATCH";
|
|
2396
2694
|
var AssetMediaTypeMismatchError = class extends Error {
|
|
2397
2695
|
code = ASSET_MEDIA_TYPE_MISMATCH;
|
|
@@ -2501,7 +2799,7 @@ function logRemoteDownloadTelemetry(event) {
|
|
|
2501
2799
|
}
|
|
2502
2800
|
var INFERRED_MEDIA_DURATION_ATTR = "data-hf-inferred-duration";
|
|
2503
2801
|
function parseSubCompHtmlForValidity(html) {
|
|
2504
|
-
return
|
|
2802
|
+
return parseHTML5(html).document;
|
|
2505
2803
|
}
|
|
2506
2804
|
function endsWithSourceUrl(code) {
|
|
2507
2805
|
const trimmed = code.trimEnd();
|
|
@@ -2509,7 +2807,7 @@ function endsWithSourceUrl(code) {
|
|
|
2509
2807
|
return lastLine.startsWith("//# sourceURL=") || lastLine.startsWith("//@ sourceURL=");
|
|
2510
2808
|
}
|
|
2511
2809
|
function prepareCompositionScripts(html) {
|
|
2512
|
-
const { document: document2 } =
|
|
2810
|
+
const { document: document2 } = parseHTML5(html);
|
|
2513
2811
|
for (const el of document2.querySelectorAll("script:not([src])")) {
|
|
2514
2812
|
const isModule = (el.getAttribute("type") || "").trim().toLowerCase() === "module";
|
|
2515
2813
|
const runsAsScript = isModule || isJavaScriptType(el);
|
|
@@ -2546,14 +2844,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
|
|
|
2546
2844
|
}
|
|
2547
2845
|
};
|
|
2548
2846
|
function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
|
|
2549
|
-
const { document: document2 } =
|
|
2847
|
+
const { document: document2 } = parseHTML5(html);
|
|
2550
2848
|
const hosts = [...document2.querySelectorAll("[data-composition-src]")];
|
|
2551
2849
|
const problems = [];
|
|
2552
2850
|
for (const el of hosts) {
|
|
2553
2851
|
const srcPath = el.getAttribute("data-composition-src");
|
|
2554
2852
|
if (!srcPath) continue;
|
|
2555
2853
|
if (isUnresolvedAssetPlaceholder(srcPath)) continue;
|
|
2556
|
-
const filePath =
|
|
2854
|
+
const filePath = resolve6(projectDir, srcPath);
|
|
2557
2855
|
if (visited.has(filePath)) continue;
|
|
2558
2856
|
const read = readProjectFile(filePath);
|
|
2559
2857
|
if (read.kind === "missing") {
|
|
@@ -2609,7 +2907,7 @@ function runtimeRoot(document2) {
|
|
|
2609
2907
|
}
|
|
2610
2908
|
function detectRenderModeHints(html, pageMotionBlur) {
|
|
2611
2909
|
const reasons = [];
|
|
2612
|
-
const { document: document2 } =
|
|
2910
|
+
const { document: document2 } = parseHTML5(html);
|
|
2613
2911
|
if (document2.querySelector("canvas[layoutsubtree]")) {
|
|
2614
2912
|
reasons.push({
|
|
2615
2913
|
code: "htmlInCanvas",
|
|
@@ -2651,7 +2949,7 @@ function detectThreeDTransformUsage(html) {
|
|
|
2651
2949
|
}
|
|
2652
2950
|
var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
|
|
2653
2951
|
function detectAncestorBackgroundImage(html) {
|
|
2654
|
-
const { document: document2 } =
|
|
2952
|
+
const { document: document2 } = parseHTML5(html);
|
|
2655
2953
|
const root = document2.querySelector("[data-composition-id]");
|
|
2656
2954
|
if (!root) return false;
|
|
2657
2955
|
const ancestors = [];
|
|
@@ -2698,7 +2996,7 @@ function detectShaderTransitionUsage(html) {
|
|
|
2698
2996
|
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
|
|
2699
2997
|
let filePath = src;
|
|
2700
2998
|
if (isHttpUrl(src)) {
|
|
2701
|
-
if (!existsSync6(downloadDir))
|
|
2999
|
+
if (!existsSync6(downloadDir)) mkdirSync4(downloadDir, { recursive: true });
|
|
2702
3000
|
try {
|
|
2703
3001
|
filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
|
|
2704
3002
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -2708,7 +3006,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
2708
3006
|
}
|
|
2709
3007
|
} else {
|
|
2710
3008
|
filePath = decodedUrlPath(src);
|
|
2711
|
-
if (!filePath.startsWith("/")) filePath =
|
|
3009
|
+
if (!filePath.startsWith("/")) filePath = join6(baseDir, filePath);
|
|
2712
3010
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
2713
3011
|
}
|
|
2714
3012
|
if (!isHttpUrl(filePath) && !existsSync6(filePath)) {
|
|
@@ -2765,7 +3063,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
2765
3063
|
function markInferredVariableMediaDurations(html, unresolvedMedia) {
|
|
2766
3064
|
const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
|
|
2767
3065
|
if (unresolvedIds.size === 0) return html;
|
|
2768
|
-
const { document: document2 } =
|
|
3066
|
+
const { document: document2 } = parseHTML5(html);
|
|
2769
3067
|
let changed = false;
|
|
2770
3068
|
for (const element of document2.querySelectorAll("video[data-var-src], audio[data-var-src]")) {
|
|
2771
3069
|
if (!unresolvedIds.has(element.id)) continue;
|
|
@@ -2836,13 +3134,13 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
|
|
|
2836
3134
|
}
|
|
2837
3135
|
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
|
|
2838
3136
|
const subCompositions = /* @__PURE__ */ new Map();
|
|
2839
|
-
const { document: document2 } =
|
|
3137
|
+
const { document: document2 } = parseHTML5(html);
|
|
2840
3138
|
const compEls = document2.querySelectorAll("[data-composition-src]");
|
|
2841
3139
|
const workItems = [];
|
|
2842
3140
|
for (const el of compEls) {
|
|
2843
3141
|
const srcPath = el.getAttribute("data-composition-src");
|
|
2844
3142
|
if (!srcPath) continue;
|
|
2845
|
-
const filePath =
|
|
3143
|
+
const filePath = resolve6(projectDir, srcPath);
|
|
2846
3144
|
if (visited.has(filePath)) {
|
|
2847
3145
|
continue;
|
|
2848
3146
|
}
|
|
@@ -2859,7 +3157,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
2859
3157
|
workItems.map(async (item) => {
|
|
2860
3158
|
const { html: compiledSub } = await compileHtmlFile(
|
|
2861
3159
|
rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
|
|
2862
|
-
|
|
3160
|
+
dirname5(item.filePath),
|
|
2863
3161
|
downloadDir,
|
|
2864
3162
|
void 0,
|
|
2865
3163
|
urlByAbsentLocalPath
|
|
@@ -2887,7 +3185,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
2887
3185
|
return { subCompositions };
|
|
2888
3186
|
}
|
|
2889
3187
|
function promoteCssImportsToLinkTags(html) {
|
|
2890
|
-
const { document: document2 } =
|
|
3188
|
+
const { document: document2 } = parseHTML5(html);
|
|
2891
3189
|
const head = document2.querySelector("head");
|
|
2892
3190
|
if (!head) return html;
|
|
2893
3191
|
const importRe = /@import\s+url\(\s*['"]?([^'")\s]+)['"]?\s*\)\s*;?/gi;
|
|
@@ -2956,10 +3254,15 @@ var ProducerHostIdentityMap = class extends Map {
|
|
|
2956
3254
|
}
|
|
2957
3255
|
};
|
|
2958
3256
|
function coalesceHeadStylesAndBodyScripts(html) {
|
|
2959
|
-
const { document: document2 } =
|
|
3257
|
+
const { document: document2 } = parseHTML5(html);
|
|
2960
3258
|
const head = document2.querySelector("head");
|
|
2961
3259
|
const body = document2.querySelector("body");
|
|
2962
3260
|
if (!head && !body) return html;
|
|
3261
|
+
const liveStyles = [...document2.querySelectorAll("style")].filter(isAlwaysAppliedStyle);
|
|
3262
|
+
const dedupedStyles = dedupeFontFaceRules(liveStyles.map((el) => el.textContent || ""));
|
|
3263
|
+
liveStyles.forEach((el, i) => {
|
|
3264
|
+
el.textContent = dedupedStyles[i] ?? "";
|
|
3265
|
+
});
|
|
2963
3266
|
const styleEls = head ? Array.from(head.querySelectorAll("style")) : [];
|
|
2964
3267
|
const importRe = /@import\s+url\([^)]*\)\s*;|@import\s+["'][^"']+["']\s*;/gi;
|
|
2965
3268
|
for (const run of styleEls.length > 1 ? headStyleRuns(styleEls) : []) {
|
|
@@ -3002,7 +3305,7 @@ function coalesceHeadStylesAndBodyScripts(html) {
|
|
|
3002
3305
|
return document2.toString();
|
|
3003
3306
|
}
|
|
3004
3307
|
function inlineSubCompositions(html, subCompositions, projectDir, variableOverrides = {}) {
|
|
3005
|
-
const { document: document2 } =
|
|
3308
|
+
const { document: document2 } = parseHTML5(html);
|
|
3006
3309
|
const head = document2.querySelector("head");
|
|
3007
3310
|
const body = document2.querySelector("body");
|
|
3008
3311
|
const hosts = Array.from(document2.querySelectorAll("[data-composition-src]"));
|
|
@@ -3031,17 +3334,17 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
3031
3334
|
resolveHtml: (srcPath) => {
|
|
3032
3335
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
3033
3336
|
if (!compHtml) {
|
|
3034
|
-
const read = readProjectFile(
|
|
3337
|
+
const read = readProjectFile(resolve6(projectDir, srcPath));
|
|
3035
3338
|
if (read.kind === "file") {
|
|
3036
3339
|
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
3037
3340
|
}
|
|
3038
3341
|
}
|
|
3039
3342
|
return compHtml;
|
|
3040
3343
|
},
|
|
3041
|
-
parseHtml: (htmlStr) =>
|
|
3344
|
+
parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
|
|
3042
3345
|
// Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
|
|
3043
3346
|
// against its own directory, project-root refs stay as authored.
|
|
3044
|
-
assetExists: (path) => existsSync6(
|
|
3347
|
+
assetExists: (path) => existsSync6(resolve6(projectDir, path)),
|
|
3045
3348
|
// Preserve the authored root wrapper as a child of the host, matching
|
|
3046
3349
|
// the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
|
|
3047
3350
|
// which the runtime compositionLoader mirrors with its own copy for the
|
|
@@ -3142,7 +3445,7 @@ ${html}
|
|
|
3142
3445
|
</html>`;
|
|
3143
3446
|
}
|
|
3144
3447
|
function injectTextRenderingRule(html) {
|
|
3145
|
-
const { document: document2 } =
|
|
3448
|
+
const { document: document2 } = parseHTML5(html);
|
|
3146
3449
|
const head = document2.querySelector("head");
|
|
3147
3450
|
if (!head) return html;
|
|
3148
3451
|
if (document2.querySelector("style[data-hyperframes-text-rendering]")) {
|
|
@@ -3166,7 +3469,7 @@ function matchesScriptIntegrity(bytes, metadata) {
|
|
|
3166
3469
|
(alg) => hashes.some((hash) => hash[1]?.toLowerCase() === alg)
|
|
3167
3470
|
);
|
|
3168
3471
|
if (!strongest) return true;
|
|
3169
|
-
const actual =
|
|
3472
|
+
const actual = createHash4(strongest).update(bytes).digest();
|
|
3170
3473
|
return hashes.some(
|
|
3171
3474
|
(hash) => hash[1]?.toLowerCase() === strongest && actual.equals(Buffer.from(hash[2], "base64"))
|
|
3172
3475
|
);
|
|
@@ -3174,7 +3477,7 @@ function matchesScriptIntegrity(bytes, metadata) {
|
|
|
3174
3477
|
async function inlineExternalScripts(html) {
|
|
3175
3478
|
const fullHtml = ensureFullDocument(html);
|
|
3176
3479
|
const wrappedFragment = fullHtml !== html;
|
|
3177
|
-
const { document: document2 } =
|
|
3480
|
+
const { document: document2 } = parseHTML5(fullHtml);
|
|
3178
3481
|
const scripts = document2.querySelectorAll("script[src]");
|
|
3179
3482
|
const externalScripts = [];
|
|
3180
3483
|
for (const el of scripts) {
|
|
@@ -3223,12 +3526,12 @@ ${safeText}
|
|
|
3223
3526
|
return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
|
|
3224
3527
|
}
|
|
3225
3528
|
function collectExternalAssets(html, projectDir) {
|
|
3226
|
-
const absProjectDir =
|
|
3529
|
+
const absProjectDir = resolve6(projectDir);
|
|
3227
3530
|
const externalAssets = /* @__PURE__ */ new Map();
|
|
3228
3531
|
function processPath(rawPath) {
|
|
3229
3532
|
const trimmed = rawPath.trim();
|
|
3230
3533
|
if (isNonRelativeUrl(trimmed)) return null;
|
|
3231
|
-
const absPath =
|
|
3534
|
+
const absPath = resolve6(absProjectDir, trimmed);
|
|
3232
3535
|
if (isPathInside2(absPath, absProjectDir)) {
|
|
3233
3536
|
return null;
|
|
3234
3537
|
}
|
|
@@ -3237,7 +3540,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
3237
3540
|
externalAssets.set(safeKey, absPath);
|
|
3238
3541
|
return safeKey;
|
|
3239
3542
|
}
|
|
3240
|
-
const { document: document2 } =
|
|
3543
|
+
const { document: document2 } = parseHTML5(html);
|
|
3241
3544
|
for (const el of document2.querySelectorAll("[src], [href]")) {
|
|
3242
3545
|
for (const attr of ["src", "href"]) {
|
|
3243
3546
|
const val = (el.getAttribute(attr) || "").trim();
|
|
@@ -3282,7 +3585,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
3282
3585
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
3283
3586
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
3284
3587
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
3285
|
-
if (!existsSync6(remoteDir))
|
|
3588
|
+
if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
|
|
3286
3589
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
3287
3590
|
await Promise.all(
|
|
3288
3591
|
[...urlSet].map(async (url) => {
|
|
@@ -3329,7 +3632,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
3329
3632
|
return downloadAndRewriteUrls(
|
|
3330
3633
|
urlSet,
|
|
3331
3634
|
html,
|
|
3332
|
-
|
|
3635
|
+
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3333
3636
|
"Remote media download failed",
|
|
3334
3637
|
"Localized remote media source(s)"
|
|
3335
3638
|
);
|
|
@@ -3344,7 +3647,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
3344
3647
|
return downloadAndRewriteUrls(
|
|
3345
3648
|
urlSet,
|
|
3346
3649
|
html,
|
|
3347
|
-
|
|
3650
|
+
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3348
3651
|
"Remote image download failed",
|
|
3349
3652
|
"Localized remote image source(s)"
|
|
3350
3653
|
);
|
|
@@ -3360,7 +3663,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
3360
3663
|
return downloadAndRewriteUrls(
|
|
3361
3664
|
urlSet,
|
|
3362
3665
|
html,
|
|
3363
|
-
|
|
3666
|
+
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3364
3667
|
"Remote background-image download failed",
|
|
3365
3668
|
"Localized remote background-image(s)",
|
|
3366
3669
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -3495,7 +3798,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
|
|
|
3495
3798
|
return downloadAndRewriteUrls(
|
|
3496
3799
|
urlSet,
|
|
3497
3800
|
processed,
|
|
3498
|
-
|
|
3801
|
+
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3499
3802
|
"Remote font download failed",
|
|
3500
3803
|
"Localized remote font face(s)",
|
|
3501
3804
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -3540,7 +3843,7 @@ async function embedLocalFontFaces(html, projectDir) {
|
|
|
3540
3843
|
while ((urlMatch = urlRe.exec(block)) !== null) {
|
|
3541
3844
|
const localPath = urlMatch[1];
|
|
3542
3845
|
if (!localPath || embeddedPaths.has(localPath)) continue;
|
|
3543
|
-
const absPath = localPath.startsWith("/") ? localPath :
|
|
3846
|
+
const absPath = localPath.startsWith("/") ? localPath : resolve6(projectDir, localPath);
|
|
3544
3847
|
if (!isPathInside2(absPath, projectDir)) continue;
|
|
3545
3848
|
const ext = absPath.match(/\.(woff2?|ttf|otf|ttc)$/i)?.[1]?.toLowerCase() ?? "ttf";
|
|
3546
3849
|
try {
|
|
@@ -3589,7 +3892,7 @@ function renderGsapDist() {
|
|
|
3589
3892
|
}
|
|
3590
3893
|
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
3591
3894
|
let gsapDist;
|
|
3592
|
-
const exists = (path) => existsSync6(
|
|
3895
|
+
const exists = (path) => existsSync6(resolve6(projectDir, path));
|
|
3593
3896
|
return html.replace(
|
|
3594
3897
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
3595
3898
|
(full, prefix, src, file, suffix) => {
|
|
@@ -3605,8 +3908,8 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
3605
3908
|
if (!isPathInside2(htmlPath, projectDir)) return html;
|
|
3606
3909
|
const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
|
|
3607
3910
|
if (!entryPath.includes("/")) return html;
|
|
3608
|
-
const { document: document2 } =
|
|
3609
|
-
const assetExists = (path) => existsSync6(
|
|
3911
|
+
const { document: document2 } = parseHTML5(html);
|
|
3912
|
+
const assetExists = (path) => existsSync6(resolve6(projectDir, path));
|
|
3610
3913
|
rewriteAssetPaths(
|
|
3611
3914
|
document2.querySelectorAll("[src], [href]"),
|
|
3612
3915
|
entryPath,
|
|
@@ -3628,7 +3931,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
3628
3931
|
}
|
|
3629
3932
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
3630
3933
|
const entryHtml = rebaseDirectEntryAssetPaths(
|
|
3631
|
-
|
|
3934
|
+
readFileSync4(htmlPath, "utf-8"),
|
|
3632
3935
|
projectDir,
|
|
3633
3936
|
htmlPath
|
|
3634
3937
|
);
|
|
@@ -3698,12 +4001,26 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
3698
4001
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
3699
4002
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
3700
4003
|
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
|
|
4004
|
+
const {
|
|
4005
|
+
html: htmlWithJpegs,
|
|
4006
|
+
preparedAssets: preparedHeifAssets,
|
|
4007
|
+
preparedImages: preparedHeifImages
|
|
4008
|
+
} = await prepareHeifImageInputs(htmlWithLocalizedFonts, {
|
|
4009
|
+
projectDir,
|
|
4010
|
+
downloadDir,
|
|
4011
|
+
cacheDir: options.animatedGifCacheDir,
|
|
4012
|
+
sourceAssets: remoteImageAssets,
|
|
4013
|
+
timeoutMs: options.ffmpegProcessTimeout
|
|
4014
|
+
});
|
|
4015
|
+
if (preparedHeifImages.length > 0) {
|
|
4016
|
+
defaultLogger.info(`[Compiler] Converted ${preparedHeifImages.length} HEIC image(s) to JPEG`);
|
|
4017
|
+
}
|
|
3701
4018
|
const gifSourceAssets = new Map(remoteImageAssets);
|
|
3702
4019
|
const {
|
|
3703
4020
|
html: htmlWithPreparedGifs,
|
|
3704
4021
|
preparedAssets: preparedGifAssets,
|
|
3705
4022
|
preparedGifs
|
|
3706
|
-
} = await prepareAnimatedGifInputs(
|
|
4023
|
+
} = await prepareAnimatedGifInputs(htmlWithJpegs, {
|
|
3707
4024
|
projectDir,
|
|
3708
4025
|
downloadDir,
|
|
3709
4026
|
cacheDir: options.animatedGifCacheDir,
|
|
@@ -3730,11 +4047,14 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
3730
4047
|
for (const [relPath, absPath] of preparedGifAssets) {
|
|
3731
4048
|
externalAssets.set(relPath, absPath);
|
|
3732
4049
|
}
|
|
4050
|
+
for (const [relPath, absPath] of preparedHeifAssets) {
|
|
4051
|
+
externalAssets.set(relPath, absPath);
|
|
4052
|
+
}
|
|
3733
4053
|
const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
|
|
3734
4054
|
const masterFxChain = parseMasterFxChain(html);
|
|
3735
4055
|
for (const video of videos) {
|
|
3736
4056
|
if (isHttpUrl(video.src)) continue;
|
|
3737
|
-
const videoPath =
|
|
4057
|
+
const videoPath = resolve6(projectDir, video.src);
|
|
3738
4058
|
const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
|
|
3739
4059
|
(async () => {
|
|
3740
4060
|
const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
|
|
@@ -3754,7 +4074,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
3754
4074
|
})().catch(() => {
|
|
3755
4075
|
});
|
|
3756
4076
|
}
|
|
3757
|
-
const { document: document2 } =
|
|
4077
|
+
const { document: document2 } = parseHTML5(html);
|
|
3758
4078
|
const rootEl = document2.querySelector("[data-composition-id]");
|
|
3759
4079
|
const width = rootEl ? parseInt(rootEl.getAttribute("data-width") || "1080", 10) : 1080;
|
|
3760
4080
|
const height = rootEl ? parseInt(rootEl.getAttribute("data-height") || "1920", 10) : 1920;
|
|
@@ -4161,7 +4481,7 @@ async function runCompileStage(input) {
|
|
|
4161
4481
|
abortSignal
|
|
4162
4482
|
} = input;
|
|
4163
4483
|
const compileStart = Date.now();
|
|
4164
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
4484
|
+
const compiled = await compileForRender(projectDir, htmlPath, join7(workDir, "downloads"), {
|
|
4165
4485
|
log,
|
|
4166
4486
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
4167
4487
|
allowSystemFontCapture,
|
|
@@ -4169,7 +4489,7 @@ async function runCompileStage(input) {
|
|
|
4169
4489
|
variables: input.variables,
|
|
4170
4490
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
4171
4491
|
pageMotionBlur: input.pageMotionBlur,
|
|
4172
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
4492
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join7(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
4173
4493
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
4174
4494
|
});
|
|
4175
4495
|
assertNotAborted();
|
|
@@ -4225,7 +4545,7 @@ async function runCompileStage(input) {
|
|
|
4225
4545
|
};
|
|
4226
4546
|
await preflightCompositionAssetMediaTypes({
|
|
4227
4547
|
projectDir,
|
|
4228
|
-
compiledDir:
|
|
4548
|
+
compiledDir: join7(workDir, "compiled"),
|
|
4229
4549
|
composition,
|
|
4230
4550
|
signal: abortSignal
|
|
4231
4551
|
});
|
|
@@ -4270,8 +4590,8 @@ async function runCompileStage(input) {
|
|
|
4270
4590
|
}
|
|
4271
4591
|
|
|
4272
4592
|
// src/services/render/stages/probeStage.ts
|
|
4273
|
-
import { join as
|
|
4274
|
-
import { parseHTML as
|
|
4593
|
+
import { join as join8 } from "node:path";
|
|
4594
|
+
import { parseHTML as parseHTML6 } from "linkedom";
|
|
4275
4595
|
import {
|
|
4276
4596
|
closeCaptureSession as closeCaptureSession4,
|
|
4277
4597
|
compositionRequiresWebGpu,
|
|
@@ -4318,7 +4638,7 @@ function durationToFrameCount(duration, fps) {
|
|
|
4318
4638
|
}
|
|
4319
4639
|
function hasScriptedAudioVolumeAutomation(html, audioCount) {
|
|
4320
4640
|
if (audioCount <= 0) return false;
|
|
4321
|
-
const { document: document2 } =
|
|
4641
|
+
const { document: document2 } = parseHTML6(html);
|
|
4322
4642
|
const scriptBodies = [...document2.querySelectorAll("script")].map((script) => script.textContent ?? "").join("\n");
|
|
4323
4643
|
if (!scriptBodies) return false;
|
|
4324
4644
|
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(
|
|
@@ -4326,12 +4646,12 @@ function hasScriptedAudioVolumeAutomation(html, audioCount) {
|
|
|
4326
4646
|
);
|
|
4327
4647
|
}
|
|
4328
4648
|
function hasAutoStartVideos(html) {
|
|
4329
|
-
const { document: document2 } =
|
|
4649
|
+
const { document: document2 } = parseHTML6(html);
|
|
4330
4650
|
return document2.querySelector("video[data-hf-auto-start]") !== null;
|
|
4331
4651
|
}
|
|
4332
4652
|
function hasVariableBoundMedia(html, variables) {
|
|
4333
4653
|
if (!variables || Object.keys(variables).length === 0) return false;
|
|
4334
|
-
const { document: document2 } =
|
|
4654
|
+
const { document: document2 } = parseHTML6(html);
|
|
4335
4655
|
return Array.from(
|
|
4336
4656
|
document2.querySelectorAll(
|
|
4337
4657
|
"img[data-var-src], audio[data-var-src], video[data-var-src], source[data-var-src]"
|
|
@@ -4342,7 +4662,7 @@ function hasVariableBoundMedia(html, variables) {
|
|
|
4342
4662
|
});
|
|
4343
4663
|
}
|
|
4344
4664
|
function authoredMediaStarts(html) {
|
|
4345
|
-
const { document: document2 } =
|
|
4665
|
+
const { document: document2 } = parseHTML6(html);
|
|
4346
4666
|
const starts = /* @__PURE__ */ new Map();
|
|
4347
4667
|
for (const media of document2.querySelectorAll("video, audio")) {
|
|
4348
4668
|
const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
|
|
@@ -4379,7 +4699,7 @@ function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, dura
|
|
|
4379
4699
|
return existingEnd <= 0 ? projectedEnd : Math.min(existingEnd, projectedEnd);
|
|
4380
4700
|
}
|
|
4381
4701
|
function hasRuntimeMediaChanges(html) {
|
|
4382
|
-
const { document: document2 } =
|
|
4702
|
+
const { document: document2 } = parseHTML6(html);
|
|
4383
4703
|
const scriptBodies = [...document2.querySelectorAll("script")].map(
|
|
4384
4704
|
(script) => script.textContent ?? ""
|
|
4385
4705
|
);
|
|
@@ -4462,7 +4782,7 @@ async function runProbeStage(input) {
|
|
|
4462
4782
|
});
|
|
4463
4783
|
fileServer = await createFileServer({
|
|
4464
4784
|
projectDir,
|
|
4465
|
-
compiledDir:
|
|
4785
|
+
compiledDir: join8(workDir, "compiled"),
|
|
4466
4786
|
port: 0,
|
|
4467
4787
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
4468
4788
|
fps: job.config.fps
|
|
@@ -4491,7 +4811,7 @@ async function runProbeStage(input) {
|
|
|
4491
4811
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
4492
4812
|
probeSession = await createCaptureSession4(
|
|
4493
4813
|
fileServer.url,
|
|
4494
|
-
|
|
4814
|
+
join8(workDir, "probe"),
|
|
4495
4815
|
captureOpts,
|
|
4496
4816
|
null,
|
|
4497
4817
|
probeCfg
|
|
@@ -4576,7 +4896,7 @@ async function runProbeStage(input) {
|
|
|
4576
4896
|
});
|
|
4577
4897
|
probeSession = await createCaptureSession4(
|
|
4578
4898
|
fileServer.url,
|
|
4579
|
-
|
|
4899
|
+
join8(workDir, "probe-screenshot"),
|
|
4580
4900
|
captureOpts,
|
|
4581
4901
|
null,
|
|
4582
4902
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -4612,7 +4932,7 @@ async function runProbeStage(input) {
|
|
|
4612
4932
|
compiled,
|
|
4613
4933
|
resolutions,
|
|
4614
4934
|
projectDir,
|
|
4615
|
-
|
|
4935
|
+
join8(workDir, "downloads")
|
|
4616
4936
|
);
|
|
4617
4937
|
assertNotAborted();
|
|
4618
4938
|
composition.videos = compiled.videos;
|
|
@@ -4803,7 +5123,7 @@ async function runProbeStage(input) {
|
|
|
4803
5123
|
}
|
|
4804
5124
|
await preflightCompositionAssetMediaTypes({
|
|
4805
5125
|
projectDir,
|
|
4806
|
-
compiledDir:
|
|
5126
|
+
compiledDir: join8(workDir, "compiled"),
|
|
4807
5127
|
composition,
|
|
4808
5128
|
signal: abortSignal
|
|
4809
5129
|
});
|
|
@@ -4968,7 +5288,7 @@ function validateDistributedDuration(input) {
|
|
|
4968
5288
|
|
|
4969
5289
|
// src/services/render/stages/extractVideosStage.ts
|
|
4970
5290
|
import { existsSync as existsSync7 } from "node:fs";
|
|
4971
|
-
import { join as
|
|
5291
|
+
import { join as join9 } from "node:path";
|
|
4972
5292
|
import {
|
|
4973
5293
|
classifyVideoExtractionError,
|
|
4974
5294
|
createFrameLookupTable,
|
|
@@ -5288,14 +5608,14 @@ async function runExtractVideosStage(input) {
|
|
|
5288
5608
|
// because short boundary counts can differ by one frame.
|
|
5289
5609
|
{
|
|
5290
5610
|
fps: job.config.fps,
|
|
5291
|
-
outputDir:
|
|
5611
|
+
outputDir: join9(compiledDir, "__hyperframes_video_frames"),
|
|
5292
5612
|
format: job.config.videoFrameFormat ?? "auto",
|
|
5293
5613
|
toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
|
|
5294
5614
|
timelineEnd: composition.duration,
|
|
5295
5615
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
5296
5616
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
5297
5617
|
deferRangeExtraction,
|
|
5298
|
-
contentKeyedDirs: [
|
|
5618
|
+
contentKeyedDirs: [join9(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
5299
5619
|
urlByAbsentLocalPath
|
|
5300
5620
|
},
|
|
5301
5621
|
abortSignal,
|
|
@@ -5389,8 +5709,8 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
5389
5709
|
}
|
|
5390
5710
|
|
|
5391
5711
|
// src/services/render/stages/audioStage.ts
|
|
5392
|
-
import { join as
|
|
5393
|
-
import { rmSync as
|
|
5712
|
+
import { join as join11 } from "node:path";
|
|
5713
|
+
import { rmSync as rmSync5 } from "node:fs";
|
|
5394
5714
|
import {
|
|
5395
5715
|
MIXED_AUDIO_FILENAME,
|
|
5396
5716
|
audioFailureDiagnostics,
|
|
@@ -5399,8 +5719,8 @@ import {
|
|
|
5399
5719
|
|
|
5400
5720
|
// src/services/render/audioPadTrim.ts
|
|
5401
5721
|
import { spawn } from "node:child_process";
|
|
5402
|
-
import { mkdtempSync, rmSync as
|
|
5403
|
-
import { dirname as
|
|
5722
|
+
import { mkdtempSync, rmSync as rmSync4 } from "node:fs";
|
|
5723
|
+
import { dirname as dirname6, join as join10 } from "node:path";
|
|
5404
5724
|
import {
|
|
5405
5725
|
DEFAULT_CONFIG,
|
|
5406
5726
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -5408,8 +5728,8 @@ import {
|
|
|
5408
5728
|
extractAudioMetadata as extractAudioMetadata3,
|
|
5409
5729
|
getFfprobeBinary,
|
|
5410
5730
|
ManagedChildProcess,
|
|
5411
|
-
renameWithRetry as
|
|
5412
|
-
runFfmpeg as
|
|
5731
|
+
renameWithRetry as renameWithRetry5,
|
|
5732
|
+
runFfmpeg as runFfmpeg5,
|
|
5413
5733
|
trackChildProcess
|
|
5414
5734
|
} from "@hyperframes/engine";
|
|
5415
5735
|
import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
|
|
@@ -5419,7 +5739,7 @@ import {
|
|
|
5419
5739
|
aacSegmentCount,
|
|
5420
5740
|
describeFfmpegFailure,
|
|
5421
5741
|
extractAudioMetadata as extractAudioMetadata2,
|
|
5422
|
-
runFfmpeg as
|
|
5742
|
+
runFfmpeg as runFfmpeg4
|
|
5423
5743
|
} from "@hyperframes/engine";
|
|
5424
5744
|
var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
|
|
5425
5745
|
function checkedDbfs(value) {
|
|
@@ -5443,7 +5763,7 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
|
|
|
5443
5763
|
else laterBlocks = Math.max(laterBlocks, block);
|
|
5444
5764
|
}
|
|
5445
5765
|
};
|
|
5446
|
-
const result = await
|
|
5766
|
+
const result = await runFfmpeg4(
|
|
5447
5767
|
["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
|
|
5448
5768
|
{ signal, timeout: timeoutMs, onStderr: readLines }
|
|
5449
5769
|
);
|
|
@@ -5603,7 +5923,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
5603
5923
|
error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
|
|
5604
5924
|
};
|
|
5605
5925
|
} finally {
|
|
5606
|
-
for (const path of plan2.cleanupPaths)
|
|
5926
|
+
for (const path of plan2.cleanupPaths) rmSync4(path, { force: true });
|
|
5607
5927
|
}
|
|
5608
5928
|
let loweredDb = 0;
|
|
5609
5929
|
if (probeTruePeak) {
|
|
@@ -5650,8 +5970,8 @@ async function constrainAacTruePeak(input) {
|
|
|
5650
5970
|
async function enforceAacTruePeak(input) {
|
|
5651
5971
|
let scratchDir;
|
|
5652
5972
|
try {
|
|
5653
|
-
scratchDir = mkdtempSync(
|
|
5654
|
-
const correctedPath =
|
|
5973
|
+
scratchDir = mkdtempSync(join10(dirname6(input.audioPath), ".true-peak-"));
|
|
5974
|
+
const correctedPath = join10(scratchDir, "audio.m4a");
|
|
5655
5975
|
const measurements = [];
|
|
5656
5976
|
let attenuationDb = 0;
|
|
5657
5977
|
let sourcePeakDbfs;
|
|
@@ -5666,7 +5986,7 @@ async function enforceAacTruePeak(input) {
|
|
|
5666
5986
|
}
|
|
5667
5987
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
5668
5988
|
if (measuredPath !== correctedPath) return { success: true };
|
|
5669
|
-
|
|
5989
|
+
renameWithRetry5(correctedPath, input.audioPath);
|
|
5670
5990
|
return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
|
|
5671
5991
|
}
|
|
5672
5992
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
@@ -5707,7 +6027,7 @@ async function enforceAacTruePeak(input) {
|
|
|
5707
6027
|
)}`
|
|
5708
6028
|
};
|
|
5709
6029
|
} finally {
|
|
5710
|
-
if (scratchDir)
|
|
6030
|
+
if (scratchDir) rmSync4(scratchDir, { recursive: true, force: true });
|
|
5711
6031
|
}
|
|
5712
6032
|
}
|
|
5713
6033
|
function failResult(outputPath, target, source, error) {
|
|
@@ -5784,7 +6104,7 @@ async function defaultProbeAudioInfo(audioPath, signal) {
|
|
|
5784
6104
|
};
|
|
5785
6105
|
}
|
|
5786
6106
|
async function defaultRunFfmpeg(args, timeoutMs, signal) {
|
|
5787
|
-
return toStepResult(await
|
|
6107
|
+
return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
|
|
5788
6108
|
}
|
|
5789
6109
|
function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
|
|
5790
6110
|
return async (inputPath, outputPath, durationSeconds, filter) => {
|
|
@@ -5854,7 +6174,7 @@ function assertAudioNotInterrupted(failures) {
|
|
|
5854
6174
|
}
|
|
5855
6175
|
async function runAudioStage(input) {
|
|
5856
6176
|
const stageStart = Date.now();
|
|
5857
|
-
const pcmSourcePath =
|
|
6177
|
+
const pcmSourcePath = join11(input.workDir, "audio-source.wav");
|
|
5858
6178
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
5859
6179
|
try {
|
|
5860
6180
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
@@ -5874,7 +6194,7 @@ async function runAudioStage(input) {
|
|
|
5874
6194
|
}
|
|
5875
6195
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
5876
6196
|
} finally {
|
|
5877
|
-
|
|
6197
|
+
rmSync5(pcmSourcePath, { force: true });
|
|
5878
6198
|
}
|
|
5879
6199
|
}
|
|
5880
6200
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
@@ -5892,7 +6212,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5892
6212
|
assertNotAborted,
|
|
5893
6213
|
log
|
|
5894
6214
|
} = input;
|
|
5895
|
-
const audioOutputPath =
|
|
6215
|
+
const audioOutputPath = join11(workDir, MIXED_AUDIO_FILENAME);
|
|
5896
6216
|
let hasAudio = false;
|
|
5897
6217
|
let audioError;
|
|
5898
6218
|
let audioFailures;
|
|
@@ -5903,7 +6223,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5903
6223
|
audioResult = await processCompositionAudio(
|
|
5904
6224
|
audios,
|
|
5905
6225
|
projectDir,
|
|
5906
|
-
|
|
6226
|
+
join11(workDir, "audio-work"),
|
|
5907
6227
|
audioOutputPath,
|
|
5908
6228
|
duration,
|
|
5909
6229
|
abortSignal,
|
|
@@ -5972,7 +6292,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5972
6292
|
|
|
5973
6293
|
// src/services/render/stages/captureStage.ts
|
|
5974
6294
|
import { existsSync as existsSync8, readdirSync, statfsSync } from "node:fs";
|
|
5975
|
-
import { join as
|
|
6295
|
+
import { join as join12 } from "node:path";
|
|
5976
6296
|
import {
|
|
5977
6297
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
5978
6298
|
frameFileExtension,
|
|
@@ -6026,13 +6346,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
6026
6346
|
if (!existsSync8(workDir)) return total;
|
|
6027
6347
|
for (const attempt of readdirSync(workDir)) {
|
|
6028
6348
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6029
|
-
total += sampleDirectoryBytes(
|
|
6349
|
+
total += sampleDirectoryBytes(join12(workDir, attempt));
|
|
6030
6350
|
continue;
|
|
6031
6351
|
}
|
|
6032
6352
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
6033
|
-
const attemptDir =
|
|
6353
|
+
const attemptDir = join12(workDir, attempt);
|
|
6034
6354
|
for (const name of readdirSync(attemptDir)) {
|
|
6035
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
6355
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join12(attemptDir, name));
|
|
6036
6356
|
}
|
|
6037
6357
|
}
|
|
6038
6358
|
return total;
|
|
@@ -6358,24 +6678,24 @@ import {
|
|
|
6358
6678
|
|
|
6359
6679
|
// src/services/render/stages/encodeStage.ts
|
|
6360
6680
|
import {
|
|
6361
|
-
copyFileSync as
|
|
6681
|
+
copyFileSync as copyFileSync4,
|
|
6362
6682
|
existsSync as existsSync9,
|
|
6363
|
-
mkdirSync as
|
|
6683
|
+
mkdirSync as mkdirSync5,
|
|
6364
6684
|
readdirSync as readdirSync2,
|
|
6365
|
-
readFileSync as
|
|
6366
|
-
rmSync as
|
|
6367
|
-
statSync as
|
|
6368
|
-
writeFileSync as
|
|
6685
|
+
readFileSync as readFileSync5,
|
|
6686
|
+
rmSync as rmSync6,
|
|
6687
|
+
statSync as statSync4,
|
|
6688
|
+
writeFileSync as writeFileSync3
|
|
6369
6689
|
} from "node:fs";
|
|
6370
6690
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
6371
|
-
import { dirname as
|
|
6691
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
6372
6692
|
import {
|
|
6373
6693
|
encodeFramesChunkedConcat,
|
|
6374
6694
|
encodeFramesFromDir,
|
|
6375
6695
|
formatFfmpegError,
|
|
6376
6696
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
|
|
6377
6697
|
resolveConfig,
|
|
6378
|
-
runFfmpeg as
|
|
6698
|
+
runFfmpeg as runFfmpeg6,
|
|
6379
6699
|
spawnStreamingEncoder as spawnStreamingEncoder4
|
|
6380
6700
|
} from "@hyperframes/engine";
|
|
6381
6701
|
import {
|
|
@@ -6451,17 +6771,17 @@ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
|
|
|
6451
6771
|
}
|
|
6452
6772
|
async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
6453
6773
|
const { outputPath } = argsInput;
|
|
6454
|
-
const result = await
|
|
6774
|
+
const result = await runFfmpeg6(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
|
|
6455
6775
|
if (!result.success) {
|
|
6456
6776
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
6457
6777
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
6458
6778
|
}
|
|
6459
|
-
const gif =
|
|
6779
|
+
const gif = readFileSync5(outputPath);
|
|
6460
6780
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
6461
6781
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
6462
6782
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
6463
6783
|
}
|
|
6464
|
-
|
|
6784
|
+
writeFileSync3(outputPath, gif);
|
|
6465
6785
|
return null;
|
|
6466
6786
|
}
|
|
6467
6787
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -6483,17 +6803,17 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
6483
6803
|
try {
|
|
6484
6804
|
const run = { signal: input.signal, timeout: input.timeout };
|
|
6485
6805
|
for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
|
|
6486
|
-
const result = await
|
|
6806
|
+
const result = await runFfmpeg6(args, run);
|
|
6487
6807
|
if (!result.success) {
|
|
6488
6808
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
6489
6809
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
6490
6810
|
}
|
|
6491
6811
|
}
|
|
6492
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
6812
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync5(outputPath)) !== false) {
|
|
6493
6813
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
6494
6814
|
if (failure) return failure;
|
|
6495
6815
|
}
|
|
6496
|
-
const fileSize = existsSync9(outputPath) ?
|
|
6816
|
+
const fileSize = existsSync9(outputPath) ? statSync4(outputPath).size : 0;
|
|
6497
6817
|
return {
|
|
6498
6818
|
success: true,
|
|
6499
6819
|
outputPath,
|
|
@@ -6502,7 +6822,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
6502
6822
|
fileSize
|
|
6503
6823
|
};
|
|
6504
6824
|
} finally {
|
|
6505
|
-
|
|
6825
|
+
rmSync6(input.palettePath, { force: true });
|
|
6506
6826
|
}
|
|
6507
6827
|
}
|
|
6508
6828
|
function startEncodeProgress(job, stage, onProgress) {
|
|
@@ -6574,10 +6894,10 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
6574
6894
|
const stream = await encoder;
|
|
6575
6895
|
for (let next = 0; next < totalFrames; next++) {
|
|
6576
6896
|
while (!landed.has(next) && !captureDone && !signal.aborted) {
|
|
6577
|
-
await new Promise((
|
|
6897
|
+
await new Promise((resolve10) => wake = resolve10);
|
|
6578
6898
|
}
|
|
6579
6899
|
if (signal.aborted) return;
|
|
6580
|
-
const mergedPath =
|
|
6900
|
+
const mergedPath = join13(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
6581
6901
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
6582
6902
|
landed.delete(next);
|
|
6583
6903
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -6652,7 +6972,7 @@ async function runEncodeStage(input) {
|
|
|
6652
6972
|
const stage5Start = Date.now();
|
|
6653
6973
|
if (isPngSequence) {
|
|
6654
6974
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
6655
|
-
if (!existsSync9(outputPath))
|
|
6975
|
+
if (!existsSync9(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
6656
6976
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
6657
6977
|
if (captured.length === 0) {
|
|
6658
6978
|
throw new Error(
|
|
@@ -6660,11 +6980,11 @@ async function runEncodeStage(input) {
|
|
|
6660
6980
|
);
|
|
6661
6981
|
}
|
|
6662
6982
|
captured.forEach((name, i) => {
|
|
6663
|
-
const dst =
|
|
6664
|
-
|
|
6983
|
+
const dst = join13(outputPath, formatExportFrameName(i, "png"));
|
|
6984
|
+
copyFileSync4(join13(framesDir, name), dst);
|
|
6665
6985
|
});
|
|
6666
6986
|
if (hasAudio && audioOutputPath && existsSync9(audioOutputPath)) {
|
|
6667
|
-
|
|
6987
|
+
copyFileSync4(audioOutputPath, join13(outputPath, MIXED_AUDIO_FILENAME2));
|
|
6668
6988
|
log.info(
|
|
6669
6989
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
6670
6990
|
);
|
|
@@ -6684,7 +7004,7 @@ async function runEncodeStage(input) {
|
|
|
6684
7004
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
6685
7005
|
fps: job.config.fps,
|
|
6686
7006
|
loop,
|
|
6687
|
-
palettePath:
|
|
7007
|
+
palettePath: join13(dirname7(videoOnlyPath), "gif-palette.png"),
|
|
6688
7008
|
preserveAlpha: needsAlpha,
|
|
6689
7009
|
signal: abortSignal,
|
|
6690
7010
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -6743,9 +7063,9 @@ function sampleDirectoryBytes(dir) {
|
|
|
6743
7063
|
continue;
|
|
6744
7064
|
}
|
|
6745
7065
|
for (const name of entries) {
|
|
6746
|
-
const full =
|
|
7066
|
+
const full = join14(current, name);
|
|
6747
7067
|
try {
|
|
6748
|
-
const st =
|
|
7068
|
+
const st = statSync5(full);
|
|
6749
7069
|
if (st.isDirectory()) {
|
|
6750
7070
|
stack.push(full);
|
|
6751
7071
|
} else if (st.isFile()) {
|
|
@@ -6829,8 +7149,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
6829
7149
|
const ranges = [];
|
|
6830
7150
|
let rangeStart = null;
|
|
6831
7151
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
6832
|
-
const framePath =
|
|
6833
|
-
const missing = !existsSync10(framePath) ||
|
|
7152
|
+
const framePath = join14(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7153
|
+
const missing = !existsSync10(framePath) || statSync5(framePath).size <= 8;
|
|
6834
7154
|
if (missing && rangeStart === null) {
|
|
6835
7155
|
rangeStart = frameIndex;
|
|
6836
7156
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -6846,7 +7166,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
6846
7166
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
6847
7167
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
6848
7168
|
const batches = [];
|
|
6849
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
7169
|
+
const outputDirFor = (batchIndex, workerId) => join14(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
6850
7170
|
if (stride > 1) {
|
|
6851
7171
|
const spans = /* @__PURE__ */ new Map();
|
|
6852
7172
|
for (const range of ranges) {
|
|
@@ -6914,7 +7234,7 @@ function isRecoverableParallelCaptureError(error) {
|
|
|
6914
7234
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
6915
7235
|
let captured = 0;
|
|
6916
7236
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
6917
|
-
const framePath =
|
|
7237
|
+
const framePath = join14(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
6918
7238
|
if (existsSync10(framePath)) captured++;
|
|
6919
7239
|
}
|
|
6920
7240
|
return captured;
|
|
@@ -6940,7 +7260,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
6940
7260
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
6941
7261
|
});
|
|
6942
7262
|
pendingTransientRetry = false;
|
|
6943
|
-
const attemptWorkDir =
|
|
7263
|
+
const attemptWorkDir = join14(options.workDir, `capture-attempt-${attempt}`);
|
|
6944
7264
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
6945
7265
|
missingRanges,
|
|
6946
7266
|
currentWorkers,
|
|
@@ -7131,7 +7451,7 @@ function explainStreamingEncodeGate(cfg, outputFormat, workerCount, durationSeco
|
|
|
7131
7451
|
var DEFAULT_SEGMENTED_MIN_SECONDS = Number.POSITIVE_INFINITY;
|
|
7132
7452
|
|
|
7133
7453
|
// src/services/distributed/planFrameCost.ts
|
|
7134
|
-
import { join as
|
|
7454
|
+
import { join as join17 } from "node:path";
|
|
7135
7455
|
import {
|
|
7136
7456
|
closeCaptureSession as closeCaptureSession9,
|
|
7137
7457
|
createCaptureSession as createCaptureSession9,
|
|
@@ -7141,7 +7461,7 @@ import {
|
|
|
7141
7461
|
|
|
7142
7462
|
// src/services/distributed/chunkFrames.ts
|
|
7143
7463
|
import { existsSync as existsSync12, readdirSync as readdirSync4 } from "node:fs";
|
|
7144
|
-
import { extname as extname2, join as
|
|
7464
|
+
import { extname as extname2, join as join16 } from "node:path";
|
|
7145
7465
|
import {
|
|
7146
7466
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
7147
7467
|
createFrameLookupTable as createFrameLookupTable2,
|
|
@@ -7151,8 +7471,8 @@ import {
|
|
|
7151
7471
|
|
|
7152
7472
|
// src/services/distributed/shared.ts
|
|
7153
7473
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7154
|
-
import { dirname as
|
|
7155
|
-
import { existsSync as existsSync11, readFileSync as
|
|
7474
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
7475
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
|
|
7156
7476
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7157
7477
|
import { promisify as promisify2 } from "node:util";
|
|
7158
7478
|
import {
|
|
@@ -7167,7 +7487,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7167
7487
|
}
|
|
7168
7488
|
function resolvePlanAudioPath(planDir) {
|
|
7169
7489
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7170
|
-
const candidate =
|
|
7490
|
+
const candidate = join15(planDir, name);
|
|
7171
7491
|
if (existsSync11(candidate)) return candidate;
|
|
7172
7492
|
}
|
|
7173
7493
|
return null;
|
|
@@ -7424,13 +7744,13 @@ function buildSyntheticRenderJob(input) {
|
|
|
7424
7744
|
var cachedProducerVersion = null;
|
|
7425
7745
|
function readProducerVersion() {
|
|
7426
7746
|
if (cachedProducerVersion !== null) return cachedProducerVersion;
|
|
7427
|
-
const startDir =
|
|
7747
|
+
const startDir = dirname9(fileURLToPath3(import.meta.url));
|
|
7428
7748
|
let current = startDir;
|
|
7429
7749
|
for (let i = 0; i < 10; i++) {
|
|
7430
|
-
const candidate =
|
|
7750
|
+
const candidate = join15(current, "package.json");
|
|
7431
7751
|
if (existsSync11(candidate)) {
|
|
7432
7752
|
try {
|
|
7433
|
-
const pkg = JSON.parse(
|
|
7753
|
+
const pkg = JSON.parse(readFileSync7(candidate, "utf-8"));
|
|
7434
7754
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
7435
7755
|
cachedProducerVersion = pkg.version;
|
|
7436
7756
|
return pkg.version;
|
|
@@ -7438,7 +7758,7 @@ function readProducerVersion() {
|
|
|
7438
7758
|
} catch {
|
|
7439
7759
|
}
|
|
7440
7760
|
}
|
|
7441
|
-
const parent =
|
|
7761
|
+
const parent = dirname9(current);
|
|
7442
7762
|
if (parent === current) break;
|
|
7443
7763
|
current = parent;
|
|
7444
7764
|
}
|
|
@@ -7481,7 +7801,7 @@ function frameNumberFromFileName(name) {
|
|
|
7481
7801
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
7482
7802
|
const result = [];
|
|
7483
7803
|
for (const v of videos) {
|
|
7484
|
-
const outputDir =
|
|
7804
|
+
const outputDir = join16(planDir, "video-frames", v.videoId);
|
|
7485
7805
|
if (!existsSync12(outputDir)) {
|
|
7486
7806
|
throw new Error(
|
|
7487
7807
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
@@ -7500,7 +7820,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
7500
7820
|
if (!frameName) continue;
|
|
7501
7821
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
7502
7822
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
7503
|
-
framePaths.set(frameIndex,
|
|
7823
|
+
framePaths.set(frameIndex, join16(outputDir, frameName));
|
|
7504
7824
|
}
|
|
7505
7825
|
result.push({
|
|
7506
7826
|
videoId: v.videoId,
|
|
@@ -7590,14 +7910,14 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
7590
7910
|
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
7591
7911
|
return Promise.all(
|
|
7592
7912
|
deferred.map(async (video) => {
|
|
7593
|
-
const outputDir =
|
|
7913
|
+
const outputDir = join16(input.outputRoot, video.videoId);
|
|
7594
7914
|
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
7595
7915
|
if (indexes.length === 0) {
|
|
7596
7916
|
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
7597
7917
|
}
|
|
7598
7918
|
const range = video.deferredRange;
|
|
7599
7919
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
7600
|
-
const sourcePath = remoteUrl ??
|
|
7920
|
+
const sourcePath = remoteUrl ?? join16(input.planDir, ...range.sourcePath.split("/"));
|
|
7601
7921
|
if (remoteUrl === void 0 && !existsSync12(sourcePath)) {
|
|
7602
7922
|
throw new RenderChunkValidationError(
|
|
7603
7923
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -7646,12 +7966,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
7646
7966
|
planVideos,
|
|
7647
7967
|
frames: sampled,
|
|
7648
7968
|
fps: fpsRatio,
|
|
7649
|
-
outputRoot:
|
|
7969
|
+
outputRoot: join17(workDir, "frame-cost-video-frames"),
|
|
7650
7970
|
cfg: input.cfg
|
|
7651
7971
|
})
|
|
7652
7972
|
];
|
|
7653
7973
|
const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
7654
|
-
const compiledDir =
|
|
7974
|
+
const compiledDir = join17(planDir, "compiled");
|
|
7655
7975
|
const fileServer = await createFileServer({
|
|
7656
7976
|
projectDir: compiledDir,
|
|
7657
7977
|
compiledDir,
|
|
@@ -7663,7 +7983,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
7663
7983
|
try {
|
|
7664
7984
|
session = await createCaptureSession9(
|
|
7665
7985
|
fileServer.url,
|
|
7666
|
-
|
|
7986
|
+
join17(workDir, "frame-cost"),
|
|
7667
7987
|
buildChunkCaptureOptions({
|
|
7668
7988
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
7669
7989
|
encoder: input,
|
|
@@ -7760,8 +8080,8 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
7760
8080
|
}
|
|
7761
8081
|
|
|
7762
8082
|
// src/services/render/stages/freezePlan.ts
|
|
7763
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
7764
|
-
import { join as
|
|
8083
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8084
|
+
import { join as join18, relative as relative3, resolve as resolve8 } from "node:path";
|
|
7765
8085
|
|
|
7766
8086
|
// src/services/distributed/planProtocol.ts
|
|
7767
8087
|
var PLAN_SCHEMA_VERSION = 1;
|
|
@@ -7859,12 +8179,12 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
|
|
|
7859
8179
|
}
|
|
7860
8180
|
|
|
7861
8181
|
// src/services/render/stages/planHash.ts
|
|
7862
|
-
import { createHash as
|
|
7863
|
-
import { closeSync as
|
|
8182
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
8183
|
+
import { closeSync as closeSync3, createReadStream as createReadStream3, openSync as openSync3, readSync as readSync2 } from "node:fs";
|
|
7864
8184
|
var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
|
|
7865
8185
|
var FIELD_DELIMITER = Buffer.from([0]);
|
|
7866
8186
|
function computePlanHash(input) {
|
|
7867
|
-
const hash =
|
|
8187
|
+
const hash = createHash5("sha256");
|
|
7868
8188
|
hash.update(PLAN_HASH_SCHEMA_PREFIX, "utf8");
|
|
7869
8189
|
hash.update(input.compositionHtml);
|
|
7870
8190
|
hash.update(FIELD_DELIMITER);
|
|
@@ -7911,27 +8231,27 @@ function canonicalJsonStringify(value) {
|
|
|
7911
8231
|
throw new TypeError(`canonicalJsonStringify: unsupported value type ${typeof value}`);
|
|
7912
8232
|
}
|
|
7913
8233
|
function sha256Hex(bytes) {
|
|
7914
|
-
const h =
|
|
8234
|
+
const h = createHash5("sha256");
|
|
7915
8235
|
h.update(bytes);
|
|
7916
8236
|
return h.digest("hex");
|
|
7917
8237
|
}
|
|
7918
8238
|
function sha256File(path) {
|
|
7919
|
-
const hash =
|
|
8239
|
+
const hash = createHash5("sha256");
|
|
7920
8240
|
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
7921
|
-
const fd =
|
|
8241
|
+
const fd = openSync3(path, "r");
|
|
7922
8242
|
try {
|
|
7923
8243
|
let bytesRead = 0;
|
|
7924
8244
|
do {
|
|
7925
|
-
bytesRead =
|
|
8245
|
+
bytesRead = readSync2(fd, buffer, 0, buffer.byteLength, null);
|
|
7926
8246
|
if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
|
|
7927
8247
|
} while (bytesRead > 0);
|
|
7928
8248
|
} finally {
|
|
7929
|
-
|
|
8249
|
+
closeSync3(fd);
|
|
7930
8250
|
}
|
|
7931
8251
|
return hash.digest("hex");
|
|
7932
8252
|
}
|
|
7933
8253
|
async function sha256FileAsync(path) {
|
|
7934
|
-
const hash =
|
|
8254
|
+
const hash = createHash5("sha256");
|
|
7935
8255
|
for await (const chunk of createReadStream3(path, { highWaterMark: 1024 * 1024 })) {
|
|
7936
8256
|
hash.update(chunk);
|
|
7937
8257
|
}
|
|
@@ -7975,11 +8295,11 @@ function stripUndefined(value) {
|
|
|
7975
8295
|
}
|
|
7976
8296
|
function listPlanFiles(planDir) {
|
|
7977
8297
|
const results = [];
|
|
7978
|
-
const rootResolved =
|
|
8298
|
+
const rootResolved = resolve8(planDir);
|
|
7979
8299
|
function walk(dir) {
|
|
7980
8300
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
7981
8301
|
for (const entry of entries) {
|
|
7982
|
-
const full =
|
|
8302
|
+
const full = join18(dir, entry.name);
|
|
7983
8303
|
if (entry.isDirectory()) {
|
|
7984
8304
|
walk(full);
|
|
7985
8305
|
} else if (entry.isFile()) {
|
|
@@ -8000,7 +8320,7 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
8000
8320
|
const assets = [];
|
|
8001
8321
|
for (const file of files) {
|
|
8002
8322
|
if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
|
|
8003
|
-
compositionHtml =
|
|
8323
|
+
compositionHtml = readFileSync8(file.absolutePath);
|
|
8004
8324
|
continue;
|
|
8005
8325
|
}
|
|
8006
8326
|
if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
|
|
@@ -8020,23 +8340,23 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
8020
8340
|
function writePlanRemoteFiles(planDir, files) {
|
|
8021
8341
|
if (files.length === 0) return;
|
|
8022
8342
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
8023
|
-
|
|
8343
|
+
writeFileSync5(join18(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
8024
8344
|
}
|
|
8025
8345
|
function readPlanRemoteFiles(planDir) {
|
|
8026
|
-
const path =
|
|
8027
|
-
return existsSync13(path) ? JSON.parse(
|
|
8346
|
+
const path = join18(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8347
|
+
return existsSync13(path) ? JSON.parse(readFileSync8(path, "utf-8")) : [];
|
|
8028
8348
|
}
|
|
8029
8349
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8030
|
-
const planJsonPath =
|
|
8031
|
-
const encoderJsonPath =
|
|
8350
|
+
const planJsonPath = join18(planDir, "plan.json");
|
|
8351
|
+
const encoderJsonPath = join18(planDir, "meta", "encoder.json");
|
|
8032
8352
|
if (!existsSync13(planJsonPath)) {
|
|
8033
8353
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8034
8354
|
}
|
|
8035
8355
|
if (!existsSync13(encoderJsonPath)) {
|
|
8036
8356
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8037
8357
|
}
|
|
8038
|
-
const planJson = JSON.parse(
|
|
8039
|
-
const encoderConfigCanonicalJson =
|
|
8358
|
+
const planJson = JSON.parse(readFileSync8(planJsonPath, "utf-8"));
|
|
8359
|
+
const encoderConfigCanonicalJson = readFileSync8(encoderJsonPath, "utf-8");
|
|
8040
8360
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8041
8361
|
return computePlanHash({
|
|
8042
8362
|
compositionHtml,
|
|
@@ -8065,18 +8385,18 @@ async function freezePlan(input) {
|
|
|
8065
8385
|
if (!existsSync13(planDir)) {
|
|
8066
8386
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8067
8387
|
}
|
|
8068
|
-
const metaDir =
|
|
8069
|
-
if (!existsSync13(metaDir))
|
|
8070
|
-
|
|
8071
|
-
|
|
8388
|
+
const metaDir = join18(planDir, "meta");
|
|
8389
|
+
if (!existsSync13(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8390
|
+
writeFileSync5(
|
|
8391
|
+
join18(metaDir, "composition.json"),
|
|
8072
8392
|
`${JSON.stringify(composition, null, 2)}
|
|
8073
8393
|
`,
|
|
8074
8394
|
"utf-8"
|
|
8075
8395
|
);
|
|
8076
8396
|
const encoderForCanonical = stripUndefined(encoder);
|
|
8077
8397
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
8078
|
-
|
|
8079
|
-
|
|
8398
|
+
writeFileSync5(join18(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
8399
|
+
writeFileSync5(join18(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
8080
8400
|
`, "utf-8");
|
|
8081
8401
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8082
8402
|
const planHash = computePlanHash({
|
|
@@ -8100,8 +8420,8 @@ async function freezePlan(input) {
|
|
|
8100
8420
|
duration: durationSeconds,
|
|
8101
8421
|
hasAudio
|
|
8102
8422
|
};
|
|
8103
|
-
const planJsonPath =
|
|
8104
|
-
|
|
8423
|
+
const planJsonPath = join18(planDir, "plan.json");
|
|
8424
|
+
writeFileSync5(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
8105
8425
|
`, "utf-8");
|
|
8106
8426
|
return { planJsonPath, planHash };
|
|
8107
8427
|
}
|
|
@@ -8142,16 +8462,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8142
8462
|
}
|
|
8143
8463
|
|
|
8144
8464
|
// src/services/distributed/chunkAudio.ts
|
|
8145
|
-
import { closeSync as
|
|
8146
|
-
import { writeFileSync as
|
|
8147
|
-
import { join as
|
|
8148
|
-
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as
|
|
8465
|
+
import { closeSync as closeSync4, existsSync as existsSync14, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
8466
|
+
import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
|
|
8467
|
+
import { join as join20 } from "node:path";
|
|
8468
|
+
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
8149
8469
|
|
|
8150
8470
|
// src/services/distributed/mp4Stitch.ts
|
|
8151
|
-
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as
|
|
8471
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync8 } from "node:fs";
|
|
8152
8472
|
import { open } from "node:fs/promises";
|
|
8153
|
-
import { renameWithRetry as
|
|
8154
|
-
import { dirname as
|
|
8473
|
+
import { renameWithRetry as renameWithRetry6 } from "@hyperframes/engine";
|
|
8474
|
+
import { dirname as dirname10, join as join19 } from "node:path";
|
|
8155
8475
|
import { finished, pipeline } from "node:stream/promises";
|
|
8156
8476
|
function boxAt(src, off, at, limit) {
|
|
8157
8477
|
let size = src.readUInt32BE(off);
|
|
@@ -8164,16 +8484,16 @@ function boxAt(src, off, at, limit) {
|
|
|
8164
8484
|
return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
|
|
8165
8485
|
}
|
|
8166
8486
|
function childBoxes(buf, parent) {
|
|
8167
|
-
const
|
|
8168
|
-
for (let at = parent.bodyStart; at < parent.end; at =
|
|
8169
|
-
|
|
8487
|
+
const boxes2 = [];
|
|
8488
|
+
for (let at = parent.bodyStart; at < parent.end; at = boxes2.at(-1).end) {
|
|
8489
|
+
boxes2.push(boxAt(buf, at, at, parent.end));
|
|
8170
8490
|
}
|
|
8171
|
-
return
|
|
8491
|
+
return boxes2;
|
|
8172
8492
|
}
|
|
8173
8493
|
function findChild(buf, parent, type) {
|
|
8174
8494
|
return childBoxes(buf, parent).find((box2) => box2.type === type);
|
|
8175
8495
|
}
|
|
8176
|
-
function
|
|
8496
|
+
function child2(buf, parent, type) {
|
|
8177
8497
|
const found = findChild(buf, parent, type);
|
|
8178
8498
|
if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
|
|
8179
8499
|
return found;
|
|
@@ -8225,7 +8545,7 @@ function rows(buf, table, columns, countAt = 4) {
|
|
|
8225
8545
|
function readMediaTime(moov, trak) {
|
|
8226
8546
|
const edts = findChild(moov, trak, "edts");
|
|
8227
8547
|
if (!edts) return 0;
|
|
8228
|
-
const elst =
|
|
8548
|
+
const elst = child2(moov, edts, "elst");
|
|
8229
8549
|
const wide = moov[elst.bodyStart] === 1;
|
|
8230
8550
|
const entries = moov.readUInt32BE(elst.bodyStart + 4);
|
|
8231
8551
|
for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
|
|
@@ -8240,7 +8560,7 @@ function readSizes(moov, stsz) {
|
|
|
8240
8560
|
if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
|
|
8241
8561
|
return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
|
|
8242
8562
|
}
|
|
8243
|
-
function
|
|
8563
|
+
function readSync3(moov, stbl, sampleCount) {
|
|
8244
8564
|
const stss = findChild(moov, stbl, "stss");
|
|
8245
8565
|
if (!stss) return null;
|
|
8246
8566
|
const sync = Array(sampleCount).fill(false);
|
|
@@ -8250,10 +8570,10 @@ function readSync2(moov, stbl, sampleCount) {
|
|
|
8250
8570
|
function readChunkOffsets(moov, stbl) {
|
|
8251
8571
|
const stco = findChild(moov, stbl, "stco");
|
|
8252
8572
|
if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
|
|
8253
|
-
return rows(moov,
|
|
8573
|
+
return rows(moov, child2(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
|
|
8254
8574
|
}
|
|
8255
8575
|
function readSamplesPerChunk(moov, stbl, chunkCount) {
|
|
8256
|
-
const stsc = rows(moov,
|
|
8576
|
+
const stsc = rows(moov, child2(moov, stbl, "stsc"), 3);
|
|
8257
8577
|
return stsc.flatMap(([firstChunk, perChunk, description], i) => {
|
|
8258
8578
|
if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
|
|
8259
8579
|
const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
|
|
@@ -8261,28 +8581,28 @@ function readSamplesPerChunk(moov, stbl, chunkCount) {
|
|
|
8261
8581
|
});
|
|
8262
8582
|
}
|
|
8263
8583
|
function readTrack(moov, trak) {
|
|
8264
|
-
const mdia =
|
|
8265
|
-
const hdlr =
|
|
8266
|
-
const stbl =
|
|
8584
|
+
const mdia = child2(moov, trak, "mdia");
|
|
8585
|
+
const hdlr = child2(moov, mdia, "hdlr");
|
|
8586
|
+
const stbl = child2(moov, child2(moov, mdia, "minf"), "stbl");
|
|
8267
8587
|
if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
|
|
8268
|
-
const stts = rows(moov,
|
|
8588
|
+
const stts = rows(moov, child2(moov, stbl, "stts"), 2);
|
|
8269
8589
|
const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
|
|
8270
|
-
const sizes = readSizes(moov,
|
|
8590
|
+
const sizes = readSizes(moov, child2(moov, stbl, "stsz"));
|
|
8271
8591
|
const chunkOffsets = readChunkOffsets(moov, stbl);
|
|
8272
8592
|
const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
|
|
8273
8593
|
if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
|
|
8274
8594
|
throw new Error("[mp4Stitch] sample tables disagree on the sample count");
|
|
8275
8595
|
}
|
|
8276
|
-
const stsd =
|
|
8596
|
+
const stsd = child2(moov, stbl, "stsd");
|
|
8277
8597
|
return {
|
|
8278
8598
|
trak,
|
|
8279
8599
|
handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
|
|
8280
|
-
timescale: timescaleOf(moov,
|
|
8600
|
+
timescale: timescaleOf(moov, child2(moov, mdia, "mdhd")),
|
|
8281
8601
|
mediaTime: readMediaTime(moov, trak),
|
|
8282
8602
|
stsd: moov.subarray(stsd.start, stsd.end),
|
|
8283
8603
|
durations,
|
|
8284
8604
|
sizes,
|
|
8285
|
-
sync:
|
|
8605
|
+
sync: readSync3(moov, stbl, sizes.length),
|
|
8286
8606
|
samplesPerChunk,
|
|
8287
8607
|
chunkOffsets
|
|
8288
8608
|
};
|
|
@@ -8423,9 +8743,9 @@ function buildStitchedHeader(layouts, audio) {
|
|
|
8423
8743
|
}
|
|
8424
8744
|
const template = layouts[0].moov;
|
|
8425
8745
|
const root = boxAt(template, 0, 0, template.length);
|
|
8426
|
-
const mvhd =
|
|
8746
|
+
const mvhd = child2(template, root, "mvhd");
|
|
8427
8747
|
const movieTimescale = timescaleOf(template, mvhd);
|
|
8428
|
-
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template,
|
|
8748
|
+
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child2(template, track.trak, "tkhd")));
|
|
8429
8749
|
const audioTrackId = Math.max(...chunkTrackIds) + 1;
|
|
8430
8750
|
const buildMoov = (dataStart2) => {
|
|
8431
8751
|
const traks = plan2.map((track, t) => {
|
|
@@ -8515,18 +8835,18 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
8515
8835
|
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
8516
8836
|
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
8517
8837
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
8518
|
-
|
|
8838
|
+
mkdirSync8(dirname10(outputPath), { recursive: true });
|
|
8519
8839
|
const workDir = mkdtempSync2(`${outputPath}.stitch-`);
|
|
8520
|
-
const out = createWriteStream(
|
|
8840
|
+
const out = createWriteStream(join19(workDir, "out.mp4"));
|
|
8521
8841
|
try {
|
|
8522
8842
|
out.write(header);
|
|
8523
8843
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
8524
8844
|
out.end();
|
|
8525
8845
|
await finished(out);
|
|
8526
|
-
|
|
8846
|
+
renameWithRetry6(join19(workDir, "out.mp4"), outputPath);
|
|
8527
8847
|
} finally {
|
|
8528
8848
|
out.destroy();
|
|
8529
|
-
|
|
8849
|
+
rmSync8(workDir, { recursive: true, force: true });
|
|
8530
8850
|
}
|
|
8531
8851
|
}
|
|
8532
8852
|
|
|
@@ -8615,9 +8935,9 @@ async function writeChunkAudioSlices(input) {
|
|
|
8615
8935
|
offset += track.sizes[offsets.length - 1];
|
|
8616
8936
|
}
|
|
8617
8937
|
});
|
|
8618
|
-
const bytes =
|
|
8938
|
+
const bytes = readFileSync9(input.audioPath);
|
|
8619
8939
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
8620
|
-
|
|
8940
|
+
mkdirSync9(join20(input.planDir, SLICE_DIR), { recursive: true });
|
|
8621
8941
|
input.chunks.forEach((_, index) => {
|
|
8622
8942
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
8623
8943
|
if (!cut) return;
|
|
@@ -8625,18 +8945,18 @@ async function writeChunkAudioSlices(input) {
|
|
|
8625
8945
|
const start = offsets[cut.firstPacket];
|
|
8626
8946
|
layout.partStarts[index] = start;
|
|
8627
8947
|
const end = offsets[last] + track.sizes[last];
|
|
8628
|
-
|
|
8948
|
+
writeFileSync6(join20(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
8629
8949
|
});
|
|
8630
8950
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
8631
|
-
|
|
8632
|
-
|
|
8951
|
+
writeFileSync6(join20(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
8952
|
+
writeFileSync6(join20(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
8633
8953
|
}
|
|
8634
8954
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
8635
|
-
const layoutPath =
|
|
8955
|
+
const layoutPath = join20(planDir, SLICE_LAYOUT);
|
|
8636
8956
|
if (!existsSync14(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
8637
|
-
const layout = JSON.parse(
|
|
8638
|
-
const frame =
|
|
8639
|
-
const fd =
|
|
8957
|
+
const layout = JSON.parse(readFileSync9(layoutPath, "utf-8"));
|
|
8958
|
+
const frame = readFileSync9(join20(planDir, SLICE_FRAME));
|
|
8959
|
+
const fd = openSync4(scratchPath, "w");
|
|
8640
8960
|
try {
|
|
8641
8961
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
8642
8962
|
writeSync(
|
|
@@ -8648,12 +8968,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
8648
8968
|
);
|
|
8649
8969
|
const start = layout.partStarts[chunkIndex];
|
|
8650
8970
|
if (start !== void 0) {
|
|
8651
|
-
const part =
|
|
8971
|
+
const part = readFileSync9(join20(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
8652
8972
|
writeSync(fd, part, 0, part.length, start);
|
|
8653
8973
|
}
|
|
8654
8974
|
ftruncateSync(fd, layout.sizeBytes);
|
|
8655
8975
|
} finally {
|
|
8656
|
-
|
|
8976
|
+
closeSync4(fd);
|
|
8657
8977
|
}
|
|
8658
8978
|
return scratchPath;
|
|
8659
8979
|
}
|
|
@@ -8673,7 +8993,7 @@ async function muxChunkWithAudio(input) {
|
|
|
8673
8993
|
const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
|
|
8674
8994
|
appendRenderProvenanceArgs(args, input.outputPath);
|
|
8675
8995
|
args.push("-y", input.outputPath);
|
|
8676
|
-
const result = await
|
|
8996
|
+
const result = await runFfmpeg7(args, { signal: input.signal, timeout: input.timeoutMs });
|
|
8677
8997
|
if (!result.success) {
|
|
8678
8998
|
throw new Error(
|
|
8679
8999
|
`[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
|
|
@@ -8702,9 +9022,9 @@ async function muxChunkWithAudio(input) {
|
|
|
8702
9022
|
}
|
|
8703
9023
|
|
|
8704
9024
|
// src/services/distributed/planSize.ts
|
|
8705
|
-
import { createHash as
|
|
9025
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
8706
9026
|
import { lstatSync, readdirSync as readdirSync6 } from "node:fs";
|
|
8707
|
-
import { extname as extname3, join as
|
|
9027
|
+
import { extname as extname3, join as join21, relative as relative4 } from "node:path";
|
|
8708
9028
|
var MAX_TOP_COMPONENTS = 10;
|
|
8709
9029
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8710
9030
|
".aac",
|
|
@@ -8732,7 +9052,7 @@ var PLAN_ROOT_CATEGORY = {
|
|
|
8732
9052
|
"plan.json": "metadata"
|
|
8733
9053
|
};
|
|
8734
9054
|
function hashComponent(value) {
|
|
8735
|
-
return
|
|
9055
|
+
return createHash6("sha256").update(value).digest("hex").slice(0, 12);
|
|
8736
9056
|
}
|
|
8737
9057
|
function classifyFile(segments, rootKind) {
|
|
8738
9058
|
const rootName = segments[0] ?? "";
|
|
@@ -8816,7 +9136,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
8816
9136
|
return;
|
|
8817
9137
|
}
|
|
8818
9138
|
for (const entry of entries) {
|
|
8819
|
-
const full =
|
|
9139
|
+
const full = join21(dir, entry.name);
|
|
8820
9140
|
if (entry.isDirectory()) {
|
|
8821
9141
|
walkRegularFiles(full, visit);
|
|
8822
9142
|
} else if (entry.isFile()) {
|
|
@@ -9032,13 +9352,13 @@ function assertPositiveInteger(name, value) {
|
|
|
9032
9352
|
}
|
|
9033
9353
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
9034
9354
|
"plan.json",
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9355
|
+
join22("meta", "composition.json"),
|
|
9356
|
+
join22("meta", "encoder.json"),
|
|
9357
|
+
join22("meta", "chunks.json")
|
|
9038
9358
|
];
|
|
9039
9359
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
9040
9360
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
9041
|
-
|
|
9361
|
+
rmSync9(join22(planDir, relativePath), { force: true });
|
|
9042
9362
|
}
|
|
9043
9363
|
}
|
|
9044
9364
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -9183,23 +9503,23 @@ function createVideoSourceShipper(input) {
|
|
|
9183
9503
|
const known = shipped.get(srcPath);
|
|
9184
9504
|
if (known) return known;
|
|
9185
9505
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
9186
|
-
const current = stagedRel === null ? srcPath :
|
|
9506
|
+
const current = stagedRel === null ? srcPath : join22(input.planDir, "video-frames", stagedRel);
|
|
9187
9507
|
let result = null;
|
|
9188
9508
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
9189
9509
|
const rel = pathInside(root, srcPath);
|
|
9190
|
-
if (rel !== null && (existsSync15(
|
|
9510
|
+
if (rel !== null && (existsSync15(join22(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
9191
9511
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
9192
9512
|
break;
|
|
9193
9513
|
}
|
|
9194
9514
|
}
|
|
9195
9515
|
if (result === null) {
|
|
9196
9516
|
result = `video-sources/${shipped.size}${extname4(current)}`;
|
|
9197
|
-
const destination =
|
|
9198
|
-
|
|
9517
|
+
const destination = join22(input.planDir, ...result.split("/"));
|
|
9518
|
+
mkdirSync10(join22(input.planDir, "video-sources"), { recursive: true });
|
|
9199
9519
|
try {
|
|
9200
9520
|
linkSync(current, destination);
|
|
9201
9521
|
} catch {
|
|
9202
|
-
|
|
9522
|
+
copyFileSync6(current, destination);
|
|
9203
9523
|
}
|
|
9204
9524
|
}
|
|
9205
9525
|
shipped.set(srcPath, result);
|
|
@@ -9215,7 +9535,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9215
9535
|
useGpu: false,
|
|
9216
9536
|
browserGpuMode: "software"
|
|
9217
9537
|
});
|
|
9218
|
-
if (!existsSync15(planDir))
|
|
9538
|
+
if (!existsSync15(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
9219
9539
|
const log = config.logger ?? defaultLogger;
|
|
9220
9540
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
9221
9541
|
const abortSignal = config.abortSignal;
|
|
@@ -9244,15 +9564,15 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9244
9564
|
variables: config.variables
|
|
9245
9565
|
});
|
|
9246
9566
|
const entryFile = config.entryFile ?? "index.html";
|
|
9247
|
-
const htmlPath =
|
|
9567
|
+
const htmlPath = join22(projectDir, entryFile);
|
|
9248
9568
|
if (!existsSync15(htmlPath)) {
|
|
9249
9569
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
9250
9570
|
}
|
|
9251
|
-
const workDir =
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
const compiledDir =
|
|
9255
|
-
|
|
9571
|
+
const workDir = join22(planDir, ".plan-work");
|
|
9572
|
+
rmSync9(workDir, { recursive: true, force: true });
|
|
9573
|
+
mkdirSync10(workDir, { recursive: true });
|
|
9574
|
+
const compiledDir = join22(workDir, "compiled");
|
|
9575
|
+
mkdirSync10(compiledDir, { recursive: true });
|
|
9256
9576
|
let stageStarted = Date.now();
|
|
9257
9577
|
cpSync2(projectDir, compiledDir, {
|
|
9258
9578
|
recursive: true,
|
|
@@ -9265,10 +9585,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9265
9585
|
}
|
|
9266
9586
|
});
|
|
9267
9587
|
logPlanStage(log, "copy", stageStarted);
|
|
9268
|
-
const finalCompiledDir =
|
|
9588
|
+
const finalCompiledDir = join22(planDir, "compiled");
|
|
9269
9589
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
9270
9590
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
9271
|
-
const remoteLocalPath = (rel) =>
|
|
9591
|
+
const remoteLocalPath = (rel) => join22(projectDir, ...rel.split("/"));
|
|
9272
9592
|
for (const [, file] of remoteFiles) {
|
|
9273
9593
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
9274
9594
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -9454,18 +9774,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9454
9774
|
let stageStarted = Date.now();
|
|
9455
9775
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
9456
9776
|
logPlanStage(log, "extract", stageStarted);
|
|
9457
|
-
const stagedVideoFrames =
|
|
9458
|
-
const videoFramesDst =
|
|
9459
|
-
if (existsSync15(videoFramesDst))
|
|
9460
|
-
const videoSourcesDir =
|
|
9461
|
-
|
|
9777
|
+
const stagedVideoFrames = join22(compiledDir, "__hyperframes_video_frames");
|
|
9778
|
+
const videoFramesDst = join22(planDir, "video-frames");
|
|
9779
|
+
if (existsSync15(videoFramesDst)) rmSync9(videoFramesDst, { recursive: true, force: true });
|
|
9780
|
+
const videoSourcesDir = join22(planDir, "video-sources");
|
|
9781
|
+
rmSync9(videoSourcesDir, { recursive: true, force: true });
|
|
9462
9782
|
if (existsSync15(stagedVideoFrames)) {
|
|
9463
|
-
|
|
9783
|
+
renameWithRetry7(stagedVideoFrames, videoFramesDst);
|
|
9464
9784
|
} else {
|
|
9465
|
-
|
|
9785
|
+
mkdirSync10(videoFramesDst, { recursive: true });
|
|
9466
9786
|
}
|
|
9467
|
-
if (existsSync15(finalCompiledDir))
|
|
9468
|
-
|
|
9787
|
+
if (existsSync15(finalCompiledDir)) rmSync9(finalCompiledDir, { recursive: true, force: true });
|
|
9788
|
+
renameWithRetry7(compiledDir, finalCompiledDir);
|
|
9469
9789
|
const shipVideoSource = createVideoSourceShipper({
|
|
9470
9790
|
planDir,
|
|
9471
9791
|
projectDir,
|
|
@@ -9494,7 +9814,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9494
9814
|
};
|
|
9495
9815
|
})
|
|
9496
9816
|
});
|
|
9497
|
-
|
|
9817
|
+
mkdirSync10(join22(planDir, "meta"), { recursive: true });
|
|
9498
9818
|
writePlanRemoteFiles(
|
|
9499
9819
|
planDir,
|
|
9500
9820
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -9503,8 +9823,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9503
9823
|
sizeBytes
|
|
9504
9824
|
}))
|
|
9505
9825
|
);
|
|
9506
|
-
|
|
9507
|
-
|
|
9826
|
+
writeFileSync7(
|
|
9827
|
+
join22(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
9508
9828
|
JSON.stringify(planVideosJson, null, 2),
|
|
9509
9829
|
"utf-8"
|
|
9510
9830
|
);
|
|
@@ -9583,9 +9903,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9583
9903
|
if (audioResult?.audioError) {
|
|
9584
9904
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
9585
9905
|
}
|
|
9586
|
-
const planAudioPath =
|
|
9906
|
+
const planAudioPath = join22(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
9587
9907
|
if (audioResult?.hasAudio && existsSync15(audioResult.audioOutputPath)) {
|
|
9588
|
-
|
|
9908
|
+
renameWithRetry7(audioResult.audioOutputPath, planAudioPath);
|
|
9589
9909
|
}
|
|
9590
9910
|
if (chunksCarryAudio && existsSync15(planAudioPath)) {
|
|
9591
9911
|
if (options.sliceChunkAudio) {
|
|
@@ -9632,7 +9952,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9632
9952
|
format: config.format
|
|
9633
9953
|
};
|
|
9634
9954
|
try {
|
|
9635
|
-
|
|
9955
|
+
rmSync9(workDir, { recursive: true, force: true });
|
|
9636
9956
|
} catch (err) {
|
|
9637
9957
|
log.warn("[plan] failed to remove temp work dir", {
|
|
9638
9958
|
workDir,
|
|
@@ -9699,10 +10019,10 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
9699
10019
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
9700
10020
|
}
|
|
9701
10021
|
if (!mixed.hasAudio || !existsSync15(mixed.audioOutputPath)) return { hasAudio: false };
|
|
9702
|
-
|
|
10022
|
+
renameWithRetry7(mixed.audioOutputPath, outputPath);
|
|
9703
10023
|
return { hasAudio: true };
|
|
9704
10024
|
} finally {
|
|
9705
|
-
|
|
10025
|
+
rmSync9(c.workDir, { recursive: true, force: true });
|
|
9706
10026
|
}
|
|
9707
10027
|
}
|
|
9708
10028
|
async function plan(projectDir, config, planDir, options = {}) {
|
|
@@ -9724,35 +10044,35 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
9724
10044
|
|
|
9725
10045
|
// src/services/distributed/planV2.ts
|
|
9726
10046
|
import {
|
|
9727
|
-
copyFileSync as
|
|
10047
|
+
copyFileSync as copyFileSync8,
|
|
9728
10048
|
existsSync as existsSync17,
|
|
9729
10049
|
lstatSync as lstatSync2,
|
|
9730
|
-
mkdirSync as
|
|
10050
|
+
mkdirSync as mkdirSync12,
|
|
9731
10051
|
mkdtempSync as mkdtempSync4,
|
|
9732
|
-
readFileSync as
|
|
10052
|
+
readFileSync as readFileSync10,
|
|
9733
10053
|
readdirSync as readdirSync7,
|
|
9734
|
-
rmSync as
|
|
9735
|
-
statSync as
|
|
9736
|
-
writeFileSync as
|
|
10054
|
+
rmSync as rmSync11,
|
|
10055
|
+
statSync as statSync7,
|
|
10056
|
+
writeFileSync as writeFileSync9
|
|
9737
10057
|
} from "node:fs";
|
|
9738
10058
|
import { tmpdir } from "node:os";
|
|
9739
|
-
import { dirname as
|
|
10059
|
+
import { dirname as dirname12, isAbsolute as isAbsolute4, join as join25, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
|
|
9740
10060
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
9741
|
-
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as
|
|
10061
|
+
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
9742
10062
|
|
|
9743
10063
|
// src/services/distributed/planV2Publisher.ts
|
|
9744
10064
|
import {
|
|
9745
|
-
copyFileSync as
|
|
10065
|
+
copyFileSync as copyFileSync7,
|
|
9746
10066
|
existsSync as existsSync16,
|
|
9747
10067
|
linkSync as linkSync2,
|
|
9748
|
-
mkdirSync as
|
|
10068
|
+
mkdirSync as mkdirSync11,
|
|
9749
10069
|
mkdtempSync as mkdtempSync3,
|
|
9750
|
-
rmSync as
|
|
9751
|
-
statSync as
|
|
9752
|
-
writeFileSync as
|
|
10070
|
+
rmSync as rmSync10,
|
|
10071
|
+
statSync as statSync6,
|
|
10072
|
+
writeFileSync as writeFileSync8
|
|
9753
10073
|
} from "node:fs";
|
|
9754
|
-
import { renameWithRetry as
|
|
9755
|
-
import { dirname as
|
|
10074
|
+
import { renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
10075
|
+
import { dirname as dirname11, join as join24 } from "node:path";
|
|
9756
10076
|
|
|
9757
10077
|
// src/services/distributed/planV2Errors.ts
|
|
9758
10078
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -9767,7 +10087,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
9767
10087
|
};
|
|
9768
10088
|
|
|
9769
10089
|
// src/services/distributed/planV2Layout.ts
|
|
9770
|
-
import { join as
|
|
10090
|
+
import { join as join23 } from "node:path";
|
|
9771
10091
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
9772
10092
|
function assertPlanV2Sha256(value, field) {
|
|
9773
10093
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -9777,7 +10097,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
9777
10097
|
}
|
|
9778
10098
|
function planV2BlobPath(root, sha256) {
|
|
9779
10099
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
9780
|
-
return
|
|
10100
|
+
return join23(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
9781
10101
|
}
|
|
9782
10102
|
|
|
9783
10103
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -9817,12 +10137,12 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9817
10137
|
}
|
|
9818
10138
|
this.destinationDir = destinationDir;
|
|
9819
10139
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
9820
|
-
|
|
9821
|
-
this.temporaryDir = mkdtempSync3(
|
|
10140
|
+
mkdirSync11(dirname11(destinationDir), { recursive: true });
|
|
10141
|
+
this.temporaryDir = mkdtempSync3(join24(dirname11(destinationDir), ".plan-v2-publish-"));
|
|
9822
10142
|
}
|
|
9823
10143
|
async putBlob(blob) {
|
|
9824
10144
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
9825
|
-
const sourceSize =
|
|
10145
|
+
const sourceSize = statSync6(blob.sourcePath).size;
|
|
9826
10146
|
if (sourceSize !== blob.sizeBytes) {
|
|
9827
10147
|
throw new PlanV2IntegrityError(
|
|
9828
10148
|
`published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
|
|
@@ -9830,19 +10150,19 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9830
10150
|
}
|
|
9831
10151
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
9832
10152
|
if (existsSync16(destinationPath)) return;
|
|
9833
|
-
|
|
9834
|
-
const stagingDir = mkdtempSync3(
|
|
9835
|
-
const temporaryPath =
|
|
10153
|
+
mkdirSync11(dirname11(destinationPath), { recursive: true });
|
|
10154
|
+
const stagingDir = mkdtempSync3(join24(dirname11(destinationPath), ".plan-v2-blob-"));
|
|
10155
|
+
const temporaryPath = join24(stagingDir, "blob");
|
|
9836
10156
|
try {
|
|
9837
10157
|
try {
|
|
9838
10158
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
9839
10159
|
} catch (error) {
|
|
9840
10160
|
if (!canFallbackToCopy(error)) throw error;
|
|
9841
|
-
|
|
10161
|
+
copyFileSync7(blob.sourcePath, temporaryPath);
|
|
9842
10162
|
}
|
|
9843
|
-
|
|
10163
|
+
renameWithRetry8(temporaryPath, destinationPath);
|
|
9844
10164
|
} finally {
|
|
9845
|
-
|
|
10165
|
+
rmSync10(stagingDir, { recursive: true, force: true });
|
|
9846
10166
|
}
|
|
9847
10167
|
}
|
|
9848
10168
|
async commitManifest(manifestBytes) {
|
|
@@ -9853,13 +10173,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9853
10173
|
);
|
|
9854
10174
|
}
|
|
9855
10175
|
}
|
|
9856
|
-
|
|
9857
|
-
|
|
10176
|
+
writeFileSync8(join24(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
10177
|
+
renameWithRetry8(this.temporaryDir, this.destinationDir);
|
|
9858
10178
|
this.#committed = true;
|
|
9859
10179
|
}
|
|
9860
10180
|
async abort() {
|
|
9861
10181
|
if (!this.#committed) {
|
|
9862
|
-
|
|
10182
|
+
rmSync10(this.temporaryDir, { recursive: true, force: true });
|
|
9863
10183
|
}
|
|
9864
10184
|
}
|
|
9865
10185
|
};
|
|
@@ -9878,7 +10198,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
9878
10198
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
9879
10199
|
}
|
|
9880
10200
|
function readJsonFile(path, label) {
|
|
9881
|
-
const contents =
|
|
10201
|
+
const contents = readFileSync10(path, "utf-8");
|
|
9882
10202
|
try {
|
|
9883
10203
|
const parsed = JSON.parse(contents);
|
|
9884
10204
|
return parsed;
|
|
@@ -9895,10 +10215,10 @@ function assertSafeRelativePath(path) {
|
|
|
9895
10215
|
}
|
|
9896
10216
|
function listFiles(root) {
|
|
9897
10217
|
const files = [];
|
|
9898
|
-
const rootResolved =
|
|
10218
|
+
const rootResolved = resolve9(root);
|
|
9899
10219
|
function walk(dir) {
|
|
9900
10220
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
9901
|
-
const absolutePath =
|
|
10221
|
+
const absolutePath = join25(dir, entry.name);
|
|
9902
10222
|
if (entry.isDirectory()) {
|
|
9903
10223
|
walk(absolutePath);
|
|
9904
10224
|
} else if (entry.isFile()) {
|
|
@@ -9925,13 +10245,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
9925
10245
|
}
|
|
9926
10246
|
}
|
|
9927
10247
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
9928
|
-
const videoRoot =
|
|
10248
|
+
const videoRoot = join25(executionPlanDir, "video-frames");
|
|
9929
10249
|
if (!existsSync17(videoRoot)) return;
|
|
9930
10250
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
9931
10251
|
if (!videoEntry.isDirectory()) continue;
|
|
9932
|
-
const videoDir =
|
|
10252
|
+
const videoDir = join25(videoRoot, videoEntry.name);
|
|
9933
10253
|
if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
9934
|
-
const sentinelPath =
|
|
10254
|
+
const sentinelPath = join25(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
9935
10255
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
9936
10256
|
}
|
|
9937
10257
|
}
|
|
@@ -9941,8 +10261,8 @@ function isExtractionCacheCompleteSentinelPath(path) {
|
|
|
9941
10261
|
return segments.length === 3 && segments[0] === "video-frames" && segments[1] !== "" && segments[2] === EXTRACTION_CACHE_COMPLETE_SENTINEL;
|
|
9942
10262
|
}
|
|
9943
10263
|
function resolveExtractedVideoOutputDir(planDir, videoId) {
|
|
9944
|
-
const videoRoot =
|
|
9945
|
-
const outputDir =
|
|
10264
|
+
const videoRoot = resolve9(planDir, "video-frames");
|
|
10265
|
+
const outputDir = resolve9(videoRoot, videoId);
|
|
9946
10266
|
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep3}`)) {
|
|
9947
10267
|
throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
|
|
9948
10268
|
}
|
|
@@ -9978,7 +10298,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
9978
10298
|
if (video.deferredRange) {
|
|
9979
10299
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
9980
10300
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
9981
|
-
framePaths2.set(index,
|
|
10301
|
+
framePaths2.set(index, join25(outputDir, String(index)));
|
|
9982
10302
|
}
|
|
9983
10303
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
9984
10304
|
}
|
|
@@ -9986,7 +10306,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
9986
10306
|
const framePaths = /* @__PURE__ */ new Map();
|
|
9987
10307
|
for (const frameName of frameNames) {
|
|
9988
10308
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
9989
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
10309
|
+
assertValidExtractionCacheCompleteSentinel(join25(outputDir, frameName));
|
|
9990
10310
|
continue;
|
|
9991
10311
|
}
|
|
9992
10312
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -10005,7 +10325,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10005
10325
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
10006
10326
|
);
|
|
10007
10327
|
}
|
|
10008
|
-
framePaths.set(frameIndex,
|
|
10328
|
+
framePaths.set(frameIndex, join25(outputDir, frameName));
|
|
10009
10329
|
}
|
|
10010
10330
|
return {
|
|
10011
10331
|
...video,
|
|
@@ -10026,11 +10346,11 @@ function parsePlanVideosJson2(value) {
|
|
|
10026
10346
|
}
|
|
10027
10347
|
}
|
|
10028
10348
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10029
|
-
const videosPath =
|
|
10349
|
+
const videosPath = join25(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10030
10350
|
if (!existsSync17(videosPath)) return;
|
|
10031
10351
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10032
10352
|
for (const video of videos.extracted) {
|
|
10033
|
-
|
|
10353
|
+
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
10034
10354
|
}
|
|
10035
10355
|
}
|
|
10036
10356
|
function parseChunkSlices(value) {
|
|
@@ -10060,13 +10380,13 @@ function parseChunkSlices(value) {
|
|
|
10060
10380
|
});
|
|
10061
10381
|
}
|
|
10062
10382
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10063
|
-
const videoRoot =
|
|
10383
|
+
const videoRoot = join25(executionPlanDir, "video-frames");
|
|
10064
10384
|
const hasExtractedFrames = existsSync17(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10065
|
-
const videosPath =
|
|
10385
|
+
const videosPath = join25(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10066
10386
|
if (!existsSync17(videosPath)) {
|
|
10067
10387
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10068
10388
|
}
|
|
10069
|
-
const chunksPath =
|
|
10389
|
+
const chunksPath = join25(executionPlanDir, "meta", "chunks.json");
|
|
10070
10390
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10071
10391
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
10072
10392
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -10103,7 +10423,7 @@ function collectChunkVideoDependencies(input) {
|
|
|
10103
10423
|
for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
|
|
10104
10424
|
const globalTime = frame * input.fpsDen / input.fpsNum;
|
|
10105
10425
|
for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
|
|
10106
|
-
const path = input.deferredSources.get(videoId) ?? relative6(
|
|
10426
|
+
const path = input.deferredSources.get(videoId) ?? relative6(resolve9(input.executionPlanDir), payload.framePath).split(sep3).join("/");
|
|
10107
10427
|
assertSafeRelativePath(path);
|
|
10108
10428
|
const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
|
|
10109
10429
|
indexes.add(chunk.index);
|
|
@@ -10121,21 +10441,21 @@ function computeManifestHash(manifest) {
|
|
|
10121
10441
|
}
|
|
10122
10442
|
function writeBlob(sourcePath, destinationPath) {
|
|
10123
10443
|
if (existsSync17(destinationPath)) return;
|
|
10124
|
-
|
|
10125
|
-
const temporaryDir = mkdtempSync4(
|
|
10126
|
-
const temporaryPath =
|
|
10444
|
+
mkdirSync12(dirname12(destinationPath), { recursive: true });
|
|
10445
|
+
const temporaryDir = mkdtempSync4(join25(dirname12(destinationPath), ".plan-v2-blob-"));
|
|
10446
|
+
const temporaryPath = join25(temporaryDir, "blob");
|
|
10127
10447
|
try {
|
|
10128
|
-
|
|
10129
|
-
|
|
10448
|
+
copyFileSync8(sourcePath, temporaryPath);
|
|
10449
|
+
renameWithRetry9(temporaryPath, destinationPath);
|
|
10130
10450
|
} finally {
|
|
10131
|
-
|
|
10451
|
+
rmSync11(temporaryDir, { recursive: true, force: true });
|
|
10132
10452
|
}
|
|
10133
10453
|
}
|
|
10134
10454
|
function publishBlobFor(absolutePath, digests) {
|
|
10135
10455
|
return {
|
|
10136
10456
|
sourcePath: absolutePath,
|
|
10137
10457
|
sha256: planFileSha256(absolutePath, digests),
|
|
10138
|
-
sizeBytes:
|
|
10458
|
+
sizeBytes: statSync7(absolutePath).size
|
|
10139
10459
|
};
|
|
10140
10460
|
}
|
|
10141
10461
|
async function putBlobs(publisher, blobs) {
|
|
@@ -10159,7 +10479,7 @@ async function putTasks(tasks) {
|
|
|
10159
10479
|
if (failure) throw failure.reason;
|
|
10160
10480
|
}
|
|
10161
10481
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
10162
|
-
const executionPlanPath =
|
|
10482
|
+
const executionPlanPath = join25(executionPlanDir, "plan.json");
|
|
10163
10483
|
if (!existsSync17(executionPlanPath)) {
|
|
10164
10484
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
10165
10485
|
}
|
|
@@ -10187,7 +10507,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10187
10507
|
}
|
|
10188
10508
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
10189
10509
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
10190
|
-
const encoder = readJsonFile(
|
|
10510
|
+
const encoder = readJsonFile(join25(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
10191
10511
|
const files = listFiles(executionPlanDir);
|
|
10192
10512
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
10193
10513
|
for (const file of files) {
|
|
@@ -10247,21 +10567,21 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
10247
10567
|
if (publication.storedBlobs.length > 0) {
|
|
10248
10568
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
10249
10569
|
}
|
|
10250
|
-
|
|
10251
|
-
const tempDir = mkdtempSync4(
|
|
10570
|
+
mkdirSync12(dirname12(planV2Dir), { recursive: true });
|
|
10571
|
+
const tempDir = mkdtempSync4(join25(dirname12(planV2Dir), ".plan-v2-build-"));
|
|
10252
10572
|
try {
|
|
10253
10573
|
for (const blob of publication.blobs) {
|
|
10254
10574
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
10255
10575
|
}
|
|
10256
|
-
|
|
10257
|
-
|
|
10576
|
+
writeFileSync9(
|
|
10577
|
+
join25(tempDir, "plan.json"),
|
|
10258
10578
|
canonicalJsonStringify(publication.manifest),
|
|
10259
10579
|
"utf-8"
|
|
10260
10580
|
);
|
|
10261
|
-
|
|
10581
|
+
renameWithRetry9(tempDir, planV2Dir);
|
|
10262
10582
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
10263
10583
|
} catch (error) {
|
|
10264
|
-
|
|
10584
|
+
rmSync11(tempDir, { recursive: true, force: true });
|
|
10265
10585
|
throw error;
|
|
10266
10586
|
}
|
|
10267
10587
|
}
|
|
@@ -10301,7 +10621,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
|
10301
10621
|
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
10302
10622
|
}
|
|
10303
10623
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
10304
|
-
const stagingRoot = mkdtempSync4(
|
|
10624
|
+
const stagingRoot = mkdtempSync4(join25(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
|
|
10305
10625
|
const digests = /* @__PURE__ */ new Map();
|
|
10306
10626
|
try {
|
|
10307
10627
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -10312,7 +10632,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
10312
10632
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
10313
10633
|
whileAudioMixes: () => putBlobs(
|
|
10314
10634
|
publisher,
|
|
10315
|
-
listFiles(
|
|
10635
|
+
listFiles(join25(stagingRoot, "compiled")).map(
|
|
10316
10636
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
10317
10637
|
)
|
|
10318
10638
|
),
|
|
@@ -10328,7 +10648,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
10328
10648
|
}
|
|
10329
10649
|
throw error;
|
|
10330
10650
|
} finally {
|
|
10331
|
-
|
|
10651
|
+
rmSync11(stagingRoot, { recursive: true, force: true });
|
|
10332
10652
|
}
|
|
10333
10653
|
}
|
|
10334
10654
|
async function planV2(projectDir, config, planV2Dir) {
|
|
@@ -10337,14 +10657,14 @@ async function planV2(projectDir, config, planV2Dir) {
|
|
|
10337
10657
|
}
|
|
10338
10658
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
10339
10659
|
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
10340
|
-
stagingParentDir:
|
|
10660
|
+
stagingParentDir: dirname12(planV2Dir)
|
|
10341
10661
|
});
|
|
10342
10662
|
return resultFromManifest(planV2Dir, manifest);
|
|
10343
10663
|
}
|
|
10344
10664
|
function resultFromManifest(planV2Dir, manifest) {
|
|
10345
10665
|
return {
|
|
10346
10666
|
planDir: planV2Dir,
|
|
10347
|
-
manifestPath:
|
|
10667
|
+
manifestPath: join25(planV2Dir, "plan.json"),
|
|
10348
10668
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
10349
10669
|
planHash: manifest.planHash,
|
|
10350
10670
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -10480,7 +10800,7 @@ function parsePlanV2Manifest(value) {
|
|
|
10480
10800
|
return parsed;
|
|
10481
10801
|
}
|
|
10482
10802
|
function readPlanV2Manifest(planV2Dir) {
|
|
10483
|
-
const manifestPath =
|
|
10803
|
+
const manifestPath = join25(planV2Dir, "plan.json");
|
|
10484
10804
|
if (!existsSync17(manifestPath)) {
|
|
10485
10805
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
10486
10806
|
}
|
|
@@ -10503,7 +10823,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
10503
10823
|
if (!existsSync17(sourcePath)) {
|
|
10504
10824
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
10505
10825
|
}
|
|
10506
|
-
const stats =
|
|
10826
|
+
const stats = statSync7(sourcePath);
|
|
10507
10827
|
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
10508
10828
|
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
10509
10829
|
}
|
|
@@ -10525,17 +10845,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
10525
10845
|
artifact,
|
|
10526
10846
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
10527
10847
|
}));
|
|
10528
|
-
|
|
10529
|
-
const tempDir = mkdtempSync4(
|
|
10848
|
+
mkdirSync12(dirname12(destinationDir), { recursive: true });
|
|
10849
|
+
const tempDir = mkdtempSync4(join25(dirname12(destinationDir), ".plan-v2-materialize-"));
|
|
10530
10850
|
try {
|
|
10531
10851
|
for (const { artifact, sourcePath } of verified) {
|
|
10532
|
-
const destinationPath =
|
|
10533
|
-
|
|
10534
|
-
|
|
10852
|
+
const destinationPath = join25(tempDir, ...artifact.path.split("/"));
|
|
10853
|
+
mkdirSync12(dirname12(destinationPath), { recursive: true });
|
|
10854
|
+
copyFileSync8(sourcePath, destinationPath);
|
|
10535
10855
|
}
|
|
10536
10856
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
10537
|
-
|
|
10538
|
-
|
|
10857
|
+
writeFileSync9(
|
|
10858
|
+
join25(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
10539
10859
|
canonicalJsonStringify({
|
|
10540
10860
|
manifest,
|
|
10541
10861
|
target,
|
|
@@ -10543,9 +10863,9 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
10543
10863
|
}),
|
|
10544
10864
|
"utf-8"
|
|
10545
10865
|
);
|
|
10546
|
-
|
|
10866
|
+
renameWithRetry9(tempDir, destinationDir);
|
|
10547
10867
|
} catch (error) {
|
|
10548
|
-
|
|
10868
|
+
rmSync11(tempDir, { recursive: true, force: true });
|
|
10549
10869
|
throw error;
|
|
10550
10870
|
}
|
|
10551
10871
|
return {
|
|
@@ -10558,7 +10878,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
10558
10878
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
10559
10879
|
// before the container change carries the legacy name, and materializing it
|
|
10560
10880
|
// under the new one would point at a file that was never written.
|
|
10561
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
10881
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join25(destinationDir, audioArtifact.path) : null : null
|
|
10562
10882
|
};
|
|
10563
10883
|
}
|
|
10564
10884
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -10577,7 +10897,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
10577
10897
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
10578
10898
|
}
|
|
10579
10899
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
10580
|
-
const markerPath =
|
|
10900
|
+
const markerPath = join25(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
10581
10901
|
if (!existsSync17(markerPath)) return null;
|
|
10582
10902
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
10583
10903
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
@@ -10595,8 +10915,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
10595
10915
|
assertRemoteSources(remoteSources, required);
|
|
10596
10916
|
for (const artifact of required) {
|
|
10597
10917
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
10598
|
-
const path =
|
|
10599
|
-
if (!existsSync17(path) ||
|
|
10918
|
+
const path = join25(planDir, ...artifact.path.split("/"));
|
|
10919
|
+
if (!existsSync17(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
10600
10920
|
throw new PlanV2IntegrityError(
|
|
10601
10921
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
10602
10922
|
);
|
|
@@ -10609,23 +10929,23 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
10609
10929
|
}
|
|
10610
10930
|
|
|
10611
10931
|
// src/services/distributed/planV2Execution.ts
|
|
10612
|
-
import { mkdtempSync as mkdtempSync6, rmSync as
|
|
10932
|
+
import { mkdtempSync as mkdtempSync6, rmSync as rmSync14 } from "node:fs";
|
|
10613
10933
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
10614
|
-
import { join as
|
|
10934
|
+
import { join as join28 } from "node:path";
|
|
10615
10935
|
|
|
10616
10936
|
// src/services/distributed/assemble.ts
|
|
10617
10937
|
import {
|
|
10618
10938
|
cpSync as cpSync3,
|
|
10619
10939
|
existsSync as existsSync18,
|
|
10620
|
-
mkdirSync as
|
|
10940
|
+
mkdirSync as mkdirSync13,
|
|
10621
10941
|
mkdtempSync as mkdtempSync5,
|
|
10622
|
-
readFileSync as
|
|
10942
|
+
readFileSync as readFileSync11,
|
|
10623
10943
|
readdirSync as readdirSync8,
|
|
10624
|
-
rmSync as
|
|
10625
|
-
statSync as
|
|
10626
|
-
writeFileSync as
|
|
10944
|
+
rmSync as rmSync12,
|
|
10945
|
+
statSync as statSync8,
|
|
10946
|
+
writeFileSync as writeFileSync10
|
|
10627
10947
|
} from "node:fs";
|
|
10628
|
-
import { dirname as
|
|
10948
|
+
import { dirname as dirname13, join as join26 } from "node:path";
|
|
10629
10949
|
import {
|
|
10630
10950
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
10631
10951
|
describeFfmpegFailure as describeFfmpegFailure4,
|
|
@@ -10633,9 +10953,9 @@ import {
|
|
|
10633
10953
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
|
|
10634
10954
|
muxVideoWithAudio as muxVideoWithAudio2,
|
|
10635
10955
|
resolveConfig as resolveConfig4,
|
|
10636
|
-
runFfmpeg as
|
|
10956
|
+
runFfmpeg as runFfmpeg8,
|
|
10637
10957
|
appendSoftwareEncoderThreadsArg,
|
|
10638
|
-
renameWithRetry as
|
|
10958
|
+
renameWithRetry as renameWithRetry10
|
|
10639
10959
|
} from "@hyperframes/engine";
|
|
10640
10960
|
import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
|
|
10641
10961
|
function cfrRateControlArgs(locked) {
|
|
@@ -10661,13 +10981,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10661
10981
|
outputPath,
|
|
10662
10982
|
durationMs: Date.now() - start,
|
|
10663
10983
|
framesEncoded: plan2.totalFrames,
|
|
10664
|
-
fileSize:
|
|
10984
|
+
fileSize: statSync8(outputPath).size
|
|
10665
10985
|
};
|
|
10666
10986
|
}
|
|
10667
10987
|
const format = plan2.dimensions.format;
|
|
10668
10988
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
10669
|
-
if (!existsSync18(
|
|
10670
|
-
|
|
10989
|
+
if (!existsSync18(dirname13(outputPath))) {
|
|
10990
|
+
mkdirSync13(dirname13(outputPath), { recursive: true });
|
|
10671
10991
|
}
|
|
10672
10992
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
10673
10993
|
const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -10689,10 +11009,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10689
11009
|
};
|
|
10690
11010
|
try {
|
|
10691
11011
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
10692
|
-
const finalPath =
|
|
11012
|
+
const finalPath = join26(workDir, `output.${format}`);
|
|
10693
11013
|
const audio = audioPath !== null && existsSync18(audioPath) ? audioPath : null;
|
|
10694
11014
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
10695
|
-
|
|
11015
|
+
renameWithRetry10(finalPath, outputPath);
|
|
10696
11016
|
log.info("[assemble] stage timings", {
|
|
10697
11017
|
...stageMs,
|
|
10698
11018
|
chunks: chunkPaths.length,
|
|
@@ -10700,7 +11020,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10700
11020
|
});
|
|
10701
11021
|
} finally {
|
|
10702
11022
|
try {
|
|
10703
|
-
|
|
11023
|
+
rmSync12(workDir, { recursive: true, force: true });
|
|
10704
11024
|
} catch (err) {
|
|
10705
11025
|
log.warn("[assemble] failed to remove work dir", {
|
|
10706
11026
|
workDir,
|
|
@@ -10708,7 +11028,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10708
11028
|
});
|
|
10709
11029
|
}
|
|
10710
11030
|
}
|
|
10711
|
-
const fileSize = existsSync18(outputPath) ?
|
|
11031
|
+
const fileSize = existsSync18(outputPath) ? statSync8(outputPath).size : 0;
|
|
10712
11032
|
return {
|
|
10713
11033
|
outputPath,
|
|
10714
11034
|
durationMs: Date.now() - start,
|
|
@@ -10717,18 +11037,18 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10717
11037
|
};
|
|
10718
11038
|
}
|
|
10719
11039
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
10720
|
-
const planJsonPath =
|
|
10721
|
-
const chunksJsonPath =
|
|
11040
|
+
const planJsonPath = join26(planDir, "plan.json");
|
|
11041
|
+
const chunksJsonPath = join26(planDir, "meta", "chunks.json");
|
|
10722
11042
|
if (!existsSync18(planJsonPath)) {
|
|
10723
11043
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
10724
11044
|
}
|
|
10725
|
-
const plan2 = JSON.parse(
|
|
11045
|
+
const plan2 = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
|
|
10726
11046
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
10727
11047
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
10728
11048
|
if (!existsSync18(chunksJsonPath)) {
|
|
10729
11049
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
10730
11050
|
}
|
|
10731
|
-
const chunks = JSON.parse(
|
|
11051
|
+
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
10732
11052
|
if (chunkPaths.length !== chunks.length) {
|
|
10733
11053
|
throw new Error(
|
|
10734
11054
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
@@ -10766,7 +11086,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
10766
11086
|
return args;
|
|
10767
11087
|
}
|
|
10768
11088
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
10769
|
-
const cfrOutputPath =
|
|
11089
|
+
const cfrOutputPath = join26(ctx.workDir, `cfr.${ctx.format}`);
|
|
10770
11090
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
10771
11091
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
10772
11092
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -10793,7 +11113,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
10793
11113
|
);
|
|
10794
11114
|
return;
|
|
10795
11115
|
}
|
|
10796
|
-
const paddedAudioPath =
|
|
11116
|
+
const paddedAudioPath = join26(ctx.workDir, "audio-padded.m4a");
|
|
10797
11117
|
const padTrimResult = await ctx.timed(
|
|
10798
11118
|
"audioMs",
|
|
10799
11119
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -10833,9 +11153,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
10833
11153
|
}
|
|
10834
11154
|
}
|
|
10835
11155
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
10836
|
-
const listPath =
|
|
11156
|
+
const listPath = join26(workDir, "concat-list.txt");
|
|
10837
11157
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
10838
|
-
|
|
11158
|
+
writeFileSync10(listPath, `${body}
|
|
10839
11159
|
`, "utf-8");
|
|
10840
11160
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
10841
11161
|
}
|
|
@@ -10850,7 +11170,7 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
|
|
|
10850
11170
|
};
|
|
10851
11171
|
}
|
|
10852
11172
|
async function runAssembleFfmpeg(args, step, signal, timeout) {
|
|
10853
|
-
const result = await
|
|
11173
|
+
const result = await runFfmpeg8(args, { signal, timeout });
|
|
10854
11174
|
if (!result.success) {
|
|
10855
11175
|
throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
|
|
10856
11176
|
error: describeFfmpegFailure4(result, timeout),
|
|
@@ -10871,13 +11191,13 @@ function stitchesSeparateAudio(planDir) {
|
|
|
10871
11191
|
return lockedAudio(planDir).separateAudio === true;
|
|
10872
11192
|
}
|
|
10873
11193
|
function planFormat(planDir) {
|
|
10874
|
-
const plan2 = JSON.parse(
|
|
11194
|
+
const plan2 = JSON.parse(readFileSync11(join26(planDir, "plan.json"), "utf-8"));
|
|
10875
11195
|
return plan2.dimensions.format;
|
|
10876
11196
|
}
|
|
10877
11197
|
function lockedAudio(planDir) {
|
|
10878
|
-
const encoderJsonPath =
|
|
11198
|
+
const encoderJsonPath = join26(planDir, "meta", "encoder.json");
|
|
10879
11199
|
if (!existsSync18(encoderJsonPath)) return {};
|
|
10880
|
-
return JSON.parse(
|
|
11200
|
+
return JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
|
|
10881
11201
|
}
|
|
10882
11202
|
function readCfrEncoder(planDir, format) {
|
|
10883
11203
|
if (format !== "mp4") {
|
|
@@ -10885,11 +11205,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
10885
11205
|
`[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.`
|
|
10886
11206
|
);
|
|
10887
11207
|
}
|
|
10888
|
-
const encoderJsonPath =
|
|
11208
|
+
const encoderJsonPath = join26(planDir, "meta", "encoder.json");
|
|
10889
11209
|
if (!existsSync18(encoderJsonPath)) {
|
|
10890
11210
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
10891
11211
|
}
|
|
10892
|
-
const encoderJson = JSON.parse(
|
|
11212
|
+
const encoderJson = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
|
|
10893
11213
|
if (encoderJson.encoder === "libx265-software") {
|
|
10894
11214
|
throw new Error(
|
|
10895
11215
|
`[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".`
|
|
@@ -10898,11 +11218,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
10898
11218
|
return encoderJson;
|
|
10899
11219
|
}
|
|
10900
11220
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
10901
|
-
if (existsSync18(outputPath))
|
|
10902
|
-
|
|
11221
|
+
if (existsSync18(outputPath)) rmSync12(outputPath, { recursive: true, force: true });
|
|
11222
|
+
mkdirSync13(outputPath, { recursive: true });
|
|
10903
11223
|
let globalIdx = 0;
|
|
10904
11224
|
for (const chunkDir of chunkPaths) {
|
|
10905
|
-
if (!
|
|
11225
|
+
if (!statSync8(chunkDir).isDirectory()) {
|
|
10906
11226
|
throw new Error(
|
|
10907
11227
|
`[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
|
|
10908
11228
|
);
|
|
@@ -10912,8 +11232,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
10912
11232
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
10913
11233
|
}
|
|
10914
11234
|
for (const frame of frames) {
|
|
10915
|
-
const dst =
|
|
10916
|
-
cpSync3(
|
|
11235
|
+
const dst = join26(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
11236
|
+
cpSync3(join26(chunkDir, frame), dst);
|
|
10917
11237
|
globalIdx += 1;
|
|
10918
11238
|
}
|
|
10919
11239
|
}
|
|
@@ -10923,13 +11243,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
10923
11243
|
);
|
|
10924
11244
|
}
|
|
10925
11245
|
if (audioPath !== null && existsSync18(audioPath)) {
|
|
10926
|
-
const sidecar =
|
|
11246
|
+
const sidecar = join26(outputPath, MIXED_AUDIO_FILENAME4);
|
|
10927
11247
|
cpSync3(audioPath, sidecar);
|
|
10928
11248
|
}
|
|
10929
11249
|
let fileSize = 0;
|
|
10930
11250
|
for (const name of readdirSync8(outputPath)) {
|
|
10931
11251
|
try {
|
|
10932
|
-
fileSize +=
|
|
11252
|
+
fileSize += statSync8(join26(outputPath, name)).size;
|
|
10933
11253
|
} catch {
|
|
10934
11254
|
}
|
|
10935
11255
|
}
|
|
@@ -10943,9 +11263,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
10943
11263
|
|
|
10944
11264
|
// src/services/distributed/renderChunk.ts
|
|
10945
11265
|
import { randomBytes } from "node:crypto";
|
|
10946
|
-
import { existsSync as existsSync19, mkdirSync as
|
|
11266
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync12, readdirSync as readdirSync9, rmSync as rmSync13, writeFileSync as writeFileSync11 } from "node:fs";
|
|
10947
11267
|
import { cpus } from "node:os";
|
|
10948
|
-
import { join as
|
|
11268
|
+
import { join as join27 } from "node:path";
|
|
10949
11269
|
import {
|
|
10950
11270
|
assertSwiftShader,
|
|
10951
11271
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -11064,10 +11384,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
11064
11384
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
11065
11385
|
}
|
|
11066
11386
|
function hashChunkOutput(outputPath, kind) {
|
|
11067
|
-
if (kind === "file") return sha256Hex(
|
|
11387
|
+
if (kind === "file") return sha256Hex(readFileSync12(outputPath));
|
|
11068
11388
|
const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
11069
11389
|
const lines = entries.map(
|
|
11070
|
-
(name) => `${name}\0${sha256Hex(
|
|
11390
|
+
(name) => `${name}\0${sha256Hex(readFileSync12(join27(outputPath, name)))}`
|
|
11071
11391
|
);
|
|
11072
11392
|
return sha256Hex(lines.join("\0"));
|
|
11073
11393
|
}
|
|
@@ -11084,16 +11404,16 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
11084
11404
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
11085
11405
|
const start = Date.now();
|
|
11086
11406
|
const log = defaultLogger;
|
|
11087
|
-
const planJsonPath =
|
|
11088
|
-
const encoderJsonPath =
|
|
11089
|
-
const chunksJsonPath =
|
|
11407
|
+
const planJsonPath = join27(planDir, "plan.json");
|
|
11408
|
+
const encoderJsonPath = join27(planDir, "meta", "encoder.json");
|
|
11409
|
+
const chunksJsonPath = join27(planDir, "meta", "chunks.json");
|
|
11090
11410
|
if (!existsSync19(planJsonPath)) {
|
|
11091
11411
|
throw new RenderChunkValidationError(
|
|
11092
11412
|
MISSING_PLAN_ARTIFACT,
|
|
11093
11413
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
11094
11414
|
);
|
|
11095
11415
|
}
|
|
11096
|
-
const plan2 = JSON.parse(
|
|
11416
|
+
const plan2 = JSON.parse(readFileSync12(planJsonPath, "utf-8"));
|
|
11097
11417
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
11098
11418
|
const planHashStarted = Date.now();
|
|
11099
11419
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -11110,13 +11430,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11110
11430
|
);
|
|
11111
11431
|
}
|
|
11112
11432
|
}
|
|
11113
|
-
const encoder = JSON.parse(
|
|
11114
|
-
const chunks = JSON.parse(
|
|
11115
|
-
const videosJsonPath =
|
|
11433
|
+
const encoder = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
|
|
11434
|
+
const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
|
|
11435
|
+
const videosJsonPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11116
11436
|
let planVideos = null;
|
|
11117
11437
|
if (existsSync19(videosJsonPath)) {
|
|
11118
11438
|
try {
|
|
11119
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
11439
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync12(videosJsonPath, "utf-8")));
|
|
11120
11440
|
} catch (err) {
|
|
11121
11441
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
11122
11442
|
throw new RenderChunkValidationError(
|
|
@@ -11144,7 +11464,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11144
11464
|
}
|
|
11145
11465
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
11146
11466
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
11147
|
-
const compiledDir =
|
|
11467
|
+
const compiledDir = join27(planDir, "compiled");
|
|
11148
11468
|
if (!existsSync19(compiledDir)) {
|
|
11149
11469
|
throw new RenderChunkValidationError(
|
|
11150
11470
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -11201,9 +11521,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11201
11521
|
forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
|
|
11202
11522
|
};
|
|
11203
11523
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
11204
|
-
|
|
11205
|
-
const framesDir =
|
|
11206
|
-
|
|
11524
|
+
mkdirSync14(workDir, { recursive: true });
|
|
11525
|
+
const framesDir = join27(workDir, "captured-frames");
|
|
11526
|
+
mkdirSync14(framesDir, { recursive: true });
|
|
11207
11527
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
11208
11528
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
11209
11529
|
const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -11227,7 +11547,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11227
11547
|
dimensions: plan2.dimensions,
|
|
11228
11548
|
encoder,
|
|
11229
11549
|
videoCount: planVideos?.videos.length ?? 0,
|
|
11230
|
-
html:
|
|
11550
|
+
html: readFileSync12(join27(compiledDir, "index.html"), "utf-8"),
|
|
11231
11551
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
11232
11552
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
11233
11553
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -11356,10 +11676,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11356
11676
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
11357
11677
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
11358
11678
|
if (isPngSequence) {
|
|
11359
|
-
if (!existsSync19(outputChunkPath))
|
|
11679
|
+
if (!existsSync19(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
11360
11680
|
} else {
|
|
11361
|
-
const outDir =
|
|
11362
|
-
if (!existsSync19(outDir))
|
|
11681
|
+
const outDir = join27(outputChunkPath, "..");
|
|
11682
|
+
if (!existsSync19(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
11363
11683
|
}
|
|
11364
11684
|
const encodeInput = {
|
|
11365
11685
|
job,
|
|
@@ -11470,8 +11790,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11470
11790
|
return observedChunkCaptureMode(capturePerfs);
|
|
11471
11791
|
},
|
|
11472
11792
|
resetForScreenshotRetry: () => {
|
|
11473
|
-
|
|
11474
|
-
|
|
11793
|
+
rmSync13(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
11794
|
+
mkdirSync14(framesDir, { recursive: true });
|
|
11475
11795
|
capturePerfs.length = 0;
|
|
11476
11796
|
captureAttempts.length = 0;
|
|
11477
11797
|
job.framesRendered = 0;
|
|
@@ -11513,8 +11833,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11513
11833
|
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
11514
11834
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
11515
11835
|
});
|
|
11516
|
-
|
|
11517
|
-
|
|
11836
|
+
rmSync13(videoOnlyPath, { force: true });
|
|
11837
|
+
rmSync13(audioScratchPath, { force: true });
|
|
11518
11838
|
}
|
|
11519
11839
|
encodeStageMs = Date.now() - encodeStarted;
|
|
11520
11840
|
} finally {
|
|
@@ -11561,11 +11881,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11561
11881
|
producerVersion: plan2.producerVersion,
|
|
11562
11882
|
ffmpegVersion
|
|
11563
11883
|
};
|
|
11564
|
-
|
|
11884
|
+
writeFileSync11(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
11565
11885
|
`, "utf-8");
|
|
11566
11886
|
try {
|
|
11567
|
-
|
|
11568
|
-
|
|
11887
|
+
rmSync13(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
11888
|
+
rmSync13(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
11569
11889
|
} catch (err) {
|
|
11570
11890
|
log.warn("[renderChunk] failed to remove work dir", {
|
|
11571
11891
|
workDir,
|
|
@@ -11594,18 +11914,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11594
11914
|
|
|
11595
11915
|
// src/services/distributed/planV2Execution.ts
|
|
11596
11916
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
11597
|
-
const workRoot = mkdtempSync6(
|
|
11598
|
-
const materializedPlanDir =
|
|
11917
|
+
const workRoot = mkdtempSync6(join28(tmpdir2(), "hf-plan-v2-chunk-"));
|
|
11918
|
+
const materializedPlanDir = join28(workRoot, "plan");
|
|
11599
11919
|
try {
|
|
11600
11920
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
11601
11921
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
11602
11922
|
} finally {
|
|
11603
|
-
|
|
11923
|
+
rmSync14(workRoot, { recursive: true, force: true });
|
|
11604
11924
|
}
|
|
11605
11925
|
}
|
|
11606
11926
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
11607
|
-
const workRoot = mkdtempSync6(
|
|
11608
|
-
const materializedPlanDir =
|
|
11927
|
+
const workRoot = mkdtempSync6(join28(tmpdir2(), "hf-plan-v2-assembler-"));
|
|
11928
|
+
const materializedPlanDir = join28(workRoot, "plan");
|
|
11609
11929
|
try {
|
|
11610
11930
|
const materialized = materializePlanV2Target(
|
|
11611
11931
|
planV2Dir,
|
|
@@ -11620,7 +11940,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
11620
11940
|
options
|
|
11621
11941
|
);
|
|
11622
11942
|
} finally {
|
|
11623
|
-
|
|
11943
|
+
rmSync14(workRoot, { recursive: true, force: true });
|
|
11624
11944
|
}
|
|
11625
11945
|
}
|
|
11626
11946
|
|
|
@@ -11869,9 +12189,9 @@ function validateIntDimension(field, value) {
|
|
|
11869
12189
|
}
|
|
11870
12190
|
|
|
11871
12191
|
// src/services/distributed/projectHash.ts
|
|
11872
|
-
import { closeSync as
|
|
11873
|
-
import { createHash as
|
|
11874
|
-
import { join as
|
|
12192
|
+
import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync9 } from "node:fs";
|
|
12193
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12194
|
+
import { join as join29 } from "node:path";
|
|
11875
12195
|
var SITE_ID_SCHEME = "site-v2\0";
|
|
11876
12196
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
11877
12197
|
function listSiteFiles(projectDir) {
|
|
@@ -11880,11 +12200,11 @@ function listSiteFiles(projectDir) {
|
|
|
11880
12200
|
function walk(dir, rel) {
|
|
11881
12201
|
for (const name of readdirSync10(dir).sort()) {
|
|
11882
12202
|
if (rel === "" && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(name)) continue;
|
|
11883
|
-
const full =
|
|
12203
|
+
const full = join29(dir, name);
|
|
11884
12204
|
const relPath = rel === "" ? name : `${rel}/${name}`;
|
|
11885
12205
|
let stat;
|
|
11886
12206
|
try {
|
|
11887
|
-
stat =
|
|
12207
|
+
stat = statSync9(full);
|
|
11888
12208
|
} catch (err) {
|
|
11889
12209
|
throw new Error(`[listSiteFiles] cannot read ${relPath} (dangling symlink?)`, {
|
|
11890
12210
|
cause: err
|
|
@@ -11905,26 +12225,26 @@ function listSiteFiles(projectDir) {
|
|
|
11905
12225
|
return files;
|
|
11906
12226
|
}
|
|
11907
12227
|
function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
|
|
11908
|
-
const hash =
|
|
12228
|
+
const hash = createHash7("sha256").update(SITE_ID_SCHEME);
|
|
11909
12229
|
const chunk = Buffer.allocUnsafe(READ_CHUNK_BYTES);
|
|
11910
12230
|
for (const rel of files) {
|
|
11911
12231
|
hash.update(rel);
|
|
11912
12232
|
hash.update("\0");
|
|
11913
|
-
const fd =
|
|
12233
|
+
const fd = openSync5(join29(projectDir, rel), "r");
|
|
11914
12234
|
try {
|
|
11915
12235
|
let read;
|
|
11916
|
-
while ((read =
|
|
12236
|
+
while ((read = readSync4(fd, chunk, 0, chunk.length, null)) > 0) {
|
|
11917
12237
|
hash.update(chunk.subarray(0, read));
|
|
11918
12238
|
}
|
|
11919
12239
|
} finally {
|
|
11920
|
-
|
|
12240
|
+
closeSync5(fd);
|
|
11921
12241
|
}
|
|
11922
12242
|
}
|
|
11923
12243
|
return hash.digest("hex").slice(0, 16);
|
|
11924
12244
|
}
|
|
11925
12245
|
|
|
11926
12246
|
// src/services/distributed/audioSidecar.ts
|
|
11927
|
-
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as
|
|
12247
|
+
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg9 } from "@hyperframes/engine";
|
|
11928
12248
|
import { extname as extname5 } from "node:path";
|
|
11929
12249
|
var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
|
|
11930
12250
|
var AUDIO_SIDECAR_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v"]);
|
|
@@ -11935,7 +12255,7 @@ async function buildAudioSidecar(inputPath, outputPath, signal) {
|
|
|
11935
12255
|
const audio = await extractAudioMetadata4(inputPath, { signal }).catch(() => null);
|
|
11936
12256
|
if (!audio) return await ffmpegIsMissing(signal) ? "unavailable" : "none";
|
|
11937
12257
|
if (audio.audioStreamCount !== 1 || (audio.audioStreamLeadSeconds ?? 0) > 0) return "none";
|
|
11938
|
-
const result = await
|
|
12258
|
+
const result = await runFfmpeg9(
|
|
11939
12259
|
["-v", "error", "-y", "-i", inputPath, "-map", "0:a:0", "-vn", "-c:a", "copy"].concat([
|
|
11940
12260
|
"-movflags",
|
|
11941
12261
|
"+faststart",
|
|
@@ -11949,10 +12269,10 @@ async function buildAudioSidecar(inputPath, outputPath, signal) {
|
|
|
11949
12269
|
return isMissing(result) ? "unavailable" : "none";
|
|
11950
12270
|
}
|
|
11951
12271
|
var isMissing = (result) => result.error?.code === "ENOENT";
|
|
11952
|
-
var ffmpegIsMissing = async (signal) => isMissing(await
|
|
12272
|
+
var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg9(["-version"], { signal }));
|
|
11953
12273
|
|
|
11954
12274
|
// src/services/distributed/probeFacts.ts
|
|
11955
|
-
import { writeFileSync as
|
|
12275
|
+
import { writeFileSync as writeFileSync12 } from "node:fs";
|
|
11956
12276
|
import { extname as extname6 } from "node:path";
|
|
11957
12277
|
import { buildProbeFactsText } from "@hyperframes/engine";
|
|
11958
12278
|
var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
|
|
@@ -11961,7 +12281,7 @@ function wantsProbeFacts(path) {
|
|
|
11961
12281
|
}
|
|
11962
12282
|
async function buildProbeFacts(inputPath, outputPath, signal) {
|
|
11963
12283
|
try {
|
|
11964
|
-
|
|
12284
|
+
writeFileSync12(outputPath, await buildProbeFactsText(inputPath, signal));
|
|
11965
12285
|
return "built";
|
|
11966
12286
|
} catch (error) {
|
|
11967
12287
|
if (signal?.aborted) throw error;
|