@hyperframes/producer 0.8.173 → 0.8.174

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/index.js CHANGED
@@ -193,11 +193,11 @@ var defaultLogger = createConsoleLogger("info");
193
193
 
194
194
  // src/services/fileServer.ts
195
195
  var readFileAsync = promisify(readFile);
196
- function isPathInside(child, parent, options = {}) {
196
+ function isPathInside(child2, parent, options = {}) {
197
197
  const { resolveSymlinks = false, pathModule } = options;
198
198
  const resolveFn = pathModule?.resolve ?? resolve2;
199
199
  const separator = pathModule?.sep ?? sep;
200
- const resolvedChild = resolveFn(child);
200
+ const resolvedChild = resolveFn(child2);
201
201
  const resolvedParent = resolveFn(parent);
202
202
  const normalizedChild = resolveSymlinks && existsSync2(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
203
203
  const normalizedParent = resolveSymlinks && existsSync2(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
@@ -3217,10 +3217,10 @@ function aggregateDrawElement(perfs, de) {
3217
3217
  gateReason: gateReasons.length > 0 ? gateReasons.join("|") : void 0,
3218
3218
  gpuRenderer: gpuRenderers.length > 0 ? gpuRenderers.join("|") : void 0,
3219
3219
  workerEncode: perfs.some((p) => p.deWorkerEncode),
3220
- verifyArmed: perfs.reduce((sum, p) => sum + (p.deVerifyArmed ?? 0), 0),
3220
+ verifyArmed: perfs.reduce((sum2, p) => sum2 + (p.deVerifyArmed ?? 0), 0),
3221
3221
  verifyChecked: drain?.verifyChecked ?? 0,
3222
3222
  verifyMinDb: roundDb(drain?.verifyMinDb),
3223
- verifyInitMs: perfs.reduce((sum, p) => sum + (p.deVerifyInitMs ?? 0), 0),
3223
+ verifyInitMs: perfs.reduce((sum2, p) => sum2 + (p.deVerifyInitMs ?? 0), 0),
3224
3224
  selfVerifyFallback: de.selfVerifyFallback,
3225
3225
  fallbackReason: de.fallbackReason,
3226
3226
  fallbackFailedDb: roundDb(de.fallbackFailedDb),
@@ -3229,9 +3229,9 @@ function aggregateDrawElement(perfs, de) {
3229
3229
  blankSuspects: drain?.blankSuspects ?? 0,
3230
3230
  blankDeterministicAccepts: drain?.blankDeterministicAccepts ?? 0,
3231
3231
  blankRecaptures: drain?.blankRecaptures ?? 0,
3232
- boundaryFrames: perfs.reduce((sum, p) => sum + (p.deBoundaryFrames ?? 0), 0),
3233
- ncprFallbacks: perfs.reduce((sum, p) => sum + (p.deNcprFallbacks ?? 0), 0),
3234
- frameTimeouts: perfs.reduce((sum, p) => sum + (p.deFrameTimeouts ?? 0), 0)
3232
+ boundaryFrames: perfs.reduce((sum2, p) => sum2 + (p.deBoundaryFrames ?? 0), 0),
3233
+ ncprFallbacks: perfs.reduce((sum2, p) => sum2 + (p.deNcprFallbacks ?? 0), 0),
3234
+ frameTimeouts: perfs.reduce((sum2, p) => sum2 + (p.deFrameTimeouts ?? 0), 0)
3235
3235
  };
3236
3236
  }
3237
3237
  function maxDefined(values) {
@@ -3246,9 +3246,9 @@ function aggregateChromeMemory(perfs) {
3246
3246
  rendererRssPeakMb: maxDefined(sampled.map((p) => p.chromeRendererRssPeakMb)),
3247
3247
  // Last samples are per session; summing approximates the whole fleet's
3248
3248
  // footprint at the end of capture (workers run concurrently).
3249
- rssLastMb: sampled.reduce((sum, p) => sum + (p.chromeRssLastMb ?? 0), 0),
3249
+ rssLastMb: sampled.reduce((sum2, p) => sum2 + (p.chromeRssLastMb ?? 0), 0),
3250
3250
  gpuProcessSeenLastSample: sampled.some((p) => p.chromeGpuProcessSeenLastSample === true),
3251
- samples: sampled.reduce((sum, p) => sum + (p.chromeMemorySamples ?? 0), 0)
3251
+ samples: sampled.reduce((sum2, p) => sum2 + (p.chromeMemorySamples ?? 0), 0)
3252
3252
  };
3253
3253
  }
3254
3254
  function aggregateDedup(perfs) {
@@ -3266,45 +3266,45 @@ function aggregateDedup(perfs) {
3266
3266
  return {
3267
3267
  enabled: perfs.some((p) => p.staticDedupEnabled),
3268
3268
  armed,
3269
- predictedFrames: perfs.reduce((sum, p) => sum + (p.staticDedupPredicted ?? 0), 0),
3270
- reusedFrames: perfs.reduce((sum, p) => sum + (p.staticDedupReused ?? 0), 0),
3269
+ predictedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupPredicted ?? 0), 0),
3270
+ reusedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupReused ?? 0), 0),
3271
3271
  skipReason: skipReasons.length > 0 ? skipReasons.join("|") : void 0,
3272
3272
  ...verificationPerfs.length === 0 ? {} : {
3273
3273
  verifiedFrames: verificationPerfs.reduce(
3274
- (sum, perf) => sum + (perf.staticDedupVerified ?? 0),
3274
+ (sum2, perf) => sum2 + (perf.staticDedupVerified ?? 0),
3275
3275
  0
3276
3276
  ),
3277
3277
  verificationOutcomes,
3278
3278
  plannedRuns: verificationPerfs.reduce(
3279
- (sum, perf) => sum + (perf.staticDedupVerificationPlannedRuns ?? 0),
3279
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationPlannedRuns ?? 0),
3280
3280
  0
3281
3281
  ),
3282
3282
  completedRuns: verificationPerfs.reduce(
3283
- (sum, perf) => sum + (perf.staticDedupVerificationCompletedRuns ?? 0),
3283
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationCompletedRuns ?? 0),
3284
3284
  0
3285
3285
  ),
3286
3286
  screenshots: verificationPerfs.reduce(
3287
- (sum, perf) => sum + (perf.staticDedupVerificationScreenshots ?? 0),
3287
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationScreenshots ?? 0),
3288
3288
  0
3289
3289
  ),
3290
3290
  seeks: verificationPerfs.reduce(
3291
- (sum, perf) => sum + (perf.staticDedupVerificationSeeks ?? 0),
3291
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationSeeks ?? 0),
3292
3292
  0
3293
3293
  ),
3294
3294
  comparisons: verificationPerfs.reduce(
3295
- (sum, perf) => sum + (perf.staticDedupVerificationComparisons ?? 0),
3295
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationComparisons ?? 0),
3296
3296
  0
3297
3297
  ),
3298
3298
  verificationElapsedMs: verificationPerfs.reduce(
3299
- (sum, perf) => sum + (perf.staticDedupVerificationElapsedMs ?? 0),
3299
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationElapsedMs ?? 0),
3300
3300
  0
3301
3301
  )
3302
3302
  }
3303
3303
  };
3304
3304
  }
