@hyperframes/producer 0.8.179 → 0.8.181

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.
@@ -33,8 +33,8 @@ export { buildChunkSlices, measurePlanDirBytes, plan, rejectUnsupportedDistribut
33
33
  export { createPlanV2FromExecutionPlan, createPlanV2FromV1, getPlanV2ExecutionPlanHash, isPlanV2DeferredSourceArtifact, listPlanV2ArtifactsForTarget, materializePlanV2Target, planV2, planV2WithPublisher, publishPlanV2FromExecutionPlan, publishPlanV2FromV1, readPlanV2Manifest, validatePlanV2MaterializedTarget, PLAN_V2_INTEGRITY_UNRECOVERABLE, PLAN_V2_MATERIALIZATION_MARKER, PlanV2IntegrityError, type PlanV2Artifact, type PlanV2Limitations, type PlanV2Manifest, type PlanV2MaterializeOptions, type PlanV2MaterializationResult, type PlanV2MaterializationTarget, type PlanV2RemoteSources, type PlanV2Result, type PlanV2WithPublisherOptions, } from "./services/distributed/planV2.js";
34
34
  export { LocalPlanV2ArtifactPublisher, type LocalPlanV2ArtifactPublisherOptions, type PlanV2ArtifactPublisher, type PlanV2PublishBlob, } from "./services/distributed/planV2Publisher.js";
35
35
  export { assembleV2, renderChunkV2 } from "./services/distributed/planV2Execution.js";
36
- export { applyRuntimeEnvSnapshot, readWebGlVendorInfoFromCanvas, renderChunk, type ChunkRenderer, type ChunkResult, type EffectiveChunkResult, FFMPEG_VERSION_MISMATCH, INVALID_VIDEO_METADATA, PLAN_HASH_MISMATCH, RenderChunkValidationError, } from "./services/distributed/renderChunk.js";
37
- export { assemble, chunksAreStitchedMp4, type AssembleResult, } from "./services/distributed/assemble.js";
36
+ export { applyRuntimeEnvSnapshot, readWebGlVendorInfoFromCanvas, renderChunk, type ChunkFrameRange, type ChunkRenderer, type ChunkResult, type EffectiveChunkResult, type RenderChunkOptions, CHUNK_FRAME_RANGE_INVALID, FFMPEG_VERSION_MISMATCH, INVALID_VIDEO_METADATA, PLAN_HASH_MISMATCH, RenderChunkValidationError, } from "./services/distributed/renderChunk.js";
37
+ export { assemble, chunksAreStitchedMp4, type AssembleChunkPaths, type AssembleResult, } from "./services/distributed/assemble.js";
38
38
  export { muxChunkWithAudio } from "./services/distributed/chunkAudio.js";
39
39
  export { buildStitchedHeader, readMp4Layout, type Mp4Layout, type ReadRange, } from "./services/distributed/mp4Stitch.js";
40
40
  export { InvalidConfigError, type SerializableDistributedRenderConfig, validateDistributedRenderConfig, validateVariablesPayload, } from "./services/distributed/renderConfigValidation.js";
@@ -5709,14 +5709,84 @@ import { mkdtempSync as mkdtempSync2, renameSync as renameSync2, rmSync as rmSyn
5709
5709
  import { dirname as dirname6, join as join13 } from "node:path";
5710
5710
  import {
5711
5711
  DEFAULT_CONFIG,
5712
- describeFfmpegFailure,
5712
+ describeFfmpegFailure as describeFfmpegFailure2,
5713
5713
  extractAudioMetadata as extractAudioMetadata2,
5714
5714
  getFfprobeBinary,
5715
5715
  ManagedChildProcess,
5716
- runFfmpeg as runFfmpeg4,
5716
+ runFfmpeg as runFfmpeg5,
5717
5717
  trackChildProcess
5718
5718
  } from "@hyperframes/engine";
5719
5719
  import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
5720
+
5721
+ // src/services/render/audioTruePeak.ts
5722
+ import { aacSegmentCount, describeFfmpegFailure, runFfmpeg as runFfmpeg4 } from "@hyperframes/engine";
5723
+ var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
5724
+ function checkedDbfs(value) {
5725
+ if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
5726
+ throw new Error("[audioTruePeak] FFmpeg did not report AAC true peak");
5727
+ }
5728
+ return value;
5729
+ }
5730
+ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
5731
+ let firstBlock;
5732
+ let laterBlocks = Number.NEGATIVE_INFINITY;
5733
+ let partialLine = "";
5734
+ const readLines = (chunk) => {
5735
+ const lines = (partialLine + chunk).split(/\r|\n/);
5736
+ partialLine = lines.pop() ?? "";
5737
+ for (const line of lines) {
5738
+ const ftpk = /FTPK:([^d]*)dBFS/.exec(line)?.[1];
5739
+ if (!ftpk) continue;
5740
+ const block = Math.max(...ftpk.trim().split(/\s+/).map(toDbfs));
5741
+ if (firstBlock === void 0) firstBlock = block;
5742
+ else laterBlocks = Math.max(laterBlocks, block);
5743
+ }
5744
+ };
5745
+ const result = await runFfmpeg4(
5746
+ ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
5747
+ { signal, timeout: timeoutMs, onStderr: readLines }
5748
+ );
5749
+ if (!result.success) {
5750
+ throw new Error(`[audioTruePeak] ${describeFfmpegFailure(result, timeoutMs)}`);
5751
+ }
5752
+ const summary = [...result.stderr.matchAll(/^\s*Peak:\s*([+-]?(?:[\d.]+|inf)) dBFS$/gim)];
5753
+ return {
5754
+ peak: checkedDbfs(toDbfs(summary.at(-1)?.[1] ?? "nan")),
5755
+ firstBlock: checkedDbfs(firstBlock ?? Number.NEGATIVE_INFINITY),
5756
+ laterBlocks: checkedDbfs(laterBlocks)
5757
+ };
5758
+ }
5759
+ async function measureTruePeakDbfs(audioPath, options) {
5760
+ const { timeoutMs, signal, segments } = options;
5761
+ const wholeFile = async () => (await scanTruePeak(audioPath, "", timeoutMs, signal)).peak;
5762
+ const block = 4800;
5763
+ const count = segments?.sampleRate === 48e3 ? aacSegmentCount(segments.durationSeconds) : 1;
5764
+ if (!segments || count === 1) return wholeFile();
5765
+ const cut = (k) => k * Math.floor(segments.durationSeconds * 10 / count) * block;
5766
+ const peers = new AbortController();
5767
+ const segmentSignal = signal ? AbortSignal.any([signal, peers.signal]) : peers.signal;
5768
+ const scans = await Promise.all(
5769
+ Array.from({ length: count }, (_, k) => {
5770
+ const start = k === 0 ? 0 : cut(k) - block;
5771
+ const end = k === count - 1 ? "" : `:end_sample=${cut(k + 1)}`;
5772
+ return scanTruePeak(
5773
+ audioPath,
5774
+ `atrim=start_sample=${start}${end},`,
5775
+ timeoutMs,
5776
+ segmentSignal
5777
+ );
5778
+ })
5779
+ ).catch((error) => {
5780
+ peers.abort();
5781
+ throw error;
5782
+ });
5783
+ const last = scans[count - 1];
5784
+ const counted = Math.max(scans[0].peak, ...scans.slice(1).map((scan) => scan.laterBlocks));
5785
+ if (last.peak > last.firstBlock || last.peak <= counted) return Math.max(counted, last.peak);
5786
+ return wholeFile();
5787
+ }
5788
+
5789
+ // src/services/render/audioPadTrim.ts
5720
5790
  var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
