hyperframes 0.6.115 → 0.6.116

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/cli.js CHANGED
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.6.115" : "0.0.0-dev";
53
+ VERSION = true ? "0.6.116" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -32382,6 +32382,25 @@ var init_systemMemory = __esm({
32382
32382
  }
32383
32383
  });
32384
32384
 
32385
+ // ../engine/src/services/vp9Options.ts
32386
+ function normalizeVp9CpuUsed(value) {
32387
+ if (value === void 0 || !Number.isFinite(value)) return DEFAULT_VP9_CPU_USED;
32388
+ const integer = Math.trunc(value);
32389
+ return Math.max(MIN_VP9_CPU_USED, Math.min(MAX_VP9_CPU_USED, integer));
32390
+ }
32391
+ function appendVp9CpuUsedArg(args, value) {
32392
+ args.push("-cpu-used", String(normalizeVp9CpuUsed(value)));
32393
+ }
32394
+ var DEFAULT_VP9_CPU_USED, MIN_VP9_CPU_USED, MAX_VP9_CPU_USED;
32395
+ var init_vp9Options = __esm({
32396
+ "../engine/src/services/vp9Options.ts"() {
32397
+ "use strict";
32398
+ DEFAULT_VP9_CPU_USED = 4;
32399
+ MIN_VP9_CPU_USED = -8;
32400
+ MAX_VP9_CPU_USED = 8;
32401
+ }
32402
+ });
32403
+
32385
32404
  // ../engine/src/config.ts