3305
3305
  function aggregateBeginFrameReuse(perfs) {
3306
- const noDamageFrames = perfs.reduce((sum, p) => sum + (p.beginFrameNoDamage ?? 0), 0);
3307
- const hasDamageFrames = perfs.reduce((sum, p) => sum + (p.beginFrameHasDamage ?? 0), 0);
3306
+ const noDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameNoDamage ?? 0), 0);
3307
+ const hasDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameHasDamage ?? 0), 0);
3308
3308
  if (noDamageFrames + hasDamageFrames === 0) return void 0;
3309
3309
  return { noDamageFrames, hasDamageFrames };
3310
3310
  }
@@ -8366,7 +8366,7 @@ var VideoExtractionStageError = class extends Error {
8366
8366
  affectedElementCount: failure.count
8367
8367
  }))
8368
8368
  )) {
8369
- const total = failures.reduce((sum, failure) => sum + failure.count, 0);
8369
+ const total = failures.reduce((sum2, failure) => sum2 + failure.count, 0);
8370
8370
  const breakdown = failures.map((failure) => `${failure.kind}=${failure.count}`).join(",");
8371
8371
  super(`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]`);
8372
8372
  this.code = code;
@@ -10567,6 +10567,16 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10567
10567
  ...loweredDb > 0 ? { audioLoweredDb: loweredDb } : {}
10568
10568
  };
10569
10569
  }