5721
5791
  var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
5722
5792
  var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
@@ -5816,7 +5886,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
5816
5886
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
5817
5887
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
5818
5888
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
5819
- const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : (audioPath, signal) => defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal));
5889
+ const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
5820
5890
  const [videoResult, audioResult] = await Promise.allSettled([
5821
5891
  probeVideo(input.videoPath),
5822
5892
  probeAudio(input.audioPath, input.signal)
@@ -5925,7 +5995,11 @@ async function constrainAacTruePeak(input) {
5925
5995
  audioPath: input.audioPath,
5926
5996
  targetDurationSeconds: input.targetDurationSeconds,
5927
5997
  signal: input.signal,
5928
- probeTruePeak: (audioPath, signal) => defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal),
5998
+ probeTruePeak: async (audioPath, signal) => {
5999
+ const { sampleRate } = await extractAudioMetadata2(audioPath, { signal });
6000
+ const segments = { durationSeconds: input.targetDurationSeconds, sampleRate };
6001
+ return measureTruePeakDbfs(audioPath, { timeoutMs, signal, segments });
6002
+ },
5929
6003
  runner: (args) => defaultRunFfmpeg(args, timeoutMs, input.signal)
5930
6004
  });
5931
6005
  }
@@ -6062,28 +6136,12 @@ async function defaultProbeAudioInfo(audioPath, signal) {
6062
6136
  audioCodec: metadata.audioCodec
6063
6137
  };