32386
32405
  function memoryAdaptiveCacheLimit() {
32387
32406
  const total = getSystemTotalMb();
@@ -32408,6 +32427,11 @@ function resolveConfig(overrides) {
32408
32427
  if (raw === void 0) return fallback;
32409
32428
  return raw === "true";
32410
32429
  };
32430
+ const envVp9CpuUsed = () => {
32431
+ const raw = env("PRODUCER_VP9_CPU_USED");
32432
+ if (raw === void 0 || raw === "") return DEFAULT_CONFIG2.vp9CpuUsed;
32433
+ return normalizeVp9CpuUsed(Number(raw));
32434
+ };
32411
32435
  const envBrowserGpuMode = () => {
32412
32436
  const raw = env("PRODUCER_BROWSER_GPU_MODE");
32413
32437
  if (raw === "hardware" || raw === "software" || raw === "auto") return raw;
@@ -32448,6 +32472,7 @@ function resolveConfig(overrides) {
32448
32472
  "HF_PAGE_SIDE_COMPOSITING",
32449
32473
  DEFAULT_CONFIG2.enablePageSideCompositing
32450
32474
  ),
32475
+ vp9CpuUsed: envVp9CpuUsed(),
32451
32476
  enableChunkedEncode: envBool(
32452
32477
  "PRODUCER_ENABLE_CHUNKED_ENCODE",
32453
32478
  DEFAULT_CONFIG2.enableChunkedEncode
@@ -32505,17 +32530,22 @@ function resolveConfig(overrides) {
32505
32530
  extractCacheDir: env("HYPERFRAMES_EXTRACT_CACHE_DIR")
32506
32531
  };
32507
32532
  const cleanEnv = Object.fromEntries(Object.entries(fromEnv).filter(([, v2]) => v2 !== void 0));
32508
- return {
32533
+ const merged = {
32509
32534
  ...DEFAULT_CONFIG2,
32510
32535
  ...cleanEnv,
32511
32536
  ...overrides
32512
32537
  };
32538
+ return {
32539
+ ...merged,
32540
+ vp9CpuUsed: normalizeVp9CpuUsed(merged.vp9CpuUsed)
32541
+ };
32513
32542
  }
32514
32543
  var DEFAULT_CONFIG2;
32515
32544
  var init_config2 = __esm({
32516
32545
  "../engine/src/config.ts"() {
32517
32546
  "use strict";
32518
32547
  init_systemMemory();
32548
+ init_vp9Options();
32519
32549
  DEFAULT_CONFIG2 = {
32520
32550
  fps: 30,
32521
32551
  quality: "standard",
@@ -32536,6 +32566,7 @@ var init_config2 = __esm({
32536
32566
  // DEFAULT_CONFIG (used directly by tests and worker-sizing fallbacks).
32537
32567
  lowMemoryMode: false,
32538
32568
  enablePageSideCompositing: true,
32569
+ vp9CpuUsed: DEFAULT_VP9_CPU_USED,
32539
32570
  enableChunkedEncode: false,
32540
32571
  chunkSizeFrames: 360,
32541
32572
  enableStreamingEncode: true,
@@ -34837,15 +34868,322 @@ var init_runFfmpeg = __esm({
34837
34868
  }
34838
34869
  });
34839
34870
 
34840
- // ../engine/src/services/chunkEncoder.ts
34871
+ // ../engine/src/utils/ffprobe.ts
34841
34872
  import { spawn as spawn3 } from "child_process";
34873
+ import { readFileSync as readFileSync3 } from "fs";
34874
+ import { extname } from "path";
34875
+ function runFfprobe(args) {
34876
+ return new Promise((resolve58, reject) => {
34877
+ const command2 = getFfprobeBinary();
34878
+ const proc = spawn3(command2, args);
34879
+ let stdout2 = "";
34880
+ let stderr = "";
34881
+ proc.stdout.on("data", (data2) => {
34882
+ stdout2 += data2.toString();
34883
+ });
34884
+ proc.stderr.on("data", (data2) => {
34885
+ stderr += data2.toString();
34886
+ });
34887
+ proc.on("close", (code) => {
34888
+ if (code !== 0) {
34889
+ reject(new Error(`[FFmpeg] ffprobe exited with code ${code}: ${stderr}`));
34890
+ } else {
34891
+ resolve58(stdout2);
34892
+ }
34893
+ });
34894
+ proc.on("error", (err) => {
34895
+ if (err.code === "ENOENT") {
34896
+ const configured = process.env[FFPROBE_PATH_ENV]?.trim();
34897
+ reject(
34898
+ new Error(
34899
+ configured ? `[FFmpeg] ffprobe not found at ${FFPROBE_PATH_ENV}="${configured}". Please install FFmpeg.` : "[FFmpeg] ffprobe not found. Please install FFmpeg."
34900
+ )
34901
+ );
34902
+ } else {
34903
+ reject(err);
34904
+ }
34905
+ });
34906
+ });
34907
+ }
34908
+ function parseProbeJson(stdout2) {
34909
+ try {
34910
+ return JSON.parse(stdout2);
34911
+ } catch (e3) {
34912
+ throw new Error(
34913
+ `[FFmpeg] Failed to parse ffprobe output: ${e3 instanceof Error ? e3.message : e3}`
34914
+ );
34915
+ }
34916
+ }
34917
+ function crc32(buf) {
34918
+ let crc = 4294967295;
34919
+ for (let i2 = 0; i2 < buf.length; i2++) {
34920
+ crc ^= buf[i2] ?? 0;
34921
+ for (let bit = 0; bit < 8; bit++) {
34922
+ const mask = -(crc & 1);
34923
+ crc = crc >>> 1 ^ 3988292384 & mask;
34924
+ }
34925
+ }
34926
+ return (crc ^ 4294967295) >>> 0;
34927
+ }
34928
+ function extractPngMetadataFromBuffer(buf) {
34929
+ if (buf.length < 8 || buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71 || buf[4] !== 13 || buf[5] !== 10 || buf[6] !== 26 || buf[7] !== 10) {
34930
+ return null;
34931
+ }
34932
+ let width = 0;
34933
+ let height = 0;
34934
+ let seenIdat = false;
34935
+ let pos = 8;
34936
+ while (pos + 12 <= buf.length) {
34937
+ const chunkLen = buf.readUInt32BE(pos);
34938
+ const chunkType = buf.toString("ascii", pos + 4, pos + 8);
34939
+ if (pos + 12 + chunkLen > buf.length) return null;
34940
+ const chunkData = buf.subarray(pos + 8, pos + 8 + chunkLen);
34941
+ const chunkCrc = buf.readUInt32BE(pos + 8 + chunkLen);
34942
+ const chunkBytes = Buffer.concat([Buffer.from(chunkType, "ascii"), chunkData]);
34943
+ if (crc32(chunkBytes) !== chunkCrc) return null;
34944
+ if (chunkType === "IHDR" && chunkLen >= 8) {
34945
+ width = buf.readUInt32BE(pos + 8);
34946
+ height = buf.readUInt32BE(pos + 12);
34947
+ }
34948
+ if (chunkType === "IDAT") {
34949
+ seenIdat = true;
34950
+ }
34951
+ if (chunkType === "cICP" && chunkLen === 4 && !seenIdat) {
34952
+ const primariesCode = chunkData[0] ?? 0;
34953
+ const transferCode = chunkData[1] ?? 0;
34954
+ const matrixCode = chunkData[2] ?? 0;
34955
+ return {
34956
+ width,
34957
+ height,
34958
+ colorSpace: {
34959
+ colorPrimaries: primariesCode === 9 ? "bt2020" : primariesCode === 1 ? "bt709" : `unknown-${primariesCode}`,
34960
+ colorTransfer: transferCode === 16 ? "smpte2084" : transferCode === 18 ? "arib-std-b67" : transferCode === 1 ? "bt709" : `unknown-${transferCode}`,
34961
+ colorSpace: matrixCode === 9 ? "bt2020nc" : matrixCode === 0 ? "gbr" : `unknown-${matrixCode}`
34962
+ }
34963
+ };
34964
+ }
34965
+ if (chunkType === "IEND") break;
34966
+ pos += 12 + chunkLen;
34967
+ }
34968
+ return width > 0 && height > 0 ? { width, height, colorSpace: null } : null;
34969
+ }
34970
+ function extractStillImageMetadata(filePath) {
34971
+ if (extname(filePath).toLowerCase() !== ".png") return null;
34972
+ try {
34973
+ return extractPngMetadataFromBuffer(readFileSync3(filePath));
34974
+ } catch {
34975
+ return null;
34976
+ }
34977
+ }
34978
+ function readTagCI(tags, name) {
34979
+ if (!tags) return "";
34980
+ const target = name.toLowerCase();
34981
+ for (const [key2, value] of Object.entries(tags)) {
34982
+ if (key2.toLowerCase() === target && typeof value === "string") return value;
34983
+ }
34984
+ return "";
34985
+ }
34986
+ function parseFrameRate(frameRateStr) {
34987
+ if (!frameRateStr) return 0;
34988
+ const parts = frameRateStr.split("/");
34989
+ if (parts.length === 2) {
34990
+ const num = parseFloat(parts[0] ?? "");
34991
+ const den = parseFloat(parts[1] ?? "");
34992
+ if (den !== 0) return Math.round(num / den * 100) / 100;
34993
+ }
34994
+ return parseFloat(frameRateStr) || 0;
34995
+ }
34996
+ async function extractMediaMetadata(filePath) {
34997
+ const cached2 = videoMetadataCache.get(filePath);
34998
+ if (cached2) return cached2;
34999
+ const probePromise = (async () => {
35000
+ const stillImageMeta = extractStillImageMetadata(filePath);
35001
+ let output = null;
35002
+ try {
35003
+ const stdout2 = await runFfprobe([
35004
+ "-v",
35005
+ "quiet",
35006
+ "-print_format",
35007
+ "json",
35008
+ "-show_format",
35009
+ "-show_streams",
35010
+ filePath
35011
+ ]);
35012
+ output = parseProbeJson(stdout2);
35013
+ } catch (error) {
35014
+ if (!stillImageMeta) throw error;
35015
+ }
35016
+ const videoStream = output?.streams.find((s2) => s2.codec_type === "video");
35017
+ if (!videoStream) {
35018
+ if (stillImageMeta) {
35019
+ return {
35020
+ durationSeconds: 0,
35021
+ videoStreamDurationSeconds: 0,
35022
+ width: stillImageMeta.width,
35023
+ height: stillImageMeta.height,
35024
+ fps: 0,
35025
+ videoCodec: "png",
35026
+ hasAudio: false,
35027
+ isVFR: false,
35028
+ hasAlpha: false,
35029
+ colorSpace: stillImageMeta.colorSpace
35030
+ };
35031
+ }
35032
+ throw new Error("[FFmpeg] No video stream found");
35033
+ }
35034
+ const rFps = parseFrameRate(videoStream.r_frame_rate);
35035
+ const avgFps = parseFrameRate(videoStream.avg_frame_rate);
35036
+ const fps = avgFps || rFps;
35037
+ const isVFR = rFps > 0 && avgFps > 0 && Math.abs(rFps - avgFps) / Math.max(rFps, avgFps) > 0.1;
35038
+ const colorTransfer = videoStream.color_transfer || "";
35039
+ const colorPrimaries = videoStream.color_primaries || "";
35040
+ const colorSpaceVal = videoStream.color_space || "";
35041
+ const ffprobeColorSpace = colorTransfer || colorPrimaries || colorSpaceVal ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null;
35042
+ const colorSpace = ffprobeColorSpace ?? stillImageMeta?.colorSpace ?? null;
35043
+ const pixelFormat = videoStream.pix_fmt || "";
35044
+ const alphaMode = readTagCI(videoStream.tags, "alpha_mode");
35045
+ const hasAlpha = /(^|[^a-z])yuva|rgba|argb|bgra|gbrap|gray[a-z0-9]*a/i.test(pixelFormat) || alphaMode === "1";
35046
+ const containerDuration = output?.format.duration ? parseFloat(output.format.duration) : 0;
35047
+ const streamDuration = videoStream.duration ? parseFloat(videoStream.duration) : 0;
35048
+ return {
35049
+ durationSeconds: containerDuration,
35050
+ videoStreamDurationSeconds: streamDuration > 0 ? streamDuration : containerDuration,
35051
+ width: videoStream.width || stillImageMeta?.width || 0,
35052
+ height: videoStream.height || stillImageMeta?.height || 0,
35053
+ fps,
35054
+ videoCodec: videoStream.codec_name || "unknown",
35055
+ hasAudio: output?.streams.some((s2) => s2.codec_type === "audio") ?? false,
35056
+ isVFR,
35057
+ hasAlpha,
35058
+ colorSpace
35059
+ };
35060
+ })();
35061
+ videoMetadataCache.set(filePath, probePromise);
35062
+ probePromise.catch(() => {
35063
+ if (videoMetadataCache.get(filePath) === probePromise) {
35064
+ videoMetadataCache.delete(filePath);
35065
+ }
35066
+ });
35067
+ return probePromise;
35068
+ }
35069
+ async function extractAudioMetadata(filePath) {
35070
+ const cached2 = audioMetadataCache.get(filePath);
35071
+ if (cached2) return cached2;
35072
+ const probePromise = (async () => {
35073
+ const stdout2 = await runFfprobe([
35074
+ "-v",
35075
+ "quiet",
35076
+ "-print_format",
35077
+ "json",
35078
+ "-show_format",
35079
+ "-show_streams",
35080
+ filePath
35081
+ ]);
35082
+ const output = parseProbeJson(stdout2);
35083
+ const audioStream = output.streams.find((s2) => s2.codec_type === "audio");
35084
+ if (!audioStream) throw new Error("[FFmpeg] No audio stream found");
35085
+ const durationSeconds = output.format.duration ? parseFloat(output.format.duration) : 0;
35086
+ return {
35087
+ durationSeconds,
35088
+ sampleRate: audioStream.sample_rate ? parseInt(audioStream.sample_rate) : 44100,
35089
+ channels: audioStream.channels || 2,
35090
+ audioCodec: audioStream.codec_name || "unknown",
35091
+ bitrate: output.format.bit_rate ? parseInt(output.format.bit_rate) : void 0
35092
+ };
35093
+ })();
35094
+ audioMetadataCache.set(filePath, probePromise);
35095
+ probePromise.catch(() => {
35096
+ if (audioMetadataCache.get(filePath) === probePromise) {
35097
+ audioMetadataCache.delete(filePath);
35098
+ }
35099
+ });
35100
+ return probePromise;
35101
+ }
35102
+ async function analyzeKeyframeIntervals(filePath) {
35103
+ const cached2 = keyframeCache.get(filePath);
35104
+ if (cached2) return cached2;
35105
+ const promise = analyzeKeyframeIntervalsUncached(filePath);
35106
+ keyframeCache.set(filePath, promise);
35107
+ promise.catch(() => {
35108
+ if (keyframeCache.get(filePath) === promise) {
35109
+ keyframeCache.delete(filePath);
35110
+ }
35111
+ });
35112
+ return promise;
35113
+ }
35114
+ async function analyzeKeyframeIntervalsUncached(filePath) {
35115
+ const stdout2 = await runFfprobe([
35116
+ "-v",
35117
+ "quiet",
35118
+ "-select_streams",
35119
+ "v:0",
35120
+ "-skip_frame",
35121
+ "nokey",
35122
+ "-show_entries",
35123
+ "frame=pts_time",
35124
+ "-of",
35125
+ "csv=p=0",
35126
+ filePath
35127
+ ]);
35128
+ const timestamps = stdout2.split("\n").map((line) => parseFloat(line.trim())).filter((t2) => Number.isFinite(t2));
35129
+ if (timestamps.length < 2) {
35130
+ return {
35131
+ avgIntervalSeconds: 0,
35132
+ maxIntervalSeconds: 0,
35133
+ keyframeCount: timestamps.length,
35134
+ isProblematic: false
35135
+ };
35136
+ }
35137
+ let maxInterval = 0;
35138
+ let totalInterval = 0;
35139
+ for (let i2 = 1; i2 < timestamps.length; i2++) {
35140
+ const interval = (timestamps[i2] ?? 0) - (timestamps[i2 - 1] ?? 0);
35141
+ totalInterval += interval;
35142
+ if (interval > maxInterval) maxInterval = interval;
35143
+ }
35144
+ const avgInterval = totalInterval / (timestamps.length - 1);
35145
+ return {
35146
+ avgIntervalSeconds: Math.round(avgInterval * 100) / 100,
35147
+ maxIntervalSeconds: Math.round(maxInterval * 100) / 100,
35148
+ keyframeCount: timestamps.length,
35149
+ isProblematic: maxInterval > 2
35150
+ };
35151
+ }
35152
+ var videoMetadataCache, audioMetadataCache, extractVideoMetadata, keyframeCache;
35153
+ var init_ffprobe = __esm({
35154
+ "../engine/src/utils/ffprobe.ts"() {
35155
+ "use strict";
35156
+ init_ffmpegBinaries();
35157
+ videoMetadataCache = /* @__PURE__ */ new Map();
35158
+ audioMetadataCache = /* @__PURE__ */ new Map();
35159
+ extractVideoMetadata = extractMediaMetadata;
35160
+ keyframeCache = /* @__PURE__ */ new Map();
35161
+ }
35162
+ });
35163
+
35164
+ // ../engine/src/services/chunkEncoder.ts
35165
+ import { spawn as spawn4 } from "child_process";
34842
35166
  import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync3 } from "fs";
34843
- import { join as join6, dirname as dirname4 } from "path";
35167
+ import { join as join6, dirname as dirname4, extname as extname2 } from "path";
34844
35168
  function appendEncodeTimeoutMessage(error, timedOut, timeoutMs) {
34845
35169
  if (!timedOut) return error;
34846
35170
  return `${error}
34847
35171
  FFmpeg killed after exceeding ffmpegEncodeTimeout (${timeoutMs} ms)`;
34848
35172
  }
35173
+ function isAacSidecar(audioPath) {
35174
+ return extname2(audioPath).toLowerCase() === ".aac";
35175
+ }
35176
+ async function shouldCopyAacSidecar(audioPath, options) {
35177
+ if (options?.audioCodec === "aac" || isAacSidecar(audioPath)) return true;
35178
+ const audioExtension = extname2(audioPath).toLowerCase();
35179
+ if (KNOWN_NON_AAC_AUDIO_EXTENSIONS.has(audioExtension)) return false;
35180
+ try {
35181
+ const metadata = await extractAudioMetadata(audioPath);
35182
+ return metadata.audioCodec === "aac";
35183
+ } catch {
35184
+ return false;
35185
+ }
35186
+ }
34849
35187
  function getEncoderPreset(quality, format = "mp4", hdr) {
34850
35188
  const base2 = ENCODER_PRESETS[quality];
34851
35189
  if (format === "webm") {
@@ -34883,6 +35221,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
34883
35221
  quality = 23,
34884
35222
  bitrate,
34885
35223
  pixelFormat = "yuv420p",
35224
+ vp9CpuUsed,
34886
35225
  useGpu = false
34887
35226
  } = options;
34888
35227
  if (options.hdr && codec === "h264") {
@@ -34985,6 +35324,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
34985
35324
  args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
34986
35325
  args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
34987
35326
  args.push("-row-mt", "1");
35327
+ appendVp9CpuUsedArg(args, vp9CpuUsed);
34988
35328
  const lockGopVp9 = options.lockGopForChunkConcat === true;
34989
35329
  if (lockGopVp9) {
34990
35330
  if (typeof options.gopSize !== "number" || !Number.isFinite(options.gopSize) || options.gopSize <= 0) {
@@ -34993,16 +35333,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
34993
35333
  );
34994
35334
  }
34995
35335
  const gop = Math.floor(options.gopSize);
34996
- args.push(
34997
- "-g",
34998
- String(gop),
34999
- "-keyint_min",
35000
- String(gop),
35001
- "-auto-alt-ref",
35002
- "0",
35003
- "-cpu-used",
35004
- "2"
35005
- );
35336
+ args.push("-g", String(gop), "-keyint_min", String(gop), "-auto-alt-ref", "0");
35006
35337
  }
35007
35338
  if (pixelFormat === "yuva420p") {
35008
35339
  if (!lockGopVp9) {
@@ -35081,7 +35412,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
35081
35412
  const inputArgs = ["-framerate", fpsToFfmpegArg(options.fps), "-i", inputPath];
35082
35413
  const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
35083
35414
  return new Promise((resolve58) => {
35084
- const ffmpeg = spawn3(getFfmpegBinary(), args);
35415
+ const ffmpeg = spawn4(getFfmpegBinary(), args);
35085
35416
  trackChildProcess(ffmpeg);
35086
35417
  let stderr = "";
35087
35418
  const onAbort = () => {
@@ -35198,7 +35529,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
35198
35529
  if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
35199
35530
  const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
35200
35531
  const chunkResult = await new Promise((resolve58) => {
35201
- const ffmpeg = spawn3(getFfmpegBinary(), args);
35532
+ const ffmpeg = spawn4(getFfmpegBinary(), args);
35202
35533
  trackChildProcess(ffmpeg);
35203
35534
  let stderr = "";
35204
35535
  const encodeTimeout = config?.ffmpegEncodeTimeout ?? DEFAULT_CONFIG2.ffmpegEncodeTimeout;
@@ -35264,7 +35595,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
35264
35595
  outputPath
35265
35596
  ];
35266
35597
  const concatResult = await new Promise((resolve58) => {
35267
- const ffmpeg = spawn3(getFfmpegBinary(), concatArgs);
35598
+ const ffmpeg = spawn4(getFfmpegBinary(), concatArgs);
35268
35599
  trackChildProcess(ffmpeg);
35269
35600
  let stderr = "";
35270
35601
  const encodeTimeout = config?.ffmpegEncodeTimeout ?? DEFAULT_CONFIG2.ffmpegEncodeTimeout;
@@ -35326,13 +35657,22 @@ async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, confi
35326
35657
  if (!existsSync6(outputDir)) mkdirSync3(outputDir, { recursive: true });
35327
35658
  const isWebm = outputPath.endsWith(".webm");
35328
35659
  const isMov = outputPath.endsWith(".mov");
35660
+ const shouldCopyAudio = isWebm ? false : await shouldCopyAacSidecar(audioPath, config);
35329
35661
  const args = ["-i", videoPath, "-i", audioPath, "-c:v", "copy"];
35330
35662
  if (isWebm) {
35331
35663
  args.push("-c:a", "libopus", "-b:a", "128k");
35332
35664
  } else if (isMov) {
35333
- args.push("-c:a", "aac", "-b:a", "192k");
35665
+ if (shouldCopyAudio) {
35666
+ args.push("-c:a", "copy");
35667
+ } else {
35668
+ args.push("-c:a", "aac", "-b:a", "192k");
35669
+ }
35334
35670
  } else {
35335
- args.push("-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart");
35671
+ if (shouldCopyAudio) {
35672
+ args.push("-c:a", "copy", "-movflags", "+faststart");
35673
+ } else {
35674
+ args.push("-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart");
35675
+ }
35336
35676
  }
35337
35677
  args.push("-avoid_negative_ts", "make_zero");
35338
35678
  if (fps !== void 0) {
@@ -35383,7 +35723,7 @@ async function applyFaststart(inputPath, outputPath, signal, config, fps) {
35383
35723
  error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0
35384
35724
  };
35385
35725
  }
35386
- var ENCODER_PRESETS;
35726
+ var ENCODER_PRESETS, KNOWN_NON_AAC_AUDIO_EXTENSIONS;
35387
35727
  var init_chunkEncoder = __esm({
35388
35728
  "../engine/src/services/chunkEncoder.ts"() {
35389
35729
  "use strict";
@@ -35393,18 +35733,29 @@ var init_chunkEncoder = __esm({
35393
35733
  init_hdr();
35394
35734
  init_runFfmpeg();
35395
35735
  init_ffmpegBinaries();
35736
+ init_ffprobe();
35396
35737
  init_src();
35738
+ init_vp9Options();
35397
35739
  init_gpuEncoder();
35398
35740
  ENCODER_PRESETS = {
35399
35741
  draft: { preset: "ultrafast", quality: 28, codec: "h264" },
35400
35742
  standard: { preset: "medium", quality: 18, codec: "h264" },
35401
35743
  high: { preset: "slow", quality: 15, codec: "h264" }
35402
35744
  };
35745
+ KNOWN_NON_AAC_AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
35746
+ ".flac",
35747
+ ".mp3",
35748
+ ".oga",
35749
+ ".ogg",
35750
+ ".opus",
35751
+ ".wav",
35752
+ ".webm"
35753
+ ]);
35403
35754
  }
35404
35755
  });
35405
35756
 
35406
35757
  // ../engine/src/services/streamingEncoder.ts
35407
- import { spawn as spawn4 } from "child_process";
35758
+ import { spawn as spawn5 } from "child_process";
35408
35759
  import { once } from "events";
35409
35760
  import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync2 } from "fs";
35410
35761
  import { dirname as dirname5 } from "path";
@@ -35453,6 +35804,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
35453
35804
  quality = 23,
35454
35805
  bitrate,
35455
35806
  pixelFormat = "yuv420p",
35807
+ vp9CpuUsed,
35456
35808
  useGpu = false,
35457
35809
  imageFormat = "jpeg"
35458
35810
  } = options;
@@ -35559,6 +35911,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
35559
35911
  args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
35560
35912
  args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
35561
35913
  args.push("-row-mt", "1");
35914
+ appendVp9CpuUsedArg(args, vp9CpuUsed);
35562
35915
  if (pixelFormat === "yuva420p") {
35563
35916
  args.push("-auto-alt-ref", "0");
35564
35917
  args.push("-metadata:s:v:0", "alpha_mode=1");
@@ -35619,7 +35972,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
35619
35972
  }
35620
35973
  const args = buildStreamingArgs(options, outputPath, gpuEncoder);
35621
35974
  const startTime = Date.now();
35622
- const ffmpeg = spawn4(getFfmpegBinary(), args, {
35975
+ const ffmpeg = spawn5(getFfmpegBinary(), args, {
35623
35976
  stdio: ["pipe", "pipe", "pipe"]
35624
35977
  });
35625
35978
  trackChildProcess(ffmpeg);
@@ -35758,306 +36111,14 @@ var init_streamingEncoder = __esm({
35758
36111
  init_hdr();
35759
36112
  init_config2();
35760
36113
  init_src();
35761
- }
35762
- });
35763
-
35764
- // ../engine/src/utils/ffprobe.ts
35765
- import { spawn as spawn5 } from "child_process";
35766
- import { readFileSync as readFileSync3 } from "fs";
35767
- import { extname } from "path";
35768
- function runFfprobe(args) {
35769
- return new Promise((resolve58, reject) => {
35770
- const command2 = getFfprobeBinary();
35771
- const proc = spawn5(command2, args);
35772
- let stdout2 = "";
35773
- let stderr = "";
35774
- proc.stdout.on("data", (data2) => {
35775
- stdout2 += data2.toString();
35776
- });
35777
- proc.stderr.on("data", (data2) => {
35778
- stderr += data2.toString();
35779
- });
35780
- proc.on("close", (code) => {
35781
- if (code !== 0) {
35782
- reject(new Error(`[FFmpeg] ffprobe exited with code ${code}: ${stderr}`));
35783
- } else {
35784
- resolve58(stdout2);
35785
- }
35786
- });
35787
- proc.on("error", (err) => {
35788
- if (err.code === "ENOENT") {
35789
- const configured = process.env[FFPROBE_PATH_ENV]?.trim();
35790
- reject(
35791
- new Error(
35792
- configured ? `[FFmpeg] ffprobe not found at ${FFPROBE_PATH_ENV}="${configured}". Please install FFmpeg.` : "[FFmpeg] ffprobe not found. Please install FFmpeg."
35793
- )
35794
- );
35795
- } else {
35796
- reject(err);
35797
- }
35798
- });
35799
- });
35800
- }
35801
- function parseProbeJson(stdout2) {
35802
- try {
35803
- return JSON.parse(stdout2);
35804
- } catch (e3) {
35805
- throw new Error(
35806
- `[FFmpeg] Failed to parse ffprobe output: ${e3 instanceof Error ? e3.message : e3}`
35807
- );
35808
- }
35809
- }
35810
- function crc32(buf) {
35811
- let crc = 4294967295;
35812
- for (let i2 = 0; i2 < buf.length; i2++) {
35813
- crc ^= buf[i2] ?? 0;
35814
- for (let bit = 0; bit < 8; bit++) {
35815
- const mask = -(crc & 1);
35816
- crc = crc >>> 1 ^ 3988292384 & mask;
35817
- }
35818
- }
35819
- return (crc ^ 4294967295) >>> 0;
35820
- }
35821
- function extractPngMetadataFromBuffer(buf) {
35822
- if (buf.length < 8 || buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71 || buf[4] !== 13 || buf[5] !== 10 || buf[6] !== 26 || buf[7] !== 10) {
35823
- return null;
35824
- }
35825
- let width = 0;
35826
- let height = 0;
35827
- let seenIdat = false;
35828
- let pos = 8;
35829
- while (pos + 12 <= buf.length) {
35830
- const chunkLen = buf.readUInt32BE(pos);
35831
- const chunkType = buf.toString("ascii", pos + 4, pos + 8);
35832
- if (pos + 12 + chunkLen > buf.length) return null;
35833
- const chunkData = buf.subarray(pos + 8, pos + 8 + chunkLen);
35834
- const chunkCrc = buf.readUInt32BE(pos + 8 + chunkLen);
35835
- const chunkBytes = Buffer.concat([Buffer.from(chunkType, "ascii"), chunkData]);
35836
- if (crc32(chunkBytes) !== chunkCrc) return null;
35837
- if (chunkType === "IHDR" && chunkLen >= 8) {
35838
- width = buf.readUInt32BE(pos + 8);
35839
- height = buf.readUInt32BE(pos + 12);
35840
- }
35841
- if (chunkType === "IDAT") {
35842
- seenIdat = true;
35843
- }
35844
- if (chunkType === "cICP" && chunkLen === 4 && !seenIdat) {
35845
- const primariesCode = chunkData[0] ?? 0;
35846
- const transferCode = chunkData[1] ?? 0;
35847
- const matrixCode = chunkData[2] ?? 0;
35848
- return {
35849
- width,
35850
- height,
35851
- colorSpace: {
35852
- colorPrimaries: primariesCode === 9 ? "bt2020" : primariesCode === 1 ? "bt709" : `unknown-${primariesCode}`,
35853
- colorTransfer: transferCode === 16 ? "smpte2084" : transferCode === 18 ? "arib-std-b67" : transferCode === 1 ? "bt709" : `unknown-${transferCode}`,
35854
- colorSpace: matrixCode === 9 ? "bt2020nc" : matrixCode === 0 ? "gbr" : `unknown-${matrixCode}`
35855
- }
35856
- };
35857
- }
35858
- if (chunkType === "IEND") break;
35859
- pos += 12 + chunkLen;
35860
- }
35861
- return width > 0 && height > 0 ? { width, height, colorSpace: null } : null;
35862
- }
35863
- function extractStillImageMetadata(filePath) {
35864
- if (extname(filePath).toLowerCase() !== ".png") return null;
35865
- try {
35866
- return extractPngMetadataFromBuffer(readFileSync3(filePath));
35867
- } catch {
35868
- return null;
35869
- }
35870
- }
35871
- function readTagCI(tags, name) {
35872
- if (!tags) return "";
35873
- const target = name.toLowerCase();
35874
- for (const [key2, value] of Object.entries(tags)) {
35875
- if (key2.toLowerCase() === target && typeof value === "string") return value;
35876
- }
35877
- return "";
35878
- }
35879
- function parseFrameRate(frameRateStr) {
35880
- if (!frameRateStr) return 0;
35881
- const parts = frameRateStr.split("/");
35882
- if (parts.length === 2) {
35883
- const num = parseFloat(parts[0] ?? "");
35884
- const den = parseFloat(parts[1] ?? "");
35885
- if (den !== 0) return Math.round(num / den * 100) / 100;
35886
- }
35887
- return parseFloat(frameRateStr) || 0;
35888
- }
35889
- async function extractMediaMetadata(filePath) {
35890
- const cached2 = videoMetadataCache.get(filePath);
35891
- if (cached2) return cached2;
35892
- const probePromise = (async () => {
35893
- const stillImageMeta = extractStillImageMetadata(filePath);
35894
- let output = null;
35895
- try {
35896
- const stdout2 = await runFfprobe([
35897
- "-v",
35898
- "quiet",
35899
- "-print_format",
35900
- "json",
35901
- "-show_format",
35902
- "-show_streams",
35903
- filePath
35904
- ]);
35905
- output = parseProbeJson(stdout2);
35906
- } catch (error) {
35907
- if (!stillImageMeta) throw error;
35908
- }
35909
- const videoStream = output?.streams.find((s2) => s2.codec_type === "video");
35910
- if (!videoStream) {
35911
- if (stillImageMeta) {
35912
- return {
35913
- durationSeconds: 0,
35914
- videoStreamDurationSeconds: 0,
35915
- width: stillImageMeta.width,
35916
- height: stillImageMeta.height,
35917
- fps: 0,
35918
- videoCodec: "png",
35919
- hasAudio: false,
35920
- isVFR: false,
35921
- hasAlpha: false,
35922
- colorSpace: stillImageMeta.colorSpace
35923
- };
35924
- }
35925
- throw new Error("[FFmpeg] No video stream found");
35926
- }
35927
- const rFps = parseFrameRate(videoStream.r_frame_rate);
35928
- const avgFps = parseFrameRate(videoStream.avg_frame_rate);
35929
- const fps = avgFps || rFps;
35930
- const isVFR = rFps > 0 && avgFps > 0 && Math.abs(rFps - avgFps) / Math.max(rFps, avgFps) > 0.1;
35931
- const colorTransfer = videoStream.color_transfer || "";
35932
- const colorPrimaries = videoStream.color_primaries || "";
35933
- const colorSpaceVal = videoStream.color_space || "";
35934
- const ffprobeColorSpace = colorTransfer || colorPrimaries || colorSpaceVal ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null;
35935
- const colorSpace = ffprobeColorSpace ?? stillImageMeta?.colorSpace ?? null;
35936
- const pixelFormat = videoStream.pix_fmt || "";
35937
- const alphaMode = readTagCI(videoStream.tags, "alpha_mode");
35938
- const hasAlpha = /(^|[^a-z])yuva|rgba|argb|bgra|gbrap|gray[a-z0-9]*a/i.test(pixelFormat) || alphaMode === "1";
35939
- const containerDuration = output?.format.duration ? parseFloat(output.format.duration) : 0;
35940
- const streamDuration = videoStream.duration ? parseFloat(videoStream.duration) : 0;
35941
- return {
35942
- durationSeconds: containerDuration,
35943
- videoStreamDurationSeconds: streamDuration > 0 ? streamDuration : containerDuration,
35944
- width: videoStream.width || stillImageMeta?.width || 0,
35945
- height: videoStream.height || stillImageMeta?.height || 0,
35946
- fps,
35947
- videoCodec: videoStream.codec_name || "unknown",
35948
- hasAudio: output?.streams.some((s2) => s2.codec_type === "audio") ?? false,
35949
- isVFR,
35950
- hasAlpha,
35951
- colorSpace
35952
- };
35953
- })();
35954
- videoMetadataCache.set(filePath, probePromise);
35955
- probePromise.catch(() => {
35956
- if (videoMetadataCache.get(filePath) === probePromise) {
35957
- videoMetadataCache.delete(filePath);
35958
- }
35959
- });
35960
- return probePromise;
35961
- }
35962
- async function extractAudioMetadata(filePath) {
35963
- const cached2 = audioMetadataCache.get(filePath);
35964
- if (cached2) return cached2;
35965
- const probePromise = (async () => {
35966
- const stdout2 = await runFfprobe([
35967
- "-v",
35968
- "quiet",
35969
- "-print_format",
35970
- "json",
35971
- "-show_format",
35972
- "-show_streams",
35973
- filePath
35974
- ]);
35975
- const output = parseProbeJson(stdout2);
35976
- const audioStream = output.streams.find((s2) => s2.codec_type === "audio");
35977
- if (!audioStream) throw new Error("[FFmpeg] No audio stream found");
35978
- const durationSeconds = output.format.duration ? parseFloat(output.format.duration) : 0;
35979
- return {
35980
- durationSeconds,
35981
- sampleRate: audioStream.sample_rate ? parseInt(audioStream.sample_rate) : 44100,
35982
- channels: audioStream.channels || 2,
35983
- audioCodec: audioStream.codec_name || "unknown",
35984
- bitrate: output.format.bit_rate ? parseInt(output.format.bit_rate) : void 0
35985
- };
35986
- })();
35987
- audioMetadataCache.set(filePath, probePromise);
35988
- probePromise.catch(() => {
35989
- if (audioMetadataCache.get(filePath) === probePromise) {
35990
- audioMetadataCache.delete(filePath);
35991
- }
35992
- });
35993
- return probePromise;
35994
- }
35995
- async function analyzeKeyframeIntervals(filePath) {
35996
- const cached2 = keyframeCache.get(filePath);
35997
- if (cached2) return cached2;
35998
- const promise = analyzeKeyframeIntervalsUncached(filePath);
35999
- keyframeCache.set(filePath, promise);
36000
- promise.catch(() => {
36001
- if (keyframeCache.get(filePath) === promise) {
36002
- keyframeCache.delete(filePath);
36003
- }
36004
- });
36005
- return promise;
36006
- }
36007
- async function analyzeKeyframeIntervalsUncached(filePath) {
36008
- const stdout2 = await runFfprobe([
36009
- "-v",
36010
- "quiet",
36011
- "-select_streams",
36012
- "v:0",
36013
- "-skip_frame",
36014
- "nokey",
36015
- "-show_entries",
36016
- "frame=pts_time",
36017
- "-of",
36018
- "csv=p=0",
36019
- filePath
36020
- ]);
36021
- const timestamps = stdout2.split("\n").map((line) => parseFloat(line.trim())).filter((t2) => Number.isFinite(t2));
36022
- if (timestamps.length < 2) {
36023
- return {
36024
- avgIntervalSeconds: 0,
36025
- maxIntervalSeconds: 0,
36026
- keyframeCount: timestamps.length,
36027
- isProblematic: false
36028
- };
36029
- }
36030
- let maxInterval = 0;
36031
- let totalInterval = 0;
36032
- for (let i2 = 1; i2 < timestamps.length; i2++) {
36033
- const interval = (timestamps[i2] ?? 0) - (timestamps[i2 - 1] ?? 0);
36034
- totalInterval += interval;
36035
- if (interval > maxInterval) maxInterval = interval;
36036
- }
36037
- const avgInterval = totalInterval / (timestamps.length - 1);
36038
- return {
36039
- avgIntervalSeconds: Math.round(avgInterval * 100) / 100,
36040
- maxIntervalSeconds: Math.round(maxInterval * 100) / 100,
36041
- keyframeCount: timestamps.length,
36042
- isProblematic: maxInterval > 2
36043
- };
36044
- }
36045
- var videoMetadataCache, audioMetadataCache, extractVideoMetadata, keyframeCache;
36046
- var init_ffprobe = __esm({
36047
- "../engine/src/utils/ffprobe.ts"() {
36048
- "use strict";
36049
- init_ffmpegBinaries();
36050
- videoMetadataCache = /* @__PURE__ */ new Map();
36051
- audioMetadataCache = /* @__PURE__ */ new Map();
36052
- extractVideoMetadata = extractMediaMetadata;
36053
- keyframeCache = /* @__PURE__ */ new Map();
36114
+ init_vp9Options();
36054
36115
  }
36055
36116
  });
36056
36117
 
36057
36118
  // ../engine/src/utils/urlDownloader.ts
36058
36119
  import { createWriteStream, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
36059
36120
  import { createHash } from "crypto";
36060
- import { join as join7, extname as extname2 } from "path";
36121
+ import { join as join7, extname as extname3 } from "path";
36061
36122
  import { Readable } from "stream";
36062
36123
  import { finished } from "stream/promises";
36063
36124
  function isBlockedHost(hostname) {
@@ -36092,7 +36153,7 @@ function assertPublicHttpsUrl(url) {
36092
36153
  function getFilenameFromUrl(url) {
36093
36154
  const hash2 = createHash("md5").update(url).digest("hex").slice(0, 12);
36094
36155
  const urlObj = new URL(url);
36095
- const ext = extname2(urlObj.pathname) || ".mp4";
36156
+ const ext = extname3(urlObj.pathname) || ".mp4";
36096
36157
  return `download_${hash2}${ext}`;
36097
36158
  }
36098
36159
  async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
@@ -40023,7 +40084,7 @@ var init_parallelCoordinator = __esm({
40023
40084
  import { Hono } from "hono";
40024
40085
  import { serve } from "@hono/node-server";
40025
40086
  import { readFileSync as readFileSync6, existsSync as existsSync14, statSync as statSync4 } from "fs";
40026
- import { join as join13, extname as extname3 } from "path";
40087
+ import { join as join13, extname as extname4 } from "path";
40027
40088
  function createFileServer(options) {
40028
40089
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
40029
40090
  const headScripts = options.headScripts ?? [];
@@ -40041,7 +40102,7 @@ function createFileServer(options) {
40041
40102
  if (!existsSync14(filePath) || !statSync4(filePath).isFile()) {
40042
40103
  return c3.text("Not found", 404);
40043
40104
  }
40044
- const ext = extname3(filePath).toLowerCase();
40105
+ const ext = extname4(filePath).toLowerCase();
40045
40106
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
40046
40107
  if (ext === ".html") {
40047
40108
  const rawHtml = readFileSync6(filePath, "utf-8");
@@ -41569,6 +41630,7 @@ __export(src_exports2, {
41569
41630
  BROWSER_GPU_NOT_SOFTWARE: () => BROWSER_GPU_NOT_SOFTWARE,
41570
41631
  DEFAULT_CONFIG: () => DEFAULT_CONFIG2,
41571
41632
  DEFAULT_HDR10_MASTERING: () => DEFAULT_HDR10_MASTERING,
41633
+ DEFAULT_VP9_CPU_USED: () => DEFAULT_VP9_CPU_USED,
41572
41634
  DOM_LAYER_MASK_STYLE_ID: () => DOM_LAYER_MASK_STYLE_ID,
41573
41635
  ENABLE_BROWSER_POOL: () => ENABLE_BROWSER_POOL,
41574
41636
  ENCODER_PRESETS: () => ENCODER_PRESETS,
@@ -41576,7 +41638,9 @@ __export(src_exports2, {
41576
41638
  FFPROBE_PATH_ENV: () => FFPROBE_PATH_ENV,
41577
41639
  FrameLookupTable: () => FrameLookupTable,
41578
41640
  LOW_MEMORY_TOTAL_MB_THRESHOLD: () => LOW_MEMORY_TOTAL_MB_THRESHOLD,
41641
+ MAX_VP9_CPU_USED: () => MAX_VP9_CPU_USED,
41579
41642
  MEDIA_VISUAL_STYLE_PROPERTIES: () => MEDIA_VISUAL_STYLE_PROPERTIES,
41643
+ MIN_VP9_CPU_USED: () => MIN_VP9_CPU_USED,
41580
41644
  SwiftShaderAssertionError: () => SwiftShaderAssertionError,
41581
41645
  TRANSITIONS: () => TRANSITIONS,
41582
41646
  VIDEO_FRAME_FORMATS: () => VIDEO_FRAME_FORMATS,
@@ -41654,6 +41718,7 @@ __export(src_exports2, {
41654
41718
  mergeWorkerFrames: () => mergeWorkerFrames,
41655
41719
  muxVideoWithAudio: () => muxVideoWithAudio,
41656
41720
  normalizeObjectFit: () => normalizeObjectFit,
41721
+ normalizeVp9CpuUsed: () => normalizeVp9CpuUsed,
41657
41722
  pageScreenshotCapture: () => pageScreenshotCapture,
41658
41723
  parseAudioElements: () => parseAudioElements,
41659
41724
  parseImageElements: () => parseImageElements,
@@ -41686,6 +41751,7 @@ var init_src2 = __esm({
41686
41751
  "../engine/src/index.ts"() {
41687
41752
  "use strict";
41688
41753
  init_config2();
41754
+ init_vp9Options();
41689
41755
  init_systemMemory();
41690
41756
  init_browserManager();
41691
41757
  init_frameCapture();
@@ -45223,9 +45289,9 @@ __export(normalize_exports, {
45223
45289
  stripBeforeOnset: () => stripBeforeOnset
45224
45290
  });
45225
45291
  import { readFileSync as readFileSync12, readdirSync as readdirSync7, writeFileSync as writeFileSync8 } from "fs";
45226
- import { extname as extname4, join as join18 } from "path";
45292
+ import { extname as extname5, join as join18 } from "path";
45227
45293
  function detectFormat(filePath) {
45228
- const ext = extname4(filePath).toLowerCase();
45294
+ const ext = extname5(filePath).toLowerCase();
45229
45295
  if (ext === ".srt") return "srt";
45230
45296
  if (ext === ".vtt") return "vtt";
45231
45297
  if (ext === ".json") return detectJsonFormat(JSON.parse(readFileSync12(filePath, "utf-8")));
@@ -45377,7 +45443,7 @@ function round3(n2) {
45377
45443
  return Math.round(n2 * 1e3) / 1e3;
45378
45444
  }
45379
45445
  function loadTranscript(filePath) {
45380
- const ext = extname4(filePath).toLowerCase();
45446
+ const ext = extname5(filePath).toLowerCase();
45381
45447
  const content = readFileSync12(filePath, "utf-8");
45382
45448
  if (ext === ".srt") {
45383
45449
  const words2 = parseSrt(content).map((w3, i2) => ({ ...w3, id: w3.id ?? `w${i2}` }));
@@ -45502,7 +45568,7 @@ __export(transcribe_exports, {
45502
45568
  });
45503
45569
  import { execFileSync as execFileSync3 } from "child_process";
45504
45570
  import { existsSync as existsSync21, readFileSync as readFileSync14, mkdirSync as mkdirSync12, unlinkSync as unlinkSync2 } from "fs";
45505
- import { join as join20, extname as extname5 } from "path";
45571
+ import { join as join20, extname as extname6 } from "path";
45506
45572
  import { tmpdir } from "os";
45507
45573
  import { randomUUID as randomUUID2 } from "crypto";
45508
45574
  function detectLanguage(whisperPath, modelPath2, wavPath) {
@@ -45576,10 +45642,10 @@ function detectSpeechOnset(wavPath) {
45576
45642
  return null;
45577
45643
  }
45578
45644
  function isAudioFile(filePath) {
45579
- return AUDIO_EXTENSIONS.has(extname5(filePath).toLowerCase());
45645
+ return AUDIO_EXTENSIONS.has(extname6(filePath).toLowerCase());
45580
45646
  }
45581
45647
  function isVideoFile(filePath) {
45582
- return VIDEO_EXTENSIONS.has(extname5(filePath).toLowerCase());
45648
+ return VIDEO_EXTENSIONS.has(extname6(filePath).toLowerCase());
45583
45649
  }
45584
45650
  function tempWavPath() {
45585
45651
  return join20(tmpdir(), `hyperframes-audio-${process.pid}-${randomUUID2()}.wav`);
@@ -45616,7 +45682,7 @@ function isWav16kMono(filePath) {
45616
45682
  }
45617
45683
  }
45618
45684
  function prepareAudio(audioPath) {
45619
- if (extname5(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
45685
+ if (extname6(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
45620
45686
  return audioPath;
45621
45687
  }
45622
45688
  const ffmpegPath = findFFmpeg();
@@ -45640,7 +45706,7 @@ async function transcribe(inputPath, outputDir, options) {
45640
45706
  onProgress: options?.onProgress
45641
45707
  });
45642
45708
  let wavPath;
45643
- const ext = extname5(inputPath).toLowerCase();
45709
+ const ext = extname6(inputPath).toLowerCase();
45644
45710
  if (isAudioFile(inputPath)) {
45645
45711
  options?.onProgress?.("Preparing audio...");
45646
45712
  wavPath = prepareAudio(inputPath);
@@ -45867,7 +45933,7 @@ var init_lint = __esm({
45867
45933
 
45868
45934
  // src/utils/lintProject.ts
45869
45935
  import { existsSync as existsSync22, readFileSync as readFileSync15, readdirSync as readdirSync8 } from "fs";
45870
- import { dirname as dirname9, extname as extname6, isAbsolute as isAbsolute6, join as join21, posix as posix3, relative as relative4, resolve as resolve12 } from "path";
45936
+ import { dirname as dirname9, extname as extname7, isAbsolute as isAbsolute6, join as join21, posix as posix3, relative as relative4, resolve as resolve12 } from "path";
45871
45937
  function readHtmlAttr(tag, name) {
45872
45938
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45873
45939
  const match = tag.match(new RegExp(`\\b${escaped}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "i"));
@@ -46040,7 +46106,7 @@ function lintProjectAudioFiles(projectDir, htmlSources) {
46040
46106
  let audioFiles;
46041
46107
  try {
46042
46108
  audioFiles = readdirSync8(projectDir).filter(
46043
- (f3) => AUDIO_EXTENSIONS2.has(extname6(f3).toLowerCase())
46109
+ (f3) => AUDIO_EXTENSIONS2.has(extname7(f3).toLowerCase())
46044
46110
  );
46045
46111
  } catch {
46046
46112
  return findings;
@@ -86064,13 +86130,13 @@ var init_subComposition = __esm({
86064
86130
  // ../core/src/studio-api/helpers/projectSignature.ts
86065
86131
  import { createHash as createHash4 } from "crypto";
86066
86132
  import { lstatSync, readFileSync as readFileSync21, readdirSync as readdirSync12 } from "fs";
86067
- import { extname as extname7, isAbsolute as isAbsolute7, relative as relative6, resolve as resolve18 } from "path";
86133
+ import { extname as extname8, isAbsolute as isAbsolute7, relative as relative6, resolve as resolve18 } from "path";
86068
86134
  function isPathWithin(parentDir, childPath) {
86069
86135
  const childRelativePath = relative6(parentDir, childPath);
86070
86136
  return childRelativePath === "" || !childRelativePath.startsWith("..") && !isAbsolute7(childRelativePath);
86071
86137
  }
86072
86138
  function isTextContentEligible(file, size) {
86073
- return SIGNATURE_TEXT_EXTENSIONS.has(extname7(file).toLowerCase()) && size <= MAX_SIGNATURE_TEXT_BYTES;
86139
+ return SIGNATURE_TEXT_EXTENSIONS.has(extname8(file).toLowerCase()) && size <= MAX_SIGNATURE_TEXT_BYTES;
86074
86140
  }
86075
86141
  function collectProjectSignatureFiles(projectDir, dir, files) {
86076
86142
  let entries2;
@@ -92412,6 +92478,8 @@ function buildAnimatedGifTranscodeArgs(input2) {
92412
92478
  "0",
92413
92479
  "-deadline",
92414
92480
  "good",
92481
+ "-cpu-used",
92482
+ String(DEFAULT_VP9_CPU_USED),
92415
92483
  "-crf",
92416
92484
  "18",
92417
92485
  "-b:v",
@@ -92704,7 +92772,7 @@ var init_hf_early_stub_inline = __esm({
92704
92772
  import { Hono as Hono3 } from "hono";
92705
92773
  import { serve as serve2 } from "@hono/node-server";
92706
92774
  import { readFileSync as readFileSync31, existsSync as existsSync38, realpathSync as realpathSync3, statSync as statSync12 } from "fs";
92707
- import { join as join38, extname as extname8, resolve as resolve22, sep as sep4 } from "path";
92775
+ import { join as join38, extname as extname9, resolve as resolve22, sep as sep4 } from "path";
92708
92776
  function isPathInside2(child, parent, options = {}) {
92709
92777
  const { resolveSymlinks = false, pathModule } = options;
92710
92778
  const resolveFn = pathModule?.resolve ?? resolve22;
@@ -92904,7 +92972,7 @@ function createFileServer2(options) {
92904
92972
  }
92905
92973
  return c3.text("Not found", 404);
92906
92974
  }
92907
- const ext = extname8(filePath).toLowerCase();
92975
+ const ext = extname9(filePath).toLowerCase();
92908
92976
  const contentType = MIME_TYPES3[ext] || "application/octet-stream";
92909
92977
  if (ext === ".html") {
92910
92978
  const rawHtml = readFileSync31(filePath, "utf-8");
@@ -98465,6 +98533,7 @@ async function runEncodeStage(input2) {
98465
98533
  quality: effectiveQuality,
98466
98534
  bitrate: effectiveBitrate,
98467
98535
  pixelFormat: preset.pixelFormat,
98536
+ vp9CpuUsed: engineCfg.vp9CpuUsed,
98468
98537
  useGpu: job.config.useGpu,
98469
98538
  hdr: preset.hdr,
98470
98539
  // Distributed chunk renders pass these so the encoder writes closed-GOP
@@ -98525,7 +98594,7 @@ async function runAssembleStage(input2) {
98525
98594
  audioOutputPath,
98526
98595
  outputPath,
98527
98596
  abortSignal,
98528
- void 0,
98597
+ { audioCodec: "aac" },
98529
98598
  job.config.fps
98530
98599
  );
98531
98600
  assertNotAborted();
@@ -99390,6 +99459,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
99390
99459
  quality: effectiveQuality,
99391
99460
  bitrate: effectiveBitrate,
99392
99461
  pixelFormat: preset.pixelFormat,
99462
+ vp9CpuUsed: cfg.vp9CpuUsed,
99393
99463
  useGpu: job.config.useGpu,
99394
99464
  imageFormat: captureOptions.format || "jpeg",
99395
99465
  hdr: preset.hdr
@@ -100848,6 +100918,7 @@ async function readFontSnapshotSha() {
100848
100918
  function buildLockedRenderConfig(input2) {
100849
100919
  const { config, forceScreenshot, deviceScaleFactor, ffmpegVersion } = input2;
100850
100920
  const { encoder, pixelFormat, preset } = resolveEncoderTriple(config);
100921
+ const locksVp9CpuUsed = encoder === "libvpx-vp9-software" ? { vp9CpuUsed: normalizeVp9CpuUsed(input2.engineConfig.vp9CpuUsed) } : {};
100851
100922
  return {
100852
100923
  captureMode: forceScreenshot ? "screenshot" : "beginframe",
100853
100924
  forceScreenshot,
@@ -100864,6 +100935,7 @@ function buildLockedRenderConfig(input2) {
100864
100935
  preset,
100865
100936
  crf: config.crf,
100866
100937
  bitrate: config.bitrate,
100938
+ ...locksVp9CpuUsed,
100867
100939
  // GOP === chunkSize so every chunk's first frame is an IDR keyframe and
100868
100940
  // ffmpeg concat-copy round-trips losslessly.
100869
100941
  gopSize: input2.effectiveChunkSize,
@@ -101091,6 +101163,7 @@ async function plan(projectDir, config, planDir) {
101091
101163
  forceScreenshot,
101092
101164
  deviceScaleFactor,
101093
101165
  ffmpegVersion,
101166
+ engineConfig: cfg,
101094
101167
  effectiveChunkSize,
101095
101168
  chunkCount,
101096
101169
  runtimeEnv
@@ -101213,7 +101286,7 @@ var init_plan = __esm({
101213
101286
  // ../producer/src/services/distributed/renderChunk.ts
101214
101287
  import { randomBytes as randomBytes2 } from "crypto";
101215
101288
  import { existsSync as existsSync51, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync20, rmSync as rmSync14, writeFileSync as writeFileSync23 } from "fs";
101216
- import { extname as extname9, join as join61 } from "path";
101289
+ import { extname as extname10, join as join61 } from "path";
101217
101290
  function rebuildExtractedFramesFromPlanDir(planDir, videos) {
101218
101291
  const result = [];
101219
101292
  for (const v2 of videos) {
@@ -101223,7 +101296,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos) {
101223
101296
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v2.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
101224
101297
  );
101225
101298
  }
101226
- const ext = (extname9(v2.framePattern) || ".jpg").toLowerCase();
101299
+ const ext = (extname10(v2.framePattern) || ".jpg").toLowerCase();
101227
101300
  const frames = readdirSync20(outputDir).filter((name) => name.toLowerCase().endsWith(ext)).sort();
101228
101301
  const framePaths = /* @__PURE__ */ new Map();
101229
101302
  for (let i2 = 0; i2 < frames.length; i2++) {
@@ -101264,6 +101337,10 @@ function resolvePresetForLockedEncoder(basePreset, lockedEncoder) {
101264
101337
  }
101265
101338
  return basePreset;
101266
101339
  }
101340
+ function resolveLockedVp9CpuUsed(lockedEncoder) {
101341
+ if (lockedEncoder.encoder !== "libvpx-vp9-software") return void 0;
101342
+ return lockedEncoder.vp9CpuUsed ?? LEGACY_DISTRIBUTED_VP9_CPU_USED;
101343
+ }
101267
101344
  async function renderChunk(planDir, chunkIndex, outputChunkPath) {
101268
101345
  const start = Date.now();
101269
101346
  const log2 = defaultLogger;
@@ -101473,6 +101550,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
101473
101550
  preset,
101474
101551
  effectiveQuality,
101475
101552
  effectiveBitrate,
101553
+ engineConfig: {
101554
+ ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
101555
+ vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
101556
+ },
101476
101557
  // Distributed chunks emit a single ffmpeg call per chunk; the
101477
101558
  // in-process per-chunk-within-chunk path would re-split our
101478
101559
  // already-chunked work.
@@ -101547,7 +101628,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
101547
101628
  envRestore.restore();
101548
101629
  }
101549
101630
  }
101550
- var FFMPEG_VERSION_MISMATCH, PLAN_HASH_MISMATCH, MISSING_PLAN_ARTIFACT, CHUNK_INDEX_OUT_OF_RANGE, MISSING_RUNTIME_ENV_SNAPSHOT, RenderChunkValidationError;
101631
+ var FFMPEG_VERSION_MISMATCH, PLAN_HASH_MISMATCH, MISSING_PLAN_ARTIFACT, CHUNK_INDEX_OUT_OF_RANGE, MISSING_RUNTIME_ENV_SNAPSHOT, LEGACY_DISTRIBUTED_VP9_CPU_USED, RenderChunkValidationError;
101551
101632
  var init_renderChunk = __esm({
101552
101633
  "../producer/src/services/distributed/renderChunk.ts"() {
101553
101634
  "use strict";
@@ -101567,6 +101648,7 @@ var init_renderChunk = __esm({
101567
101648
  MISSING_PLAN_ARTIFACT = "MISSING_PLAN_ARTIFACT";
101568
101649
  CHUNK_INDEX_OUT_OF_RANGE = "CHUNK_INDEX_OUT_OF_RANGE";
101569
101650
  MISSING_RUNTIME_ENV_SNAPSHOT = "MISSING_RUNTIME_ENV_SNAPSHOT";
101651
+ LEGACY_DISTRIBUTED_VP9_CPU_USED = 2;
101570
101652
  RenderChunkValidationError = class extends Error {
101571
101653
  code;
101572
101654
  constructor(code, message) {
@@ -101580,41 +101662,89 @@ var init_renderChunk = __esm({
101580
101662
 
101581
101663
  // ../producer/src/services/render/audioPadTrim.ts
101582
101664
  import { spawn as spawn11 } from "child_process";
101583
- function buildPadTrimAudioArgs(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
101665
+ import { rmSync as rmSync15 } from "fs";
101666
+ import { pathToFileURL as pathToFileURL2 } from "url";
101667
+ function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds, audioInfo = {}) {
101584
101668
  const delta = targetDurationSeconds - sourceDurationSeconds;
101585
101669
  const targetSec = formatSeconds(targetDurationSeconds);
101586
101670
  if (Math.abs(delta) < AUDIO_DURATION_TOLERANCE_SECONDS) {
101587
101671
  return {
101588
101672
  operation: "copy",
101589
- args: ["-i", audioPath, "-c:a", "copy", "-y", outputPath]
101673
+ steps: [{ kind: "copy", args: ["-i", audioPath, "-c:a", "copy", "-y", outputPath] }],
101674
+ cleanupPaths: []
101590
101675
  };
101591
101676
  }
101592
101677
  if (delta > 0) {
101593
101678
  const padDur = formatSeconds(delta);
101679
+ const silencePath = `${outputPath}.pad-silence.aac`;
101594
101680
  return {
101595
101681
  operation: "pad",
101596
- args: [
101597
- "-i",
101598
- audioPath,
101599
- "-af",
101600
- `apad=pad_dur=${padDur}`,
101601
- "-c:a",
101602
- "aac",
101603
- "-b:a",
101604
- "192k",
101605
- "-y",
101606
- outputPath
101607
- ]
101682
+ steps: [
101683
+ {
101684
+ kind: "pad-silence",
101685
+ args: [
101686
+ "-f",
101687
+ "lavfi",
101688
+ "-i",
101689
+ `anullsrc=channel_layout=${channelLayoutForChannels(audioInfo.channels)}:sample_rate=${sampleRateForFilter(audioInfo.sampleRate)}`,
101690
+ "-t",
101691
+ padDur,
101692
+ "-c:a",
101693
+ "aac",
101694
+ "-b:a",
101695
+ "192k",
101696
+ "-y",
101697
+ silencePath
101698
+ ]
101699
+ },
101700
+ {
101701
+ kind: "pad-concat",
101702
+ args: [
101703
+ "-f",
101704
+ "concat",
101705
+ "-safe",
101706
+ "0",
101707
+ "-protocol_whitelist",
101708
+ "file,pipe,crypto,data",
101709
+ "-i",
101710
+ "pipe:0",
101711
+ "-c:a",
101712
+ "copy",
101713
+ "-y",
101714
+ outputPath
101715
+ ],
101716
+ stdin: `${concatFileLine(audioPath)}
101717
+ ${concatFileLine(silencePath)}
101718
+ `
101719
+ }
101720
+ ],
101721
+ cleanupPaths: [silencePath]
101608
101722
  };
101609
101723
  }
101610
101724
  return {
101611
101725
  operation: "trim",
101612
- args: ["-i", audioPath, "-t", targetSec, "-c:a", "copy", "-y", outputPath]
101726
+ steps: [
101727
+ { kind: "trim", args: ["-i", audioPath, "-t", targetSec, "-c:a", "copy", "-y", outputPath] }
101728
+ ],
101729
+ cleanupPaths: []
101613
101730
  };
101614
101731
  }
101615
101732
  function formatSeconds(sec) {
101616
101733
  return sec.toFixed(6);
101617
101734
  }
101735
+ function sampleRateForFilter(sampleRate) {
101736
+ return sampleRate !== void 0 && Number.isFinite(sampleRate) && sampleRate > 0 ? Math.round(sampleRate) : 48e3;
101737
+ }
101738
+ function channelLayoutForChannels(channels) {
101739
+ if (channels === 1) return "mono";
101740
+ if (channels === 6) return "5.1";
101741
+ if (channels === 8) return "7.1";
101742
+ return "stereo";
101743
+ }
101744
+ function concatFileLine(path2) {
101745
+ const normalized = pathToFileURL2(path2).href;
101746
+ return `file '${normalized.replace(/'/g, "'\\''")}'`;
101747
+ }
101618
101748
  async function padOrTrimAudioToVideoFrameCount(input2) {
101619
101749
  const probeVideo2 = input2.probeVideoFrameInfo ?? defaultProbeVideoFrameInfo;
101620
101750
  const probeAudio = input2.probeAudioInfo ?? defaultProbeAudioInfo;
@@ -101650,29 +101780,45 @@ async function padOrTrimAudioToVideoFrameCount(input2) {
101650
101780
  );
101651
101781
  }
101652
101782
  const targetDurationSeconds = videoInfo.frameCount * videoInfo.fpsDen / videoInfo.fpsNum;
101653
- const { args, operation } = buildPadTrimAudioArgs(
101783
+ const plan2 = buildPadTrimAudioPlan(
101654
101784
  input2.audioPath,
101655
101785
  input2.outputPath,
101656
101786
  audioInfo.durationSeconds,
101657
- targetDurationSeconds
101787
+ targetDurationSeconds,
101788
+ audioInfo
101658
101789
  );
101659
- const ffmpegResult = await runner(args);
101660
- if (!ffmpegResult.success) {
101790
+ try {
101791
+ for (const step of plan2.steps) {
101792
+ const ffmpegResult = await runner(step.args, { stdin: step.stdin });
101793
+ if (!ffmpegResult.success) {
101794
+ return {
101795
+ success: false,
101796
+ outputPath: input2.outputPath,
101797
+ targetDurationSeconds,
101798
+ sourceDurationSeconds: audioInfo.durationSeconds,
101799
+ operation: plan2.operation,
101800
+ error: ffmpegResult.error
101801
+ };
101802
+ }
101803
+ }
101804
+ } catch (err) {
101661
101805
  return {
101662
101806
  success: false,
101663
101807
  outputPath: input2.outputPath,
101664
101808
  targetDurationSeconds,
101665
101809
  sourceDurationSeconds: audioInfo.durationSeconds,
101666
- operation,
101667
- error: ffmpegResult.error
101810
+ operation: plan2.operation,
101811
+ error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
101668
101812
  };
101813
+ } finally {
101814
+ for (const path2 of plan2.cleanupPaths) rmSync15(path2, { force: true });
101669
101815
  }
101670
101816
  return {
101671
101817
  success: true,
101672
101818
  outputPath: input2.outputPath,
101673
101819
  targetDurationSeconds,
101674
101820
  sourceDurationSeconds: audioInfo.durationSeconds,
101675
- operation
101821
+ operation: plan2.operation
101676
101822
  };
101677
101823
  }
101678
101824
  function failResult(outputPath, target, source, error) {
@@ -101733,9 +101879,15 @@ function parseFrameRate2(rate) {
101733
101879
  }
101734
101880
  async function defaultProbeAudioInfo(audioPath) {
101735
101881
  const metadata = await extractAudioMetadata(audioPath);
101736
- return { durationSeconds: metadata.durationSeconds };
101882
+ return {
101883
+ durationSeconds: metadata.durationSeconds,
101884
+ sampleRate: metadata.sampleRate,
101885
+ channels: metadata.channels,
101886
+ audioCodec: metadata.audioCodec
101887
+ };
101737
101888
  }
101738
- async function defaultRunFfmpeg(args) {
101889
+ async function defaultRunFfmpeg(args, options) {
101890
+ if (options?.stdin !== void 0) return runFfmpegWithStdin(args, options.stdin);
101739
101891
  const result = await runFfmpeg(args);
101740
101892
  if (result.success) return { success: true };
101741
101893
  return {
@@ -101743,6 +101895,32 @@ async function defaultRunFfmpeg(args) {
101743
101895
  error: `[audioPadTrim] ${formatFfmpegError(result.exitCode, result.stderr)}`
101744
101896
  };
101745
101897
  }
101898
+ async function runFfmpegWithStdin(args, stdin) {
101899
+ return new Promise((resolve58) => {
101900
+ const proc = spawn11(getFfmpegBinary(), args);
101901
+ let stderr = "";
101902
+ proc.stderr.on("data", (data2) => {
101903
+ stderr += data2.toString();
101904
+ });
101905
+ proc.on("error", (err) => {
101906
+ resolve58({
101907
+ success: false,
101908
+ error: `[audioPadTrim] ${err instanceof Error ? err.message : String(err)}`
101909
+ });
101910
+ });
101911
+ proc.on("close", (code) => {
101912
+ if (code === 0) {
101913
+ resolve58({ success: true });
101914
+ return;
101915
+ }
101916
+ resolve58({
101917
+ success: false,
101918
+ error: `[audioPadTrim] ${formatFfmpegError(code, stderr)}`
101919
+ });
101920
+ });
101921
+ proc.stdin.end(stdin);
101922
+ });
101923
+ }
101746
101924
  function runFfprobeJson(args) {
101747
101925
  return new Promise((resolve58, reject) => {
101748
101926
  const proc = spawn11(getFfprobeBinary(), args);
@@ -101790,7 +101968,7 @@ import {
101790
101968
  mkdirSync as mkdirSync31,
101791
101969
  readFileSync as readFileSync39,
101792
101970
  readdirSync as readdirSync21,
101793
- rmSync as rmSync15,
101971
+ rmSync as rmSync16,
101794
101972
  statSync as statSync18,
101795
101973
  writeFileSync as writeFileSync24
101796
101974
  } from "fs";
@@ -101827,7 +102005,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
101827
102005
  mkdirSync31(dirname21(outputPath), { recursive: true });
101828
102006
  }
101829
102007
  const workDir = `${outputPath}.assemble-work`;
101830
- if (existsSync52(workDir)) rmSync15(workDir, { recursive: true, force: true });
102008
+ if (existsSync52(workDir)) rmSync16(workDir, { recursive: true, force: true });
101831
102009
  mkdirSync31(workDir, { recursive: true });
101832
102010
  try {
101833
102011
  const concatOutputPath = join62(workDir, `concat.${plan2.dimensions.format}`);
@@ -101943,7 +102121,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
101943
102121
  audioForMux,
101944
102122
  muxOutputPath,
101945
102123
  abortSignal,
101946
- void 0,
102124
+ { audioCodec: "aac" },
101947
102125
  { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen }
101948
102126
  );
101949
102127
  if (!muxResult.success) {
@@ -101965,7 +102143,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
101965
102143
  }
101966
102144
  } finally {
101967
102145
  try {
101968
- rmSync15(workDir, { recursive: true, force: true });
102146
+ rmSync16(workDir, { recursive: true, force: true });
101969
102147
  } catch (err) {
101970
102148
  log2.warn("[assemble] failed to remove work dir", {
101971
102149
  workDir,
@@ -101982,7 +102160,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
101982
102160
  };
101983
102161
  }
101984
102162
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
101985
- if (existsSync52(outputPath)) rmSync15(outputPath, { recursive: true, force: true });
102163
+ if (existsSync52(outputPath)) rmSync16(outputPath, { recursive: true, force: true });
101986
102164
  mkdirSync31(outputPath, { recursive: true });
101987
102165
  let globalIdx = 0;
101988
102166
  for (const chunkDir of chunkPaths) {
@@ -105915,6 +106093,7 @@ function buildDockerRunArgs(input2) {
105915
106093
  ...options.gifLoop != null ? ["--gif-loop", String(options.gifLoop)] : [],
105916
106094
  ...options.workers != null ? ["--workers", String(options.workers)] : [],
105917
106095
  ...options.crf != null ? ["--crf", String(options.crf)] : [],
106096
+ ...options.vp9CpuUsed != null ? ["--vp9-cpu-used", String(options.vp9CpuUsed)] : [],
105918
106097
  ...options.videoBitrate ? ["--video-bitrate", options.videoBitrate] : [],
105919
106098
  ...options.videoFrameFormat && options.videoFrameFormat !== "auto" ? ["--video-frame-format", options.videoFrameFormat] : [],
105920
106099
  ...options.quiet ? ["--quiet"] : [],
@@ -106503,7 +106682,7 @@ __export(render_exports, {
106503
106682
  renderLocal: () => renderLocal,
106504
106683
  resolveBrowserGpuForCli: () => resolveBrowserGpuForCli
106505
106684
  });
106506
- import { mkdirSync as mkdirSync35, readdirSync as readdirSync25, readFileSync as readFileSync48, statSync as statSync21, writeFileSync as writeFileSync28, rmSync as rmSync16 } from "fs";
106685
+ import { mkdirSync as mkdirSync35, readdirSync as readdirSync25, readFileSync as readFileSync48, statSync as statSync21, writeFileSync as writeFileSync28, rmSync as rmSync17 } from "fs";
106507
106686
  import { cpus as cpus4, freemem as freemem5, tmpdir as tmpdir5 } from "os";
106508
106687
  import { resolve as resolve42, dirname as dirname27, join as join70, basename as basename13 } from "path";
106509
106688
  import { execFileSync as execFileSync8, spawn as spawn14 } from "child_process";
@@ -106595,7 +106774,7 @@ function ensureDockerImage(version2, platform10, quiet) {
106595
106774
  const message = error instanceof Error ? error.message : String(error);
106596
106775
  throw new Error(`Failed to build Docker image: ${message}`);
106597
106776
  } finally {
106598
- rmSync16(tmpDir, { recursive: true, force: true });
106777
+ rmSync17(tmpDir, { recursive: true, force: true });
106599
106778
  }
106600
106779
  if (!quiet) console.log(c.dim(` Docker image: ${tag} (built)`));
106601
106780
  return tag;
@@ -106657,6 +106836,7 @@ async function renderDocker(projectDir, outputPath, options) {
106657
106836
  browserGpu: options.browserGpuMode === "hardware",
106658
106837
  hdrMode: options.hdrMode,
106659
106838
  crf: options.crf,
106839
+ vp9CpuUsed: options.vp9CpuUsed,
106660
106840
  videoBitrate: options.videoBitrate,
106661
106841
  videoFrameFormat: options.videoFrameFormat,
106662
106842
  quiet: options.quiet,
@@ -106745,7 +106925,8 @@ async function renderLocal(projectDir, outputPath, options) {
106745
106925
  browserGpuMode: options.browserGpuMode ?? "software",
106746
106926
  ...options.pageNavigationTimeoutMs != null ? { pageNavigationTimeout: options.pageNavigationTimeoutMs } : {},
106747
106927
  ...options.protocolTimeout != null && { protocolTimeout: options.protocolTimeout },
106748
- ...options.playerReadyTimeout != null && { playerReadyTimeout: options.playerReadyTimeout }
106928
+ ...options.playerReadyTimeout != null && { playerReadyTimeout: options.playerReadyTimeout },
106929
+ ...options.vp9CpuUsed != null ? { vp9CpuUsed: options.vp9CpuUsed } : {}
106749
106930
  }),
106750
106931
  hdrMode: options.hdrMode,
106751
106932
  crf: options.crf,
@@ -107116,6 +107297,10 @@ var init_render2 = __esm({
107116
107297
  type: "string",
107117
107298
  description: "Target video bitrate such as 10M. Mutually exclusive with --crf."
107118
107299
  },
107300
+ "vp9-cpu-used": {
107301
+ type: "string",
107302
+ description: "libvpx-vp9 -cpu-used value for WebM encodes (-8 to 8). Higher is faster with a larger quality/size tradeoff. Env: PRODUCER_VP9_CPU_USED."
107303
+ },
107119
107304
  gpu: { type: "boolean", description: "Use GPU encoding", default: false },
107120
107305
  "browser-gpu": {
107121
107306
  type: "boolean",
@@ -107380,6 +107565,19 @@ var init_render2 = __esm({
107380
107565
  }
107381
107566
  crf = parsed;
107382
107567
  }
107568
+ let vp9CpuUsed;
107569
+ if (args["vp9-cpu-used"] != null) {
107570
+ const raw = args["vp9-cpu-used"];
107571
+ const parsed = Number(raw);
107572
+ if (!Number.isInteger(parsed) || parsed < MIN_VP9_CPU_USED || parsed > MAX_VP9_CPU_USED) {
107573
+ errorBox(
107574
+ "Invalid vp9-cpu-used",
107575
+ `Got "${raw}". Must be an integer between ${MIN_VP9_CPU_USED} and ${MAX_VP9_CPU_USED}.`
107576
+ );
107577
+ process.exit(1);
107578
+ }
107579
+ vp9CpuUsed = parsed;
107580
+ }
107383
107581
  if (args["video-bitrate"] != null && !videoBitrate) {
107384
107582
  errorBox(
107385
107583
  "Invalid video-bitrate",
@@ -107502,6 +107700,7 @@ var init_render2 = __esm({
107502
107700
  browserGpuMode,
107503
107701
  hdrMode,
107504
107702
  crf,
107703
+ vp9CpuUsed,
107505
107704
  videoBitrate,
107506
107705
  quiet: batchQuiet,
107507
107706
  browserPath,
@@ -107549,6 +107748,7 @@ var init_render2 = __esm({
107549
107748
  browserGpuMode,
107550
107749
  hdrMode: args.sdr ? "force-sdr" : args.hdr ? "force-hdr" : "auto",
107551
107750
  crf,
107751
+ vp9CpuUsed,
107552
107752
  videoBitrate,
107553
107753
  videoFrameFormat,
107554
107754
  quiet,
@@ -107572,6 +107772,7 @@ var init_render2 = __esm({
107572
107772
  browserGpuMode,
107573
107773
  hdrMode: args.sdr ? "force-sdr" : args.hdr ? "force-hdr" : "auto",
107574
107774
  crf,
107775
+ vp9CpuUsed,
107575
107776
  videoBitrate,
107576
107777
  videoFrameFormat,
107577
107778
  quiet,
@@ -109991,9 +110192,9 @@ __export(pipeline_exports, {
109991
110192
  waitForExit: () => waitForExit
109992
110193
  });
109993
110194
  import { spawn as spawn15 } from "child_process";
109994
- import { extname as extname10 } from "path";
110195
+ import { extname as extname11 } from "path";
109995
110196
  function inferOutputFormat(outputPath) {
109996
- const ext = extname10(outputPath).toLowerCase();
110197
+ const ext = extname11(outputPath).toLowerCase();
109997
110198
  if (ext === ".webm") return "webm";
109998
110199
  if (ext === ".mov") return "mov";
109999
110200
  if (ext === ".png") return "png";
@@ -110002,7 +110203,7 @@ function inferOutputFormat(outputPath) {
110002
110203
  );
110003
110204
  }
110004
110205
  function inferInputKind(inputPath) {
110005
- const ext = extname10(inputPath).toLowerCase();
110206
+ const ext = extname11(inputPath).toLowerCase();
110006
110207
  if (VIDEO_EXTENSIONS2.has(ext)) return "video";
110007
110208
  if (IMAGE_EXTENSIONS.has(ext)) return "image";
110008
110209
  throw new Error(
@@ -110047,6 +110248,8 @@ function buildEncoderArgs2(format, width, height, fps, outputPath, quality = DEF
110047
110248
  "good",
110048
110249
  "-row-mt",
110049
110250
  "1",
110251
+ "-cpu-used",
110252
+ String(DEFAULT_VP9_CPU_USED),
110050
110253
  "-auto-alt-ref",
110051
110254
  "0",
110052
110255
  "-pix_fmt",
@@ -110102,7 +110305,7 @@ function resolveRenderTargets(inputPath, outputPath, backgroundOutputPath) {
110102
110305
  const inputKind = inferInputKind(inputPath);
110103
110306
  if (inputKind === "image" && format !== "png") {
110104
110307
  throw new Error(
110105
- `Image input requires a .png output (got ${extname10(outputPath)}). Use a video input for .webm/.mov.`
110308
+ `Image input requires a .png output (got ${extname11(outputPath)}). Use a video input for .webm/.mov.`
110106
110309
  );
110107
110310
  }
110108
110311
  if (inputKind === "video" && format === "png") {
@@ -110285,6 +110488,7 @@ var init_pipeline = __esm({
110285
110488
  "use strict";
110286
110489
  init_ffmpeg();
110287
110490
  init_inference();
110491
+ init_src2();
110288
110492
  QUALITY_CRF = {
110289
110493
  fast: 30,
110290
110494
  balanced: 18,
@@ -110521,7 +110725,7 @@ __export(transcribe_exports2, {
110521
110725
  examples: () => examples18
110522
110726
  });
110523
110727
  import { existsSync as existsSync72, writeFileSync as writeFileSync30 } from "fs";
110524
- import { resolve as resolve48, join as join78, extname as extname11, dirname as dirname32 } from "path";
110728
+ import { resolve as resolve48, join as join78, extname as extname12, dirname as dirname32 } from "path";
110525
110729
  async function importTranscript(inputPath, dir, json) {
110526
110730
  const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
110527
110731
  const { words, format } = loadTranscript2(inputPath);
@@ -110657,7 +110861,7 @@ var init_transcribe2 = __esm({
110657
110861
  process.exit(1);
110658
110862
  }
110659
110863
  const dir = resolve48(args.dir ?? dirname32(inputPath));
110660
- const ext = extname11(inputPath).toLowerCase();
110864
+ const ext = extname12(inputPath).toLowerCase();
110661
110865
  const isImport = ext === ".json" || ext === ".srt" || ext === ".vtt";
110662
110866
  if (isImport) {
110663
110867
  return importTranscript(inputPath, dir, args.json);
@@ -110946,7 +111150,7 @@ __export(tts_exports, {
110946
111150
  examples: () => examples19
110947
111151
  });
110948
111152
  import { existsSync as existsSync75, readFileSync as readFileSync56 } from "fs";
110949
- import { resolve as resolve49, extname as extname12 } from "path";
111153
+ import { resolve as resolve49, extname as extname13 } from "path";
110950
111154
  function listVoices(json) {
110951
111155
  const rows = BUNDLED_VOICES.map((v2) => ({ ...v2, defaultLang: inferLangFromVoiceId(v2.id) }));
110952
111156
  if (json) {
@@ -111055,7 +111259,7 @@ var init_tts = __esm({
111055
111259
  }
111056
111260
  let text;
111057
111261
  const maybeFile = resolve49(args.input);
111058
- if (existsSync75(maybeFile) && extname12(maybeFile).toLowerCase() === ".txt") {
111262
+ if (existsSync75(maybeFile) && extname13(maybeFile).toLowerCase() === ".txt") {
111059
111263
  text = readFileSync56(maybeFile, "utf-8").trim();
111060
111264
  if (!text) {
111061
111265
  console.error(c.error("File is empty."));
@@ -112056,7 +112260,7 @@ __export(contactSheet_exports, {
112056
112260
  });
112057
112261
  import sharp from "sharp";
112058
112262
  import { readdirSync as readdirSync29, readFileSync as readFileSync59, writeFileSync as writeFileSync32, unlinkSync as unlinkSync8, existsSync as existsSync78 } from "fs";
112059
- import { join as join83, extname as extname13, basename as basename16, dirname as dirname36 } from "path";
112263
+ import { join as join83, extname as extname14, basename as basename16, dirname as dirname36 } from "path";
112060
112264
  async function createContactSheet(imagePaths, outputPath, opts = {}) {
112061
112265
  const {
112062
112266
  cols = 3,
@@ -112089,7 +112293,7 @@ async function createContactSheet(imagePaths, outputPath, opts = {}) {
112089
112293
  overlays.push({ input: resized, left: x3, top: y + labelH });
112090
112294
  let labelText = `${i2 + 1}`;
112091
112295
  if (labelMode === "filename") {
112092
- labelText = `${i2 + 1}. ${basename16(files[i2]).replace(extname13(files[i2]), "")}`;
112296
+ labelText = `${i2 + 1}. ${basename16(files[i2]).replace(extname14(files[i2]), "")}`;
112093
112297
  } else if (labelMode === "custom" && labels?.[i2]) {
112094
112298
  labelText = `${i2 + 1}. ${labels[i2]}`;
112095
112299
  }
@@ -112171,7 +112375,7 @@ async function createSnapshotContactSheet(snapshotsDir, outputPath) {
112171
112375
  async function createAssetContactSheet(assetsDir, outputPath) {
112172
112376
  if (!existsSync78(assetsDir)) return [];
112173
112377
  const imageExts = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp"]);
112174
- const assetFiles = readdirSync29(assetsDir).filter((f3) => imageExts.has(extname13(f3).toLowerCase()) && !f3.includes("contact-sheet")).sort();
112378
+ const assetFiles = readdirSync29(assetsDir).filter((f3) => imageExts.has(extname14(f3).toLowerCase()) && !f3.includes("contact-sheet")).sort();
112175
112379
  if (assetFiles.length === 0) return [];
112176
112380
  const paths = assetFiles.map((f3) => join83(assetsDir, f3));
112177
112381
  return createContactSheetPages(paths, outputPath, {
@@ -151984,7 +152188,7 @@ __export(snapshot_exports, {
151984
152188
  examples: () => examples25
151985
152189
  });
151986
152190
  import { spawn as spawn16 } from "child_process";
151987
- import { existsSync as existsSync79, mkdtempSync as mkdtempSync5, readFileSync as readFileSync60, mkdirSync as mkdirSync40, rmSync as rmSync17, writeFileSync as writeFileSync33 } from "fs";
152191
+ import { existsSync as existsSync79, mkdtempSync as mkdtempSync5, readFileSync as readFileSync60, mkdirSync as mkdirSync40, rmSync as rmSync18, writeFileSync as writeFileSync33 } from "fs";
151988
152192
  import { tmpdir as tmpdir6 } from "os";
151989
152193
  import { resolve as resolve51, join as join84, relative as relative14, isAbsolute as isAbsolute12 } from "path";
151990
152194
  async function extractVideoFrameToBuffer(videoPath, timeSeconds, useVp9AlphaDecoder = false) {
@@ -152035,7 +152239,7 @@ async function extractVideoFrameToBuffer(videoPath, timeSeconds, useVp9AlphaDeco
152035
152239
  return readFileSync60(outPath);
152036
152240
  } finally {
152037
152241
  try {
152038
- rmSync17(tmp, { recursive: true, force: true });
152242
+ rmSync18(tmp, { recursive: true, force: true });
152039
152243
  } catch {
152040
152244
  }
152041
152245
  }
@@ -152136,7 +152340,7 @@ async function captureSnapshots(projectDir, opts) {
152136
152340
  const { readdirSync: readdirSync35 } = await import("fs");
152137
152341
  for (const file of readdirSync35(snapshotDir)) {
152138
152342
  if (/\.(png|jpg|jpeg)$/i.test(file)) {
152139
- rmSync17(join84(snapshotDir, file), { force: true });
152343
+ rmSync18(join84(snapshotDir, file), { force: true });
152140
152344
  }
152141
152345
  }
152142
152346
  } catch {
@@ -152428,7 +152632,7 @@ ${c.error("\u2717")} Snapshot failed: ${msg}`);
152428
152632
 
152429
152633
  // src/capture/assetDownloader.ts
152430
152634
  import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync41 } from "fs";
152431
- import { join as join85, extname as extname14 } from "path";
152635
+ import { join as join85, extname as extname15 } from "path";
152432
152636
  import { createHash as createHash12 } from "crypto";
152433
152637
  function svgContentHashSlug(svgSource, isLogo) {
152434
152638
  const hash2 = createHash12("sha1").update(svgSource).digest("hex").slice(0, 8);
@@ -152463,7 +152667,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
152463
152667
  for (const icon of faviconLinks || []) {
152464
152668
  if (!icon.href) continue;
152465
152669
  try {
152466
- const ext = extname14(new URL(icon.href).pathname) || ".ico";
152670
+ const ext = extname15(new URL(icon.href).pathname) || ".ico";
152467
152671
  const name = `favicon${ext}`;
152468
152672
  const localPath = `assets/${name}`;
152469
152673
  const buffer = await fetchBuffer(icon.href);
@@ -152507,7 +152711,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
152507
152711
  const results = await Promise.allSettled(
152508
152712
  batch.map(async ({ url, isPoster, catalog }) => {
152509
152713
  const parsedUrl = new URL(url);
152510
- const pathExt = extname14(parsedUrl.pathname);
152714
+ const pathExt = extname15(parsedUrl.pathname);
152511
152715
  const ext = pathExt && pathExt.length <= 5 ? pathExt : ".jpg";
152512
152716
  const buffer = await fetchBuffer(url);
152513
152717
  if (!buffer) return null;
@@ -152542,7 +152746,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
152542
152746
  }
152543
152747
  if (tokens.ogImage && !downloadedUrls.has(normalizeUrl(tokens.ogImage))) {
152544
152748
  try {
152545
- const ext = extname14(new URL(tokens.ogImage).pathname) || ".jpg";
152749
+ const ext = extname15(new URL(tokens.ogImage).pathname) || ".jpg";
152546
152750
  const localPath = `assets/og-image${ext}`;
152547
152751
  const buffer = await fetchBuffer(tokens.ogImage);
152548
152752
  if (buffer && buffer.length > 5e3) {
@@ -154276,7 +154480,7 @@ var init_animationCataloger = __esm({
154276
154480
 
154277
154481
  // src/capture/mediaCapture.ts
154278
154482
  import { mkdirSync as mkdirSync43, writeFileSync as writeFileSync36, readdirSync as readdirSync31, readFileSync as readFileSync63, statSync as statSync25 } from "fs";
154279
- import { join as join88, extname as extname15 } from "path";
154483
+ import { join as join88, extname as extname16 } from "path";
154280
154484
  async function saveLottieAnimations(discoveredLotties, lottieDir) {
154281
154485
  let savedCount = 0;
154282
154486
  const savedHashes = /* @__PURE__ */ new Set();
@@ -154415,7 +154619,7 @@ async function downloadVideoBody(srcUrl, filename, videosDir) {
154415
154619
  if (isPrivateUrl(srcUrl)) return null;
154416
154620
  let ext = "";
154417
154621
  try {
154418
- ext = extname15(new URL(srcUrl).pathname).toLowerCase();
154622
+ ext = extname16(new URL(srcUrl).pathname).toLowerCase();
154419
154623
  } catch {
154420
154624
  return null;
154421
154625
  }
@@ -156359,7 +156563,7 @@ __export(state_exports, {
156359
156563
  stateFilePath: () => stateFilePath,
156360
156564
  writeStackOutputs: () => writeStackOutputs
156361
156565
  });
156362
- import { existsSync as existsSync86, mkdirSync as mkdirSync46, readdirSync as readdirSync34, readFileSync as readFileSync66, rmSync as rmSync18, writeFileSync as writeFileSync41 } from "fs";
156566
+ import { existsSync as existsSync86, mkdirSync as mkdirSync46, readdirSync as readdirSync34, readFileSync as readFileSync66, rmSync as rmSync19, writeFileSync as writeFileSync41 } from "fs";
156363
156567
  import { dirname as dirname37, join as join94 } from "path";
156364
156568
  function stateFilePath(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
156365
156569
  return join94(cwd, STATE_DIR_NAME, `${STATE_FILE_PREFIX}${stackName}.json`);
@@ -156381,7 +156585,7 @@ function readStackOutputs(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
156381
156585
  }
156382
156586
  function deleteStackOutputs(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
156383
156587
  const path2 = stateFilePath(stackName, cwd);
156384
- if (existsSync86(path2)) rmSync18(path2);
156588
+ if (existsSync86(path2)) rmSync19(path2);
156385
156589
  }
156386
156590
  function listStackNames(cwd = process.cwd()) {
156387
156591
  const dir = join94(cwd, STATE_DIR_NAME);