10570
+ async function constrainAacTruePeak(input) {
10571
+ const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
10572
+ return enforceAacTruePeak({
10573
+ audioPath: input.audioPath,
10574
+ targetDurationSeconds: input.targetDurationSeconds,
10575
+ signal: input.signal,
10576
+ probeTruePeak: (audioPath, signal) => defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal),
10577
+ runner: (args) => defaultRunFfmpeg(args, timeoutMs, input.signal)
10578
+ });
10579
+ }
10570
10580
  async function enforceAacTruePeak(input) {
10571
10581
  let scratchDir;
10572
10582
  try {
@@ -11161,7 +11171,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
11161
11171
  return captured;
11162
11172
  }
11163
11173
  function countFrameRanges(ranges) {
11164
- return ranges.reduce((sum, range) => sum + (range.endFrame - range.startFrame), 0);
11174
+ return ranges.reduce((sum2, range) => sum2 + (range.endFrame - range.startFrame), 0);
11165
11175
  }
11166
11176
  async function executeDiskCaptureWithAdaptiveRetry(options) {
11167
11177
  const attempts = [];
@@ -11412,7 +11422,7 @@ function scanElementTags(html) {
11412
11422
  const byTag = Object.fromEntries(
11413
11423
  ranked.slice(0, MAX_REPORTED_ELEMENT_TAGS)
11414
11424
  );
11415
- const otherCount = ranked.slice(MAX_REPORTED_ELEMENT_TAGS).reduce((sum, [, count]) => sum + count, 0);
11425
+ const otherCount = ranked.slice(MAX_REPORTED_ELEMENT_TAGS).reduce((sum2, [, count]) => sum2 + count, 0);
11416
11426
  if (otherCount > 0) byTag.other = (byTag.other ?? 0) + otherCount;
11417
11427
  const arollVideoCount = markup.match(/<video\b[^>]*\bdata-aroll=["']true["'][^>]*>/gi)?.length ?? 0;
11418
11428
  const heygenVideoCount = markup.match(/<video\b[^>]*\bdata-media-source=["']heygen["'][^>]*>/gi)?.length ?? 0;
@@ -14438,15 +14448,15 @@ var ChunkExceedsWorkerTimeoutError = class extends Error {
14438
14448
  function resolveMaxParallelChunks(config, totalFrames, megapixels, log = defaultLogger) {
14439
14449
  if (config.maxParallelChunks !== void 0) return config.maxParallelChunks;
14440
14450
  const { workerTimeoutSeconds, workerConcurrency } = config;
14441
- const pinsChunking = config.chunkSize !== void 0 || config.targetChunkFrames !== void 0;
14442
- if (pinsChunking || workerTimeoutSeconds === void 0 && workerConcurrency === void 0) {
14443
- return DEFAULT_MAX_PARALLEL_CHUNKS;
14444
- }
14445
- const secondsPerFrame = ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME * megapixels;
14446
14451
  const wave = Math.min(
14447
14452
  workerConcurrency ?? DEFAULT_MAX_PARALLEL_CHUNKS,
14448
14453
  MAX_PARALLEL_CHUNKS_CEILING
14449
14454
  );
14455
+ const pinsChunking = config.chunkSize !== void 0 || config.targetChunkFrames !== void 0;
14456
+ if (pinsChunking || workerTimeoutSeconds === void 0 && workerConcurrency === void 0) {
14457
+ return wave;
14458
+ }
14459
+ const secondsPerFrame = ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME * megapixels;
14450
14460
  const minFramesPerChunk = Math.ceil(ESTIMATED_CHUNK_OVERHEAD_SECONDS / secondsPerFrame);
14451
14461
  const wide = Math.max(1, Math.min(wave, Math.floor(totalFrames / minFramesPerChunk)));
14452
14462
  if (workerTimeoutSeconds === void 0) return wide;
@@ -14463,7 +14473,7 @@ function resolveMaxParallelChunks(config, totalFrames, megapixels, log = default
14463
14473
  }
14464
14474
  if (needed > wave) {
14465
14475
  log.warn(
14466
- `[plan] ${needed} chunks are needed to keep each under the ${workerTimeoutSeconds} s worker time limit, but the plan assumes ${wave} workers at once, so the render runs in ${Math.ceil(needed / wave)} waves. Raise --concurrency to ${needed} to render in one wave (Lambda: hyperframes lambda deploy --concurrency ${needed}).`,
14476
+ `[plan] ${needed} chunks are needed to keep each under the ${workerTimeoutSeconds} s worker time limit, but the plan assumes ${wave} workers at once, so the render runs in ${Math.ceil(needed / wave)} waves. Raise --concurrency to ${needed} to render in one wave (Lambda: hyperframes lambda deploy --concurrency ${needed}, or a higher account concurrency quota).`,
14467
14477
  { needed, workerConcurrency: wave, workerTimeoutSeconds }
14468
14478
  );
14469
14479
  }
@@ -14635,21 +14645,6 @@ function buildLockedRenderConfig(input) {
14635
14645
  function carriesChunkAudio(config, options) {
14636
14646
  return options.chunksCarryAudio === true && config.format === "mp4" && config.cfr !== true;
14637
14647
  }
14638
- async function fitPlanAudioToFrames(audioPath, workDir, totalFrames, fps, run) {
14639
- const fittedPath = join30(workDir, "audio-fitted.m4a");
14640
- const result = await padOrTrimAudioToVideoFrameCount({
14641
- videoPath: audioPath,
14642
- probeVideoFrameInfo: async () => ({ frameCount: totalFrames, fpsNum: fps, fpsDen: 1 }),
14643
- audioPath,
14644
- outputPath: fittedPath,
14645
- signal: run.signal,
14646
- timeoutMs: run.timeoutMs
14647
- });
14648
- if (!result.success) {
14649
- throw encoderFailureError("[plan] audio pad/trim failed", result);
14650
- }
14651
- renameSync5(fittedPath, audioPath);
14652
- }
14653
14648
  function resolveEncoderTriple(config) {
14654
14649
  if (config.format === "mp4") {
14655
14650
  return resolveMp4EncoderTriple(config.codec);
@@ -14947,13 +14942,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14947
14942
  "utf-8"
14948
14943
  );
14949
14944
  const digests = options.digests ?? /* @__PURE__ */ new Map();
14945
+ const chunksCarryAudio2 = carriesChunkAudio(config, options);
14946
+ const audioSeconds = chunksCarryAudio2 ? totalFrames / config.fps : probeResult.duration;
14950
14947
  stageStarted = Date.now();
14951
14948
  const [audioOutcome, overlapOutcome] = await Promise.allSettled([
14952
14949
  runAudioStage({
14953
14950
  projectDir,
14954
14951
  workDir,
14955
14952
  compiledDir: finalCompiledDir,
14956
- duration: probeResult.duration,
14953
+ duration: audioSeconds,
14957
14954
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
14958
14955
  audioGain: cfg.audioGain,
14959
14956
  audios: composition.audios,
@@ -14983,12 +14980,16 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
14983
14980
  if (audioResult.hasAudio && existsSync19(audioResult.audioOutputPath)) {
14984
14981
  renameSync5(audioResult.audioOutputPath, planAudioPath);
14985
14982
  }
14986
- const chunksCarryAudio2 = carriesChunkAudio(config, options);
14987
14983
  if (chunksCarryAudio2 && existsSync19(planAudioPath)) {
14988
- await fitPlanAudioToFrames(planAudioPath, workDir, totalFrames, config.fps, {
14984
+ stageStarted = Date.now();
14985
+ const peak = await constrainAacTruePeak({
14986
+ audioPath: planAudioPath,
14987
+ targetDurationSeconds: audioSeconds,
14989
14988
  signal: abortSignal,
14990
14989
  timeoutMs: cfg.ffmpegProcessTimeout
14991
14990
  });
14991
+ if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
14992
+ logPlanStage(log, "audio-peak", stageStarted);
14992
14993
  }
14993
14994
  const ffmpegVersion = await readFfmpegVersion();
14994
14995
  const producerVersion = readProducerVersion();
@@ -15073,8 +15074,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
15073
15074
  producerVersion
15074
15075
  };
15075
15076
  }
15076
- async function plan(projectDir, config, planDir) {
15077
- const executionPlan = await buildLocalExecutionPlan(projectDir, config, planDir);
15077
+ async function plan(projectDir, config, planDir, options = {}) {
15078
+ const executionPlan = await buildLocalExecutionPlan(projectDir, config, planDir, options);
15078
15079
  return {
15079
15080
  planDir: executionPlan.executionPlanDir,
15080
15081
  planProtocol: PLAN_PROTOCOL_V1,
@@ -16704,7 +16705,7 @@ function resolveExtractedVideoOutputDir(planDir, videoId) {
16704
16705
  }
16705
16706
  function artifactTargets(path, videoDependencies, chunksCarryAudio2) {
16706
16707
  if (isPlanAudioArtifactPath(path)) {
16707
- return { chunks: chunksCarryAudio2 ? "all" : [], assembler: true };
16708
+ return chunksCarryAudio2 ? { chunks: "all", assembler: false } : { chunks: [], assembler: true };
16708
16709
  }
16709
16710
  if (path === "plan.json" || path === "meta/chunks.json" || path === "meta/encoder.json") {
16710
16711
  return { chunks: "all", assembler: true };
@@ -17265,7 +17266,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir) {
17265
17266
  planHash: manifest.planHash,
17266
17267
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
17267
17268
  artifactCount: artifacts.length,
17268
- sizeBytes: artifacts.reduce((sum, artifact) => sum + artifact.sizeBytes, 0),
17269
+ sizeBytes: artifacts.reduce((sum2, artifact) => sum2 + artifact.sizeBytes, 0),
17269
17270
  // Join the artifact's OWN name, not the current constant: a plan written
17270
17271
  // before the container change carries the legacy name, and materializing it
17271
17272
  // under the new one would point at a file that was never written.
@@ -17302,24 +17303,24 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
17302
17303
  }
17303
17304
 
17304
17305
  // src/services/distributed/planV2Execution.ts
17305
- import { mkdtempSync as mkdtempSync8, rmSync as rmSync16 } from "node:fs";
17306
+ import { mkdtempSync as mkdtempSync9, rmSync as rmSync17 } from "node:fs";
17306
17307
  import { tmpdir as tmpdir5 } from "node:os";
17307
- import { join as join39 } from "node:path";
17308
+ import { join as join40 } from "node:path";
17308
17309
 
17309
17310
  // src/services/distributed/assemble.ts
17310
17311
  import {
17311
17312
  cpSync as cpSync3,
17312
17313
  existsSync as existsSync25,
17313
- mkdirSync as mkdirSync15,
17314
- mkdtempSync as mkdtempSync7,
17314
+ mkdirSync as mkdirSync16,
17315
+ mkdtempSync as mkdtempSync8,
17315
17316
  readFileSync as readFileSync13,
17316
17317
  readdirSync as readdirSync9,
17317
- renameSync as renameSync8,
17318
- rmSync as rmSync14,
17318
+ renameSync as renameSync9,
17319
+ rmSync as rmSync15,
17319
17320
  statSync as statSync11,
17320
17321
  writeFileSync as writeFileSync11
17321
17322
  } from "node:fs";
17322
- import { dirname as dirname16, join as join37 } from "node:path";
17323
+ import { dirname as dirname17, join as join38 } from "node:path";
17323
17324
  import {
17324
17325
  appendRenderProvenanceArgs,
17325
17326
  describeFfmpegFailure as describeFfmpegFailure2,
@@ -17330,6 +17331,353 @@ import {
17330
17331
  runFfmpeg as runFfmpeg5
17331
17332
  } from "@hyperframes/engine";
17332
17333
  import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
17334
+
17335
+ // src/services/distributed/mp4Stitch.ts
17336
+ import {
17337
+ createReadStream as createReadStream5,
17338
+ createWriteStream,
17339
+ mkdirSync as mkdirSync15,
17340
+ mkdtempSync as mkdtempSync7,
17341
+ renameSync as renameSync8,
17342
+ rmSync as rmSync14
17343
+ } from "node:fs";
17344
+ import { open } from "node:fs/promises";
17345
+ import { dirname as dirname16, join as join37 } from "node:path";
17346
+ import { finished, pipeline } from "node:stream/promises";
17347
+ function boxAt(src, off, at, limit) {
17348
+ let size = src.readUInt32BE(off);
17349
+ let bodyStart = at + 8;
17350
+ if (size === 1) {
17351
+ size = Number(src.readBigUInt64BE(off + 8));
17352
+ bodyStart = at + 16;
17353
+ }
17354
+ if (size < 8 || at + size > limit) throw new Error(`[mp4Stitch] malformed box at ${at}`);
17355
+ return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
17356
+ }
17357
+ function childBoxes(buf, parent) {
17358
+ const boxes = [];
17359
+ for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
17360
+ boxes.push(boxAt(buf, at, at, parent.end));
17361
+ }
17362
+ return boxes;
17363
+ }
17364
+ function findChild(buf, parent, type) {
17365
+ return childBoxes(buf, parent).find((box2) => box2.type === type);
17366
+ }
17367
+ function child(buf, parent, type) {
17368
+ const found = findChild(buf, parent, type);
17369
+ if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
17370
+ return found;
17371
+ }
17372
+ var SCAN_BYTES = 64 * 1024;
17373
+ async function readMp4Layout(read) {
17374
+ const found = /* @__PURE__ */ new Map();
17375
+ let payload;
17376
+ let buf = Buffer.alloc(0);
17377
+ let bufAt = 0;
17378
+ const load = async (start, end) => {
17379
+ if (start >= bufAt && end <= bufAt + buf.length) return;
17380
+ buf = await read(start, Math.max(end, start + SCAN_BYTES));
17381
+ bufAt = start;
17382
+ };
17383
+ for (let at = 0; !(found.has("ftyp") && found.has("moov") && payload); ) {
17384
+ await load(at, at + 16);
17385
+ if (at + 8 > bufAt + buf.length) {
17386
+ throw new Error(`[mp4Stitch] chunk needs ftyp, moov and mdat; found ${[...found.keys()]}`);
17387
+ }
17388
+ const box2 = boxAt(buf, at - bufAt, at, Number.MAX_SAFE_INTEGER);
17389
+ if (box2.type === "mdat") payload ??= box2;
17390
+ if (box2.type === "ftyp" || box2.type === "moov") {
17391
+ await load(at, box2.end);
17392
+ found.set(box2.type, buf.subarray(at - bufAt, box2.end - bufAt));
17393
+ }
17394
+ at = box2.end;
17395
+ }
17396
+ const [ftyp, moov] = [found.get("ftyp"), found.get("moov")];
17397
+ if (moov.length !== moovSize(moov)) throw new Error("[mp4Stitch] moov is cut off");
17398
+ return { ftyp, moov, payloadStart: payload.bodyStart, payloadEnd: payload.end };
17399
+ }
17400
+ var moovSize = (moov) => boxAt(moov, 0, 0, Number.MAX_SAFE_INTEGER).end;
17401
+ function readTracks(moov) {
17402
+ const root = boxAt(moov, 0, 0, moov.length);
17403
+ return childBoxes(moov, root).filter((box2) => box2.type === "trak").map((trak) => readTrack(moov, trak));
17404
+ }
17405
+ function rows(buf, table, columns, countAt = 4) {
17406
+ const count = buf.readUInt32BE(table.bodyStart + countAt);
17407
+ const first = table.bodyStart + countAt + 4;
17408
+ return Array.from(
17409
+ { length: count },
17410
+ (_, row) => Array.from(
17411
+ { length: columns },
17412
+ (_2, col) => buf.readUInt32BE(first + (row * columns + col) * 4)
17413
+ )
17414
+ );
17415
+ }
17416
+ function readMediaTime(moov, trak) {
17417
+ const edts = findChild(moov, trak, "edts");
17418
+ if (!edts) return 0;
17419
+ const elst = child(moov, edts, "elst");
17420
+ const wide = moov[elst.bodyStart] === 1;
17421
+ const entries = moov.readUInt32BE(elst.bodyStart + 4);
17422
+ for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
17423
+ const offset = at + (wide ? 8 : 4);
17424
+ const mediaTime = wide ? Number(moov.readBigInt64BE(offset)) : moov.readInt32BE(offset);
17425
+ if (mediaTime !== -1) return mediaTime;
17426
+ }
17427
+ return 0;
17428
+ }
17429
+ function readSizes(moov, stsz) {
17430
+ const fixedSize = moov.readUInt32BE(stsz.bodyStart + 4);
17431
+ if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
17432
+ return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
17433
+ }
17434
+ function readSync4(moov, stbl, sampleCount) {
17435
+ const stss = findChild(moov, stbl, "stss");
17436
+ if (!stss) return null;
17437
+ const sync = Array(sampleCount).fill(false);
17438
+ for (const [n] of rows(moov, stss, 1)) sync[n - 1] = true;
17439
+ return sync;
17440
+ }
17441
+ function readChunkOffsets(moov, stbl) {
17442
+ const stco = findChild(moov, stbl, "stco");
17443
+ if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
17444
+ return rows(moov, child(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
17445
+ }
17446
+ function readSamplesPerChunk(moov, stbl, chunkCount) {
17447
+ const stsc = rows(moov, child(moov, stbl, "stsc"), 3);
17448
+ return stsc.flatMap(([firstChunk, perChunk, description], i) => {
17449
+ if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
17450
+ const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
17451
+ return Array(nextFirst - firstChunk).fill(perChunk);
17452
+ });
17453
+ }
17454
+ function readTrack(moov, trak) {
17455
+ const mdia = child(moov, trak, "mdia");
17456
+ const hdlr = child(moov, mdia, "hdlr");
17457
+ const stbl = child(moov, child(moov, mdia, "minf"), "stbl");
17458
+ if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
17459
+ const stts = rows(moov, child(moov, stbl, "stts"), 2);
17460
+ const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
17461
+ const sizes = readSizes(moov, child(moov, stbl, "stsz"));
17462
+ const chunkOffsets = readChunkOffsets(moov, stbl);
17463
+ const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
17464
+ if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
17465
+ throw new Error("[mp4Stitch] sample tables disagree on the sample count");
17466
+ }
17467
+ const stsd = child(moov, stbl, "stsd");
17468
+ return {
17469
+ trak,
17470
+ handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
17471
+ timescale: timescaleOf(moov, child(moov, mdia, "mdhd")),
17472
+ mediaTime: readMediaTime(moov, trak),
17473
+ stsd: moov.subarray(stsd.start, stsd.end),
17474
+ durations,
17475
+ sizes,
17476
+ sync: readSync4(moov, stbl, sizes.length),
17477
+ samplesPerChunk,
17478
+ chunkOffsets
17479
+ };
17480
+ }
17481
+ function sum(values) {
17482
+ return values.reduce((total, value) => total + value, 0);
17483
+ }
17484
+ function box(type, ...body) {
17485
+ const header = Buffer.alloc(8);
17486
+ header.writeUInt32BE(8 + sum(body.map((part) => part.length)));
17487
+ header.write(type, 4, "latin1");
17488
+ return Buffer.concat([header, ...body]);
17489
+ }
17490
+ function fullBox(type, version, body) {
17491
+ return box(type, Buffer.from([version, 0, 0, 0]), body);
17492
+ }
17493
+ function u32s(values) {
17494
+ const buf = Buffer.alloc(values.length * 4);
17495
+ values.forEach((value, i) => buf.writeUInt32BE(value, i * 4));
17496
+ return buf;
17497
+ }
17498
+ function runs(values) {
17499
+ const out = [];
17500
+ for (const value of values) {
17501
+ const last = out.at(-1);
17502
+ if (last && last[1] === value) last[0]++;
17503
+ else out.push([1, value]);
17504
+ }
17505
+ return out;
17506
+ }
17507
+ function rebuild(buf, parent, replace) {
17508
+ const parts = childBoxes(buf, parent).map(
17509
+ (box2) => replace[box2.type]?.(box2) ?? buf.subarray(box2.start, box2.end)
17510
+ );
17511
+ return box(parent.type, ...parts);
17512
+ }
17513
+ var DURATION_AT = {
17514
+ mvhd: [16, 24],
17515
+ mdhd: [16, 24],
17516
+ tkhd: [20, 28]
17517
+ };
17518
+ function withDuration(buf, target, duration) {
17519
+ const copy = Buffer.from(buf.subarray(target.start, target.end));
17520
+ const body = target.bodyStart - target.start;
17521
+ const wide = copy[body] === 1;
17522
+ const at = body + DURATION_AT[target.type][wide ? 1 : 0];
17523
+ if (wide) copy.writeBigUInt64BE(BigInt(duration), at);
17524
+ else if (duration <= 4294967295) copy.writeUInt32BE(duration, at);
17525
+ else throw new Error(`[mp4Stitch] ${target.type} duration ${duration} needs a version 1 box`);
17526
+ return copy;
17527
+ }
17528
+ function timescaleOf(buf, header) {
17529
+ return buf.readUInt32BE(header.bodyStart + (buf[header.bodyStart] === 1 ? 20 : 12));
17530
+ }
17531
+ function buildStbl(stsd, track) {
17532
+ const stts = runs(track.durations);
17533
+ const stsc = [];
17534
+ let firstChunk = 1;
17535
+ for (const [count, perChunk] of runs(track.samplesPerChunk)) {
17536
+ stsc.push([firstChunk, perChunk, 1]);
17537
+ firstChunk += count;
17538
+ }
17539
+ const co64 = Buffer.alloc(4 + track.chunkOffsets.length * 8);
17540
+ co64.writeUInt32BE(track.chunkOffsets.length);
17541
+ track.chunkOffsets.forEach((offset, i) => co64.writeBigUInt64BE(BigInt(offset), 4 + i * 8));
17542
+ const syncSamples = track.sync?.flatMap((isSync, i) => isSync ? [i + 1] : []) ?? null;
17543
+ return box(
17544
+ "stbl",
17545
+ stsd,
17546
+ fullBox("stts", 0, u32s([stts.length, ...stts.flat()])),
17547
+ ...syncSamples ? [fullBox("stss", 0, u32s([syncSamples.length, ...syncSamples]))] : [],
17548
+ fullBox("stsc", 0, u32s([stsc.length, ...stsc.flat()])),
17549
+ fullBox("stsz", 0, u32s([0, track.sizes.length, ...track.sizes])),
17550
+ fullBox("co64", 0, co64)
17551
+ );
17552
+ }
17553
+ var codecOf = (track) => track.stsd.toString("latin1", 20, 24);
17554
+ var sameTrack = (track, plan2) => track.handler === plan2.handler && track.timescale === plan2.timescale && codecOf(track) === codecOf(plan2);
17555
+ function planTracksOf(tracks, plan2, index) {
17556
+ let next = 0;
17557
+ const matched = plan2.map((planTrack) => {
17558
+ const track = tracks[next];
17559
+ if (track && sameTrack(track, planTrack)) return tracks[next++];
17560
+ if (planTrack.handler === "soun") return null;
17561
+ throw new Error(`[mp4Stitch] chunk ${index} tracks differ from chunk 0`);
17562
+ });
17563
+ if (next !== tracks.length)
17564
+ throw new Error(`[mp4Stitch] chunk ${index} tracks differ from chunk 0`);
17565
+ for (const track of tracks) {
17566
+ if (track.handler === "vide" && track.mediaTime !== 0) {
17567
+ throw new Error(`[mp4Stitch] chunk ${index} video starts at media time ${track.mediaTime}`);
17568
+ }
17569
+ }
17570
+ return matched;
17571
+ }
17572
+ function buildStitchedHeader(layouts) {
17573
+ const plan2 = readTracks(layouts[0].moov);
17574
+ const chunks = layouts.map((layout, i) => planTracksOf(readTracks(layout.moov), plan2, i));
17575
+ const payloadBases = [];
17576
+ let payloadBytes = 0;
17577
+ for (const layout of layouts) {
17578
+ payloadBases.push(payloadBytes);
17579
+ payloadBytes += layout.payloadEnd - layout.payloadStart;
17580
+ }
17581
+ const template = layouts[0].moov;
17582
+ const root = boxAt(template, 0, 0, template.length);
17583
+ const movieTimescale = timescaleOf(template, child(template, root, "mvhd"));
17584
+ const buildMoov = (dataStart2) => {
17585
+ const traks = plan2.map((track, t) => {
17586
+ const parts = chunks.flatMap((tracks, chunk) => {
17587
+ const part = tracks[t];
17588
+ return part ? [{ ...part, chunk }] : [];
17589
+ });
17590
+ const merged = {
17591
+ durations: parts.flatMap((part) => part.durations),
17592
+ sizes: parts.flatMap((part) => part.sizes),
17593
+ sync: parts.every((part) => !part.sync) ? null : parts.flatMap((part) => part.sync ?? part.sizes.map(() => true)),
17594
+ samplesPerChunk: parts.flatMap((part) => part.samplesPerChunk),
17595
+ chunkOffsets: parts.flatMap(
17596
+ (part) => part.chunkOffsets.map((offset) => {
17597
+ const i = part.chunk;
17598
+ const { payloadStart, payloadEnd } = layouts[i];
17599
+ if (offset < payloadStart || offset >= payloadEnd) {
17600
+ throw new Error(`[mp4Stitch] chunk ${i} has samples outside its mdat`);
17601
+ }
17602
+ return offset - payloadStart + payloadBases[i] + dataStart2;
17603
+ })
17604
+ )
17605
+ };
17606
+ const mediaDuration = sum(merged.durations);
17607
+ const shown = Math.round(
17608
+ (mediaDuration - track.mediaTime) * movieTimescale / track.timescale
17609
+ );
17610
+ const elst = Buffer.alloc(24);
17611
+ elst.writeUInt32BE(1);
17612
+ elst.writeBigUInt64BE(BigInt(shown), 4);
17613
+ elst.writeBigInt64BE(BigInt(track.mediaTime), 12);
17614
+ elst.writeUInt32BE(65536, 20);
17615
+ const edts = track.mediaTime ? box("edts", fullBox("elst", 1, elst)) : Buffer.alloc(0);
17616
+ const trak = rebuild(template, track.trak, {
17617
+ tkhd: (tkhd) => Buffer.concat([withDuration(template, tkhd, shown), edts]),
17618
+ edts: () => Buffer.alloc(0),
17619
+ mdia: (mdia) => rebuild(template, mdia, {
17620
+ mdhd: (mdhd) => withDuration(template, mdhd, mediaDuration),
17621
+ minf: (minf) => rebuild(template, minf, { stbl: () => buildStbl(track.stsd, merged) })
17622
+ })
17623
+ });
17624
+ return { trak, shown };
17625
+ });
17626
+ let next = 0;
17627
+ return rebuild(template, root, {
17628
+ mvhd: (mvhd) => withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown))),
17629
+ trak: () => traks[next++].trak
17630
+ });
17631
+ };
17632
+ const ftyp = layouts[0].ftyp;
17633
+ const dataStart = ftyp.length + buildMoov(0).length + 16;
17634
+ const mdat = Buffer.alloc(16);
17635
+ mdat.writeUInt32BE(1);
17636
+ mdat.write("mdat", 4, "latin1");
17637
+ mdat.writeBigUInt64BE(BigInt(16 + payloadBytes), 8);
17638
+ return Buffer.concat([ftyp, buildMoov(dataStart), mdat]);
17639
+ }
17640
+ function fileRange(path) {
17641
+ return async (start, end) => {
17642
+ const file = await open(path, "r", 384);
17643
+ try {
17644
+ const buf = Buffer.alloc(end - start);
17645
+ const { bytesRead } = await file.read(buf, 0, buf.length, start);
17646
+ return buf.subarray(0, bytesRead);
17647
+ } finally {
17648
+ await file.close();
17649
+ }
17650
+ };
17651
+ }
17652
+ async function copyPayload(path, layout, index, out) {
17653
+ const { payloadStart, payloadEnd } = layout;
17654
+ const payload = createReadStream5(path, { start: payloadStart, end: payloadEnd - 1 });
17655
+ await pipeline(payload, out, { end: false });
17656
+ if (payload.bytesRead !== payloadEnd - payloadStart) {
17657
+ throw new Error(
17658
+ `[mp4Stitch] chunk ${index} mdat holds ${payload.bytesRead} of ${payloadEnd - payloadStart} payload bytes`
17659
+ );
17660
+ }
17661
+ }
17662
+ async function writeStitchedMp4(chunkPaths, outputPath) {
17663
+ const layouts = await Promise.all(chunkPaths.map((path) => readMp4Layout(fileRange(path))));
17664
+ const header = buildStitchedHeader(layouts);
17665
+ mkdirSync15(dirname16(outputPath), { recursive: true });
17666
+ const workDir = mkdtempSync7(`${outputPath}.stitch-`);
17667
+ const out = createWriteStream(join37(workDir, "out.mp4"));
17668
+ try {
17669
+ out.write(header);
17670
+ for (const [i, path] of chunkPaths.entries()) await copyPayload(path, layouts[i], i, out);
17671
+ out.end();
17672
+ await finished(out);
17673
+ renameSync8(join37(workDir, "out.mp4"), outputPath);
17674
+ } finally {
17675
+ out.destroy();
17676
+ rmSync14(workDir, { recursive: true, force: true });
17677
+ }
17678
+ }
17679
+
17680
+ // src/services/distributed/assemble.ts
17333
17681
  function cfrRateControlArgs(locked) {
17334
17682
  const preset = getEncoderPreset5(locked.quality ?? "standard", "mp4");
17335
17683
  if (locked.crf == null && locked.bitrate) {
@@ -17345,12 +17693,21 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17345
17693
  if (plan2.dimensions.format === "png-sequence") {
17346
17694
  return mergePngFrameDirs(chunkPaths, outputPath, plan2.totalFrames, audioPath, start);
17347
17695
  }
17696
+ if (chunksAreStitchedMp4(planDir, cfr)) {
17697
+ await writeStitchedMp4(chunkPaths, outputPath);
17698
+ return {
17699
+ outputPath,
17700
+ durationMs: Date.now() - start,
17701
+ framesEncoded: plan2.totalFrames,
17702
+ fileSize: statSync11(outputPath).size
17703
+ };
17704
+ }
17348
17705
  const format = plan2.dimensions.format;
17349
17706
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
17350
- if (!existsSync25(dirname16(outputPath))) {
17351
- mkdirSync15(dirname16(outputPath), { recursive: true });
17707
+ if (!existsSync25(dirname17(outputPath))) {
17708
+ mkdirSync16(dirname17(outputPath), { recursive: true });
17352
17709
  }
17353
- const workDir = mkdtempSync7(`${outputPath}.assemble-work-`);
17710
+ const workDir = mkdtempSync8(`${outputPath}.assemble-work-`);
17354
17711
  const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
17355
17712
  const stageMs = {};
17356
17713
  const ctx = {
@@ -17358,7 +17715,6 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17358
17715
  format,
17359
17716
  fps,
17360
17717
  fpsArg: fpsToFfmpegArg3(fps),
17361
- chunkSeconds: chunksCarryAudio(planDir) ? plan2.chunks.map((chunk) => (chunk.endFrame - chunk.startFrame) * fps.den / fps.num) : null,
17362
17718
  signal: options?.abortSignal,
17363
17719
  timeout: resolveConfig6().ffmpegProcessTimeout,
17364
17720
  log,
@@ -17371,10 +17727,10 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17371
17727
  };
17372
17728
  try {
17373
17729
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
17374
- const finalPath = join37(workDir, `output.${format}`);
17730
+ const finalPath = join38(workDir, `output.${format}`);
17375
17731
  const audio = audioPath !== null && existsSync25(audioPath) ? audioPath : null;
17376
17732
  await writeJoined(ctx, videoSources, audio, finalPath);
17377
- renameSync8(finalPath, outputPath);
17733
+ renameSync9(finalPath, outputPath);
17378
17734
  log.info("[assemble] stage timings", {
17379
17735
  ...stageMs,
17380
17736
  chunks: chunkPaths.length,
@@ -17382,7 +17738,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17382
17738
  });
17383
17739
  } finally {
17384
17740
  try {
17385
- rmSync14(workDir, { recursive: true, force: true });
17741
+ rmSync15(workDir, { recursive: true, force: true });
17386
17742
  } catch (err) {
17387
17743
  log.warn("[assemble] failed to remove work dir", {
17388
17744
  workDir,
@@ -17399,8 +17755,8 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
17399
17755
  };
17400
17756
  }
17401
17757
  function readAssemblePlan(planDir, chunkPaths) {
17402
- const planJsonPath = join37(planDir, "plan.json");
17403
- const chunksJsonPath = join37(planDir, "meta", "chunks.json");
17758
+ const planJsonPath = join38(planDir, "plan.json");
17759
+ const chunksJsonPath = join38(planDir, "meta", "chunks.json");
17404
17760
  if (!existsSync25(planJsonPath)) {
17405
17761
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
17406
17762
  }
@@ -17421,13 +17777,13 @@ function readAssemblePlan(planDir, chunkPaths) {
17421
17777
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
17422
17778
  }
17423
17779
  }
17424
- return { ...plan2, chunks };
17780
+ return plan2;
17425
17781
  }
17426
17782
  function videoInputArgs(ctx, videoSources) {
17427
- return videoSources.length === 1 ? ["-i", videoSources[0]] : concatDemuxerInput(videoSources, ctx.workDir, ctx.fpsArg, ctx.chunkSeconds);
17783
+ return videoSources.length === 1 ? ["-i", videoSources[0]] : concatDemuxerInput(videoSources, ctx.workDir, ctx.fpsArg);
17428
17784
  }
17429
17785
  async function reencodeCfr(ctx, chunkPaths, encoder) {
17430
- const cfrOutputPath = join37(ctx.workDir, `cfr.${ctx.format}`);
17786
+ const cfrOutputPath = join38(ctx.workDir, `cfr.${ctx.format}`);
17431
17787
  const cfrArgs = [
17432
17788
  ...videoInputArgs(ctx, chunkPaths),
17433
17789
  "-c:v",
@@ -17465,7 +17821,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
17465
17821
  );
17466
17822
  return;
17467
17823
  }
17468
- const paddedAudioPath = join37(ctx.workDir, "audio-padded.m4a");
17824
+ const paddedAudioPath = join38(ctx.workDir, "audio-padded.m4a");
17469
17825
  const padTrimResult = await ctx.timed(
17470
17826
  "audioMs",
17471
17827
  () => padOrTrimAudioToVideoFrameCount({
@@ -17504,15 +17860,10 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
17504
17860
  throw encoderFailureError("[assemble] audio mux failed", muxResult);
17505
17861
  }
17506
17862
  }
17507
- function concatDemuxerInput(chunkPaths, workDir, fpsArg, chunkSeconds) {
17508
- const listPath = join37(workDir, "concat-list.txt");
17509
- const head = chunkSeconds ? ["stream", "exact_stream_id 1"] : [];
17510
- const body = chunkPaths.map((path, i) => {
17511
- const line = `file '${path.replace(/'/g, "'\\''")}'`;
17512
- return chunkSeconds ? `${line}
17513
- duration ${chunkSeconds[i].toFixed(6)}` : line;
17514
- });
17515
- writeFileSync11(listPath, `${[...head, ...body].join("\n")}
17863
+ function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
17864
+ const listPath = join38(workDir, "concat-list.txt");
17865
+ const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
17866
+ writeFileSync11(listPath, `${body}
17516
17867
  `, "utf-8");
17517
17868
  return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
17518
17869
  }
@@ -17521,7 +17872,7 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
17521
17872
  videoPaths.map((path) => defaultProbeVideoFrameInfo(path, signal))
17522
17873
  );
17523
17874
  return {
17524
- frameCount: infos.reduce((sum, info) => sum + info.frameCount, 0),
17875
+ frameCount: infos.reduce((sum2, info) => sum2 + info.frameCount, 0),
17525
17876
  fpsNum: fps.num,
17526
17877
  fpsDen: fps.den
17527
17878
  };
@@ -17535,8 +17886,15 @@ async function runAssembleFfmpeg(args, step, signal, timeout) {
17535
17886
  });
17536
17887
  }
17537
17888
  }
17889
+ function chunksAreStitchedMp4(planDir, cfr) {
17890
+ if (!chunksCarryAudio(planDir)) return false;
17891
+ if (cfr) {
17892
+ throw new Error("[assemble] cfr=true cannot re-encode a plan whose mp4 chunks carry audio");
17893
+ }
17894
+ return true;
17895
+ }
17538
17896
  function chunksCarryAudio(planDir) {
17539
- const encoderJsonPath = join37(planDir, "meta", "encoder.json");
17897
+ const encoderJsonPath = join38(planDir, "meta", "encoder.json");
17540
17898
  if (!existsSync25(encoderJsonPath)) return false;
17541
17899
  return JSON.parse(readFileSync13(encoderJsonPath, "utf-8")).chunksCarryAudio === true;
17542
17900
  }
@@ -17546,7 +17904,7 @@ function readCfrEncoder(planDir, format) {
17546
17904
  `[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.`
17547
17905
  );
17548
17906
  }
17549
- const encoderJsonPath = join37(planDir, "meta", "encoder.json");
17907
+ const encoderJsonPath = join38(planDir, "meta", "encoder.json");
17550
17908
  if (!existsSync25(encoderJsonPath)) {
17551
17909
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
17552
17910
  }
@@ -17559,8 +17917,8 @@ function readCfrEncoder(planDir, format) {
17559
17917
  return encoderJson;
17560
17918
  }
17561
17919
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
17562
- if (existsSync25(outputPath)) rmSync14(outputPath, { recursive: true, force: true });
17563
- mkdirSync15(outputPath, { recursive: true });
17920
+ if (existsSync25(outputPath)) rmSync15(outputPath, { recursive: true, force: true });
17921
+ mkdirSync16(outputPath, { recursive: true });
17564
17922
  let globalIdx = 0;
17565
17923
  for (const chunkDir of chunkPaths) {
17566
17924
  if (!statSync11(chunkDir).isDirectory()) {
@@ -17573,8 +17931,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
17573
17931
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
17574
17932
  }
17575
17933
  for (const frame of frames) {
17576
- const dst = join37(outputPath, formatExportFrameName(globalIdx, "png"));
17577
- cpSync3(join37(chunkDir, frame), dst);
17934
+ const dst = join38(outputPath, formatExportFrameName(globalIdx, "png"));
17935
+ cpSync3(join38(chunkDir, frame), dst);
17578
17936
  globalIdx += 1;
17579
17937
  }
17580
17938
  }
@@ -17584,13 +17942,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
17584
17942
  );
17585
17943
  }
17586
17944
  if (audioPath !== null && existsSync25(audioPath)) {
17587
- const sidecar = join37(outputPath, MIXED_AUDIO_FILENAME4);
17945
+ const sidecar = join38(outputPath, MIXED_AUDIO_FILENAME4);
17588
17946
  cpSync3(audioPath, sidecar);
17589
17947
  }
17590
17948
  let fileSize = 0;
17591
17949
  for (const name of readdirSync9(outputPath)) {
17592
17950
  try {
17593
- fileSize += statSync11(join37(outputPath, name)).size;
17951
+ fileSize += statSync11(join38(outputPath, name)).size;
17594
17952
  } catch {
17595
17953
  }
17596
17954
  }
@@ -17604,8 +17962,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
17604
17962
 
17605
17963
  // src/services/distributed/renderChunk.ts
17606
17964
  import { randomBytes } from "node:crypto";
17607
- import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync14, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
17608
- import { extname as extname6, join as join38 } from "node:path";
17965
+ import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync14, readdirSync as readdirSync10, rmSync as rmSync16, writeFileSync as writeFileSync12 } from "node:fs";
17966
+ import { extname as extname6, join as join39 } from "node:path";
17609
17967
  import {
17610
17968
  assertSwiftShader,
17611
17969
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE3,
@@ -17811,7 +18169,7 @@ function frameNumberFromFileName(name) {
17811
18169
  function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
17812
18170
  const result = [];
17813
18171
  for (const v of videos) {
17814
- const outputDir = join38(planDir, "video-frames", v.videoId);
18172
+ const outputDir = join39(planDir, "video-frames", v.videoId);
17815
18173
  if (!existsSync26(outputDir)) {
17816
18174
  throw new Error(
17817
18175
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
@@ -17830,7 +18188,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
17830
18188
  if (!frameName) continue;
17831
18189
  const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
17832
18190
  const frameIndex = frameNumber === null ? i : frameNumber - 1;
17833
- framePaths.set(frameIndex, join38(outputDir, frameName));
18191
+ framePaths.set(frameIndex, join39(outputDir, frameName));
17834
18192
  }
17835
18193
  result.push({
17836
18194
  videoId: v.videoId,
@@ -17852,13 +18210,13 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
17852
18210
  return result;
17853
18211
  }
17854
18212
  function contiguousRuns(sortedIndexes) {
17855
- const runs = [];
18213
+ const runs2 = [];
17856
18214
  for (const index of sortedIndexes) {
17857
- const last = runs[runs.length - 1];
18215
+ const last = runs2[runs2.length - 1];
17858
18216
  if (last && last.firstFrame + last.frames === index) last.frames++;
17859
- else runs.push({ firstFrame: index, frames: 1 });
18217
+ else runs2.push({ firstFrame: index, frames: 1 });
17860
18218
  }
17861
- return runs;
18219
+ return runs2;
17862
18220
  }
17863
18221
  async function extractDeferredVideoFramesForChunk(input) {
17864
18222
  const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
@@ -17883,13 +18241,13 @@ async function extractDeferredVideoFramesForChunk(input) {
17883
18241
  }
17884
18242
  return Promise.all(
17885
18243
  deferred.map(async (video) => {
17886
- const outputDir = join38(input.outputRoot, video.videoId);
18244
+ const outputDir = join39(input.outputRoot, video.videoId);
17887
18245
  const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
17888
18246
  if (indexes.length === 0) {
17889
18247
  return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
17890
18248
  }
17891
18249
  const range = video.deferredRange;
17892
- const sourcePath = join38(input.planDir, ...range.sourcePath.split("/"));
18250
+ const sourcePath = join39(input.planDir, ...range.sourcePath.split("/"));
17893
18251
  if (!existsSync26(sourcePath)) {
17894
18252
  throw new RenderChunkValidationError(
17895
18253
  MISSING_PLAN_ARTIFACT,
@@ -17925,7 +18283,7 @@ function hashChunkOutput(outputPath, kind) {
17925
18283
  if (kind === "file") return sha256Hex(readFileSync14(outputPath));
17926
18284
  const entries = readdirSync10(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
17927
18285
  const lines = entries.map(
17928
- (name) => `${name}\0${sha256Hex(readFileSync14(join38(outputPath, name)))}`
18286
+ (name) => `${name}\0${sha256Hex(readFileSync14(join39(outputPath, name)))}`
17929
18287
  );
17930
18288
  return sha256Hex(lines.join("\0"));
17931
18289
  }
@@ -17942,9 +18300,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
17942
18300
  async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17943
18301
  const start = Date.now();
17944
18302
  const log = defaultLogger;
17945
- const planJsonPath = join38(planDir, "plan.json");
17946
- const encoderJsonPath = join38(planDir, "meta", "encoder.json");
17947
- const chunksJsonPath = join38(planDir, "meta", "chunks.json");
18303
+ const planJsonPath = join39(planDir, "plan.json");
18304
+ const encoderJsonPath = join39(planDir, "meta", "encoder.json");
18305
+ const chunksJsonPath = join39(planDir, "meta", "chunks.json");
17948
18306
  if (!existsSync26(planJsonPath)) {
17949
18307
  throw new RenderChunkValidationError(
17950
18308
  MISSING_PLAN_ARTIFACT,
@@ -17968,7 +18326,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17968
18326
  }
17969
18327
  const encoder = JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
17970
18328
  const chunks = JSON.parse(readFileSync14(chunksJsonPath, "utf-8"));
17971
- const videosJsonPath = join38(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18329
+ const videosJsonPath = join39(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
17972
18330
  let planVideos = null;
17973
18331
  if (existsSync26(videosJsonPath)) {
17974
18332
  try {
@@ -17998,7 +18356,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
17998
18356
  `[renderChunk] chunk ${chunkIndex} has non-positive frame count: ${framesInChunk}`
17999
18357
  );
18000
18358
  }
18001
- const compiledDir = join38(planDir, "compiled");
18359
+ const compiledDir = join39(planDir, "compiled");
18002
18360
  if (!existsSync26(compiledDir)) {
18003
18361
  throw new RenderChunkValidationError(
18004
18362
  MISSING_PLAN_ARTIFACT,
@@ -18055,9 +18413,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18055
18413
  forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
18056
18414
  };
18057
18415
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
18058
- mkdirSync16(workDir, { recursive: true });
18059
- const framesDir = join38(workDir, "captured-frames");
18060
- mkdirSync16(framesDir, { recursive: true });
18416
+ mkdirSync17(workDir, { recursive: true });
18417
+ const framesDir = join39(workDir, "captured-frames");
18418
+ mkdirSync17(framesDir, { recursive: true });
18061
18419
  const videoFramesDir = `${workDir}.video-frames`;
18062
18420
  const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable3(
18063
18421
  planVideos.videos,
@@ -18110,7 +18468,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18110
18468
  // `beginFrameTimeTicks` is host-independent. Only chunks ever set this.
18111
18469
  lockWarmupTicks: true,
18112
18470
  requiresWebGpu: compositionRequiresWebGpu3(
18113
- readFileSync14(join38(compiledDir, "index.html"), "utf-8")
18471
+ readFileSync14(join39(compiledDir, "index.html"), "utf-8")
18114
18472
  )
18115
18473
  };
18116
18474
  const chunkWorkerCount = calculateOptimalWorkers2(framesInChunk, void 0, cfg);
@@ -18241,8 +18599,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18241
18599
  return observedMode;
18242
18600
  },
18243
18601
  resetForScreenshotRetry: () => {
18244
- rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
18245
- mkdirSync16(framesDir, { recursive: true });
18602
+ rmSync16(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
18603
+ mkdirSync17(framesDir, { recursive: true });
18246
18604
  capturePerfs.length = 0;
18247
18605
  captureAttempts.length = 0;
18248
18606
  job.framesRendered = 0;
@@ -18273,10 +18631,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18273
18631
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
18274
18632
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
18275
18633
  if (isPngSequence) {
18276
- if (!existsSync26(outputChunkPath)) mkdirSync16(outputChunkPath, { recursive: true });
18634
+ if (!existsSync26(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
18277
18635
  } else {
18278
- const outDir = join38(outputChunkPath, "..");
18279
- if (!existsSync26(outDir)) mkdirSync16(outDir, { recursive: true });
18636
+ const outDir = join39(outputChunkPath, "..");
18637
+ if (!existsSync26(outDir)) mkdirSync17(outDir, { recursive: true });
18280
18638
  }
18281
18639
  const encodeStarted = Date.now();
18282
18640
  await runEncodeStage({
@@ -18328,7 +18686,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18328
18686
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
18329
18687
  timeoutMs: cfg.ffmpegProcessTimeout
18330
18688
  });
18331
- rmSync15(videoOnlyPath, { force: true });
18689
+ rmSync16(videoOnlyPath, { force: true });
18332
18690
  }
18333
18691
  encodeStageMs = Date.now() - encodeStarted;
18334
18692
  } finally {
@@ -18370,8 +18728,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18370
18728
  writeFileSync12(perfPath, `${JSON.stringify(perfPayload, null, 2)}
18371
18729
  `, "utf-8");
18372
18730
  try {
18373
- rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
18374
- rmSync15(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
18731
+ rmSync16(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
18732
+ rmSync16(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
18375
18733
  } catch (err) {
18376
18734
  log.warn("[renderChunk] failed to remove work dir", {
18377
18735
  workDir,
@@ -18399,18 +18757,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
18399
18757
 
18400
18758
  // src/services/distributed/planV2Execution.ts
18401
18759
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
18402
- const workRoot = mkdtempSync8(join39(tmpdir5(), "hf-plan-v2-chunk-"));
18403
- const materializedPlanDir = join39(workRoot, "plan");
18760
+ const workRoot = mkdtempSync9(join40(tmpdir5(), "hf-plan-v2-chunk-"));
18761
+ const materializedPlanDir = join40(workRoot, "plan");
18404
18762
  try {
18405
18763
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
18406
18764
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath);
18407
18765
  } finally {
18408
- rmSync16(workRoot, { recursive: true, force: true });
18766
+ rmSync17(workRoot, { recursive: true, force: true });
18409
18767
  }
18410
18768
  }
18411
18769
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
18412
- const workRoot = mkdtempSync8(join39(tmpdir5(), "hf-plan-v2-assembler-"));
18413
- const materializedPlanDir = join39(workRoot, "plan");
18770
+ const workRoot = mkdtempSync9(join40(tmpdir5(), "hf-plan-v2-assembler-"));
18771
+ const materializedPlanDir = join40(workRoot, "plan");
18414
18772
  try {
18415
18773
  const materialized = materializePlanV2Target(
18416
18774
  planV2Dir,
@@ -18425,7 +18783,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
18425
18783
  options
18426
18784
  );
18427
18785
  } finally {
18428
- rmSync16(workRoot, { recursive: true, force: true });
18786
+ rmSync17(workRoot, { recursive: true, force: true });
18429
18787
  }
18430
18788
  }
18431
18789
  export {