6064
6138
  }
6065
- async function defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal) {
6066
- const result = await runFfmpeg4(
6067
- ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", "ebur128=peak=true", "-f", "null", "-"],
6068
- { signal, timeout: timeoutMs }
6069
- );
6070
- if (!result.success) {
6071
- throw new Error(`[audioPadTrim] ${describeFfmpegFailure(result, timeoutMs)}`);
6072
- }
6073
- const matches = [...result.stderr.matchAll(/^\s*Peak:\s*([+-]?(?:[\d.]+|inf)) dBFS$/gim)];
6074
- const token = matches.at(-1)?.[1]?.toLowerCase();
6075
- const value = token === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
6076
- if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
6077
- throw new Error("[audioPadTrim] FFmpeg did not report AAC true peak");
6078
- }
6079
- return value;
6080
- }
6081
6139
  async function defaultRunFfmpeg(args, timeoutMs, signal) {
6082
- const result = await runFfmpeg4(args, { signal, timeout: timeoutMs });
6140
+ const result = await runFfmpeg5(args, { signal, timeout: timeoutMs });
6083
6141
  if (result.success) return { success: true };
6084
6142
  return {
6085
6143
  success: false,
6086
- error: `[audioPadTrim] ${describeFfmpegFailure(result, timeoutMs)}`,
6144
+ error: `[audioPadTrim] ${describeFfmpegFailure2(result, timeoutMs)}`,
6087
6145
  failureReason: result.failureReason
6088
6146
  };
6089
6147
  }
@@ -6773,6 +6831,7 @@ var FFMPEG_VERSION_MISMATCH = "FFMPEG_VERSION_MISMATCH";
6773
6831
  var PLAN_HASH_MISMATCH = "PLAN_HASH_MISMATCH";
6774
6832
  var MISSING_PLAN_ARTIFACT = "MISSING_PLAN_ARTIFACT";
6775
6833
  var CHUNK_INDEX_OUT_OF_RANGE = "CHUNK_INDEX_OUT_OF_RANGE";
6834
+ var CHUNK_FRAME_RANGE_INVALID = "CHUNK_FRAME_RANGE_INVALID";
6776
6835
  var MISSING_RUNTIME_ENV_SNAPSHOT = "MISSING_RUNTIME_ENV_SNAPSHOT";
6777
6836
  var RenderChunkValidationError = class extends Error {
6778
6837
  code;
@@ -7024,7 +7083,8 @@ var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
7024
7083
  accepts: Object.freeze([PLAN_PROTOCOL_V1, PLAN_PROTOCOL_V2]),
7025
7084
  acceptsLegacyV1WithoutDescriptor: true
7026
7085
  })
7027
- })
7086
+ }),
7087
+ chunkFrameRange: true
7028
7088
  });
7029
7089
  function getDistributedRenderCapabilities() {
7030
7090
  return DISTRIBUTED_RENDER_CAPABILITIES;
@@ -7373,7 +7433,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
7373
7433
  import { closeSync as closeSync4, existsSync as existsSync14, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync9 } from "node:fs";
7374
7434
  import { writeFileSync as writeFileSync5, writeSync } from "node:fs";
7375
7435
  import { join as join20 } from "node:path";
7376
- import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure2, runFfmpeg as runFfmpeg5 } from "@hyperframes/engine";
7436
+ import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
7377
7437
 
7378
7438
  // src/services/distributed/mp4Stitch.ts
7379
7439
  import {
@@ -7739,6 +7799,18 @@ function audioCutForChunk(grid, chunks, index, fps) {
7739
7799
  const durationSeconds = end === grid.packetCount ? null : cutSeconds(end) - (seekSeconds ?? 0);
7740
7800
  return { seekSeconds, durationSeconds, firstPacket: first, packetCount: end - first };
7741
7801
  }
7802
+ function chunksWithSubRange(chunks, index, range) {
7803
+ const slice = chunks[index];
7804
+ const parts = [
7805
+ { startFrame: slice.startFrame, endFrame: range.startFrame },
7806
+ range,
7807
+ { startFrame: range.endFrame, endFrame: slice.endFrame }
7808
+ ].filter((part) => part.endFrame > part.startFrame);
7809
+ const split = [...chunks.slice(0, index), ...parts, ...chunks.slice(index + 1)].map(
7810
+ (chunk, i) => ({ index: i, startFrame: chunk.startFrame, endFrame: chunk.endFrame })
7811
+ );
7812
+ return { chunks: split, index: range.startFrame > slice.startFrame ? index + 1 : index };
7813
+ }
7742
7814
  async function probeAacPacketGrid(audioPath) {
7743
7815
  const probe = await runFfprobeJson([
7744
7816
  "-v",
@@ -7851,10 +7923,10 @@ async function muxChunkWithAudio(input) {
7851
7923
  const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
7852
7924
  appendRenderProvenanceArgs(args, input.outputPath);
7853
7925
  args.push("-y", input.outputPath);
7854
- const result = await runFfmpeg5(args, { signal: input.signal, timeout: input.timeoutMs });
7926
+ const result = await runFfmpeg6(args, { signal: input.signal, timeout: input.timeoutMs });
7855
7927
  if (!result.success) {
7856
7928
  throw new Error(
7857
- `[chunkAudio] chunk mux failed: ${describeFfmpegFailure2(result, input.timeoutMs)}`
7929
+ `[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
7858
7930
  );
7859
7931
  }
7860
7932
  const slice = input.chunks[input.chunkIndex];
@@ -9684,12 +9756,12 @@ import {
9684
9756
  import { dirname as dirname12, join as join26 } from "node:path";
9685
9757
  import {
9686
9758
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
9687
- describeFfmpegFailure as describeFfmpegFailure3,
9759
+ describeFfmpegFailure as describeFfmpegFailure4,
9688
9760
  getEncoderPreset as getEncoderPreset5,
9689
9761
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
9690
9762
  muxVideoWithAudio as muxVideoWithAudio2,
9691
9763
  resolveConfig as resolveConfig4,
9692
- runFfmpeg as runFfmpeg6
9764
+ runFfmpeg as runFfmpeg7
9693
9765
  } from "@hyperframes/engine";
9694
9766
  import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
9695
9767
  function cfrRateControlArgs(locked) {
@@ -9699,11 +9771,12 @@ function cfrRateControlArgs(locked) {
9699
9771
  }
9700
9772
  return ["-preset", preset.preset, "-crf", String(locked.crf ?? preset.quality)];
9701
9773
  }
9702
- async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
9774
+ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
9703
9775
  const start = Date.now();
9704
9776
  const log = options?.logger ?? defaultLogger;
9705
9777
  const cfr = options?.cfr === true;
9706
- const plan2 = readAssemblePlan(planDir, chunkPaths);
9778
+ const plan2 = readAssemblePlan(planDir, chunkOutputs);
9779
+ const chunkPaths = chunkOutputs.flat();
9707
9780
  if (plan2.dimensions.format === "png-sequence") {
9708
9781
  return mergePngFrameDirs(chunkPaths, outputPath, plan2.totalFrames, audioPath, start);
9709
9782
  }
@@ -9786,7 +9859,12 @@ function readAssemblePlan(planDir, chunkPaths) {
9786
9859
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
9787
9860
  );
9788
9861
  }
9789
- for (const path of chunkPaths) {
9862
+ for (const [index, entry] of chunkPaths.entries()) {
9863
+ if (typeof entry !== "string" && entry.length === 0) {
9864
+ throw new Error(`[assemble] chunk ${index} has an empty list of parts`);
9865
+ }
9866
+ }
9867
+ for (const path of chunkPaths.flat()) {
9790
9868
  if (!existsSync18(path)) {
9791
9869
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
9792
9870
  }
@@ -9892,10 +9970,10 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
9892
9970
  };
9893
9971
  }
9894
9972
  async function runAssembleFfmpeg(args, step, signal, timeout) {
9895
- const result = await runFfmpeg6(args, { signal, timeout });
9973
+ const result = await runFfmpeg7(args, { signal, timeout });
9896
9974
  if (!result.success) {
9897
9975
  throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
9898
- error: describeFfmpegFailure3(result, timeout),
9976
+ error: describeFfmpegFailure4(result, timeout),
9899
9977
  failureReason: result.failureReason
9900
9978
  });
9901
9979
  }
@@ -10005,6 +10083,17 @@ function validatePlanVideosForChunk(value) {
10005
10083
  );
10006
10084
  }
10007
10085
  }
10086
+ function resolveChunkFrameRange(slice, chunkIndex, frameRange) {
10087
+ if (frameRange === void 0) return { startFrame: slice.startFrame, endFrame: slice.endFrame };
10088
+ const { startFrame, endFrame } = frameRange;
10089
+ if (!Number.isSafeInteger(startFrame) || !Number.isSafeInteger(endFrame) || startFrame < slice.startFrame || endFrame > slice.endFrame || endFrame <= startFrame) {
10090
+ throw new RenderChunkValidationError(
10091
+ CHUNK_FRAME_RANGE_INVALID,
10092
+ `[renderChunk] frameRange ${JSON.stringify(frameRange)} must be a non-empty integer range inside chunk ${chunkIndex}'s slice [${slice.startFrame}, ${slice.endFrame})`
10093
+ );
10094
+ }
10095
+ return { startFrame, endFrame };
10096
+ }
10008
10097
  var distributedCaptureSessionDependencies = {
10009
10098
  createCaptureSession: createCaptureSession9,
10010
10099
  assertSwiftShader,
@@ -10075,7 +10164,7 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
10075
10164
  if (lockedEncoder.encoder !== "libvpx-vp9-software") return void 0;
10076
10165
  return lockedEncoder.vp9CpuUsed ?? LEGACY_DISTRIBUTED_VP9_CPU_USED;
10077
10166
  }
10078
- async function renderChunk(planDir, chunkIndex, outputChunkPath) {
10167
+ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10079
10168
  const start = Date.now();
10080
10169
  const log = defaultLogger;
10081
10170
  const planJsonPath = join27(planDir, "plan.json");
@@ -10129,13 +10218,15 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
10129
10218
  if (slice === void 0) {
10130
10219
  throw new RenderChunkValidationError(CHUNK_INDEX_OUT_OF_RANGE, "[renderChunk] missing slice");
10131
10220
  }
10132
- const framesInChunk = slice.endFrame - slice.startFrame;
10133
- if (framesInChunk <= 0) {
10221
+ const sliceFrames = slice.endFrame - slice.startFrame;
10222
+ if (sliceFrames <= 0) {
10134
10223
  throw new RenderChunkValidationError(
10135
10224
  CHUNK_INDEX_OUT_OF_RANGE,
10136
- `[renderChunk] chunk ${chunkIndex} has non-positive frame count: ${framesInChunk}`
10225
+ `[renderChunk] chunk ${chunkIndex} has non-positive frame count: ${sliceFrames}`
10137
10226
  );
10138
10227
  }
10228
+ const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
10229
+ const framesInChunk = range.endFrame - range.startFrame;
10139
10230
  const compiledDir = join27(planDir, "compiled");
10140
10231
  if (!existsSync19(compiledDir)) {
10141
10232
  throw new RenderChunkValidationError(
@@ -10200,10 +10291,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
10200
10291
  const deferredVideoFrames = planVideos ? await extractDeferredVideoFramesForChunk({
10201
10292
  planDir,
10202
10293
  planVideos,
10203
- frames: Array.from(
10204
- { length: slice.endFrame - slice.startFrame },
10205
- (_, i) => slice.startFrame + i
10206
- ),
10294
+ frames: Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i),
10207
10295
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
10208
10296
  outputRoot: videoFramesDir,
10209
10297
  cfg,
@@ -10359,7 +10447,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
10359
10447
  abortSignal: void 0,
10360
10448
  assertNotAborted: () => {
10361
10449
  },
10362
- frameRange: { startFrame: slice.startFrame, endFrame: slice.endFrame }
10450
+ frameRange: range
10363
10451
  });
10364
10452
  const observedModes = new Set(capturePerfs.map((perf) => perf.captureMode));
10365
10453
  if (observedModes.size !== 1 || ![...observedModes].every(isCaptureMode)) {
@@ -10453,12 +10541,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
10453
10541
  });
10454
10542
  if (encoder.chunksCarryAudio) {
10455
10543
  const audioScratchPath = `${outputChunkPath}.audio.m4a`;
10544
+ const audioChunks = options?.frameRange ? chunksWithSubRange(chunks, chunkIndex, range) : { chunks, index: chunkIndex };
10456
10545
  await muxChunkWithAudio({
10457
10546
  videoPath: videoOnlyPath,
10458
10547
  audioPath: chunkAudioSource(planDir, chunkIndex, audioScratchPath),
10459
10548
  outputPath: outputChunkPath,
10460
- chunks,
10461
- chunkIndex,
10549
+ chunks: audioChunks.chunks,
10550
+ chunkIndex: audioChunks.index,
10462
10551
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
10463
10552
  timeoutMs: cfg.ffmpegProcessTimeout
10464
10553
  });
@@ -10487,8 +10576,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
10487
10576
  const perfPayload = {
10488
10577
  planHash: plan2.planHash,
10489
10578
  chunkIndex,
10490
- startFrame: slice.startFrame,
10491
- endFrame: slice.endFrame,
10579
+ startFrame: range.startFrame,
10580
+ endFrame: range.endFrame,
10492
10581
  framesEncoded,
10493
10582
  durationMs,
10494
10583
  planHashMs,
@@ -10517,6 +10606,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
10517
10606
  outputPath: outputChunkPath,
10518
10607
  outputKind,
10519
10608
  framesEncoded,
10609
+ frameRange: range,
10520
10610
  sha256,
10521
10611
  durationMs,
10522
10612
  planHashMs,
@@ -10533,12 +10623,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
10533
10623
  }
10534
10624
 
10535
10625
  // src/services/distributed/planV2Execution.ts
10536
- async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
10626
+ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
10537
10627
  const workRoot = mkdtempSync7(join28(tmpdir2(), "hf-plan-v2-chunk-"));
10538
10628
  const materializedPlanDir = join28(workRoot, "plan");
10539
10629
  try {
10540
10630
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
10541
- return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath);
10631
+ return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
10542
10632
  } finally {
10543
10633
  rmSync13(workRoot, { recursive: true, force: true });
10544
10634
  }
@@ -10852,6 +10942,7 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
10852
10942
  }
10853
10943
  export {
10854
10944
  CHUNK_EXCEEDS_WORKER_TIMEOUT,
10945
+ CHUNK_FRAME_RANGE_INVALID,
10855
10946
  CURRENT_PLAN_PROTOCOL,
10856
10947
  ChunkExceedsWorkerTimeoutError,
10857
10948
  DEFAULT_CHUNK_SIZE,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "0.1.0",
3
3
  "buildId": "dev",
4
- "sha256": "5da92c0e03e3d47df672f23707742908357e94fb7b39ed0f518d4c37400f433d",
4
+ "sha256": "2ae4b2eb24c5dea85f83bd9af6c14abc153ad59fc3f338d1ca9142d9df0c662e",
5
5
  "artifacts": {
6
6
  "iife": "hyperframe.runtime.iife.js",
7
7
  "esm": "hyperframe.runtime.mjs"