hyperframes 0.8.21 → 0.8.23

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.8.21" : "0.0.0-dev";
53
+ VERSION = true ? "0.8.23" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -70946,6 +70946,11 @@ var init_evenDimensions = __esm({
70946
70946
 
70947
70947
  // ../engine/src/utils/runFfmpeg.ts
70948
70948
  import { spawn as spawn2 } from "child_process";
70949
+ function isExternalFfmpegInterruption(result) {
70950
+ if (result.terminationReason !== "exit" || result.exitCode === 0) return false;
70951
+ if (result.signal === "SIGTERM") return true;
70952
+ return result.exitCode === 255 && FFMPEG_SIGTERM_EXIT_LINE.test(result.stderr);
70953
+ }
70949
70954
  function formatWindowsFfmpegExit(exitCode) {
70950
70955
  if (process.platform !== "win32" || exitCode === null) return void 0;
70951
70956
  if (exitCode === 3221225781 || exitCode === -1073741515) {
@@ -70985,22 +70990,28 @@ async function runFfmpeg(args, opts) {
70985
70990
  onStderr: opts?.onStderr
70986
70991
  });
70987
70992
  const outcome = await managed.wait();
70988
- return {
70993
+ const result = {
70989
70994
  success: outcome.reason === "exit" && outcome.exitCode === 0,
70990
70995
  exitCode: outcome.exitCode,
70996
+ signal: outcome.signal,
70991
70997
  stderr: outcome.stderr,
70992
70998
  durationMs: outcome.durationMs,
70993
70999
  terminationReason: outcome.reason,
70994
71000
  error: outcome.error
70995
71001
  };
71002
+ if (isExternalFfmpegInterruption(result)) {
71003
+ result.failureReason = "external_interruption";
71004
+ }
71005
+ return result;
70996
71006
  }
70997
- var DEFAULT_TIMEOUT, DEFAULT_STDERR_TAIL_LINES;
71007
+ var FFMPEG_SIGTERM_EXIT_LINE, DEFAULT_TIMEOUT, DEFAULT_STDERR_TAIL_LINES;
70998
71008
  var init_runFfmpeg = __esm({
70999
71009
  "../engine/src/utils/runFfmpeg.ts"() {
71000
71010
  "use strict";
71001
71011
  init_ffmpegBinaries();
71002
71012
  init_processTracker();
71003
71013
  init_managedChildProcess();
71014
+ FFMPEG_SIGTERM_EXIT_LINE = /^Exiting normally, received signal 15\.?\r?$/m;
71004
71015
  DEFAULT_TIMEOUT = 3e5;
71005
71016
  DEFAULT_STDERR_TAIL_LINES = 15;
71006
71017
  }
@@ -71949,7 +71960,8 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
71949
71960
  formatFfmpegError(result.exitCode, result.stderr),
71950
71961
  result.terminationReason === "deadline",
71951
71962
  encodeTimeout
71952
- )
71963
+ ),
71964
+ failureReason: isExternalFfmpegInterruption(result) ? "external_interruption" : void 0
71953
71965
  };
71954
71966
  }
71955
71967
  const fileSize = existsSync7(outputPath) ? statSync2(outputPath).size : 0;
@@ -72025,7 +72037,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
72025
72037
  durationMs: Date.now() - start,
72026
72038
  framesEncoded: 0,
72027
72039
  fileSize: 0,
72028
- error: chunkResult.error
72040
+ error: chunkResult.error,
72041
+ failureReason: isExternalFfmpegInterruption(processResult) ? "external_interruption" : void 0
72029
72042
  };
72030
72043
  }
72031
72044
  chunkPaths.push(chunkPath);
@@ -72053,7 +72066,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
72053
72066
  durationMs: Date.now() - start,
72054
72067
  framesEncoded: 0,
72055
72068
  fileSize: 0,
72056
- error: concatResult.error
72069
+ error: concatResult.error,
72070
+ failureReason: isExternalFfmpegInterruption(concatProcessResult) ? "external_interruption" : void 0
72057
72071
  };
72058
72072
  }
72059
72073
  const fileSize = existsSync7(outputPath) ? statSync2(outputPath).size : 0;
@@ -72106,7 +72120,8 @@ async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, confi
72106
72120
  success: result.success,
72107
72121
  outputPath,
72108
72122
  durationMs: result.durationMs,
72109
- error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0
72123
+ error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0,
72124
+ failureReason: result.failureReason
72110
72125
  };
72111
72126
  }
72112
72127
  async function applyFaststart(inputPath, outputPath, signal, config, fps) {
@@ -72134,7 +72149,8 @@ async function applyFaststart(inputPath, outputPath, signal, config, fps) {
72134
72149
  success: result.success,
72135
72150
  outputPath,
72136
72151
  durationMs: result.durationMs,
72137
- error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0
72152
+ error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0,
72153
+ failureReason: result.failureReason
72138
72154
  };
72139
72155
  }
72140
72156
  var ENCODER_PRESETS, KNOWN_NON_AAC_AUDIO_EXTENSIONS;
@@ -72424,6 +72440,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
72424
72440
  let exitStatus = "running";
72425
72441
  let stderr = "";
72426
72442
  let exitCode = null;
72443
+ let exitSignal = null;
72427
72444
  let terminationReason = "exit";
72428
72445
  ffmpeg.stdin?.on("error", () => {
72429
72446
  });
@@ -72436,6 +72453,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
72436
72453
  });
72437
72454
  const exitPromise = managed.wait().then((outcome) => {
72438
72455
  exitCode = outcome.exitCode;
72456
+ exitSignal = outcome.signal;
72439
72457
  stderr = outcome.stderr;
72440
72458
  terminationReason = outcome.reason;
72441
72459
  exitStatus = outcome.reason === "exit" && outcome.exitCode === 0 ? "success" : "error";
@@ -72467,7 +72485,11 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
72467
72485
  const encoder = {
72468
72486
  writeFrame: async (buffer) => {
72469
72487
  const stdin = ffmpeg.stdin;
72470
- if (exitStatus !== "running" || !stdin || stdin.destroyed) {
72488
+ if (exitStatus !== "running") {
72489
+ return false;
72490
+ }
72491
+ if (!stdin || stdin.destroyed) {
72492
+ await exitPromise;
72471
72493
  return false;
72472
72494
  }
72473
72495
  const copy = Buffer.from(buffer);
@@ -72507,7 +72529,13 @@ FFmpeg stopped after ${streamingTimeout} ms without consuming a frame.` : "";
72507
72529
  success: false,
72508
72530
  durationMs,
72509
72531
  fileSize: 0,
72510
- error: `${formatFfmpegError(exitCode, stderr)}${inactivitySuffix}`
72532
+ error: `${formatFfmpegError(exitCode, stderr)}${inactivitySuffix}`,
72533
+ failureReason: isExternalFfmpegInterruption({
72534
+ exitCode,
72535
+ signal: exitSignal,
72536
+ stderr,
72537
+ terminationReason
72538
+ }) ? "external_interruption" : void 0
72511
72539
  };
72512
72540
  }
72513
72541
  const fileSize = existsSync8(outputPath) ? statSync3(outputPath).size : 0;
@@ -72517,6 +72545,15 @@ FFmpeg stopped after ${streamingTimeout} ms without consuming a frame.` : "";
72517
72545
  getExitError: () => {
72518
72546
  if (exitStatus !== "error") return void 0;
72519
72547
  return formatFfmpegError(exitCode, stderr);
72548
+ },
72549
+ getExitFailureReason: () => {
72550
+ if (exitStatus !== "error") return void 0;
72551
+ return isExternalFfmpegInterruption({
72552
+ exitCode,
72553
+ signal: exitSignal,
72554
+ stderr,
72555
+ terminationReason
72556
+ }) ? "external_interruption" : void 0;
72520
72557
  }
72521
72558
  };
72522
72559
  return encoder;
@@ -81981,7 +82018,7 @@ function shouldBlockRender(strictErrors, strictAll, totalErrors, totalWarnings)
81981
82018
  }
81982
82019
  function execFileAsync(file, args) {
81983
82020
  return new Promise((resolvePromise, reject2) => {
81984
- execFile2(file, args, { timeout: PROBE_TIMEOUT_MS }, (error, stdout2) => {
82021
+ execFile2(file, args, { timeout: PROBE_TIMEOUT_MS, windowsHide: true }, (error, stdout2) => {
81985
82022
  if (error) reject2(error);
81986
82023
  else resolvePromise(stdout2.toString());
81987
82024
  });
@@ -83998,6 +84035,47 @@ ${other.raw}`)
83998
84035
  }
83999
84036
  return findings;
84000
84037
  },
84038
+ // gsap_timeline_return_used_as_tween — `tl.to()` returns the TIMELINE, not the tween it just
84039
+ // created. `gsap.to()` returns a Tween, so the two read identically and behave nothing alike.
84040
+ // Capturing the timeline's return and later treating it as an individual animation aims a
84041
+ // tween-scoped call at the whole composition: `.kill()` on it interrupts the master timeline
84042
+ // and detaches it from its parent, which stops a parent-driven seek dead while leaving an
84043
+ // explicit `tl.progress()` still working -- so a render of the packaged defaults looks fine
84044
+ // and only live playback breaks. A rebuild that collected these to discard them also leaves
84045
+ // every previous keyframe in place and stacks the new ones on top.
84046
+ ({ scripts }) => {
84047
+ const findings = [];
84048
+ for (const script of scripts) {
84049
+ const source = stripJsComments(script.content);
84050
+ const timelineVars = collectTimelineVarNames(source);
84051
+ if (timelineVars.length === 0) continue;
84052
+ const receivers = timelineVars.map(escapeRegExp22).join("|");
84053
+ const pattern = new RegExp(
84054
+ String.raw`(?:\.push\s*\(\s*|(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*)(?:${receivers})\s*\.\s*(to|from|fromTo|set|call|add)\s*\(`,
84055
+ "g"
84056
+ );
84057
+ let match;
84058
+ while ((match = pattern.exec(source)) !== null) {
84059
+ const boundName = match[1];
84060
+ const method = match[2];
84061
+ if (boundName) {
84062
+ const treatedAsTween = new RegExp(
84063
+ String.raw`\b${escapeRegExp22(boundName)}\s*\.\s*(?:kill|revert|invalidate|pause|resume|restart|seek|progress|timeScale)\s*\(`
84064
+ );
84065
+ if (!treatedAsTween.test(source)) continue;
84066
+ }
84067
+ const contextStart = Math.max(0, match.index - 40);
84068
+ findings.push({
84069
+ code: "gsap_timeline_return_used_as_tween",
84070
+ severity: "error",
84071
+ message: `\`${match[0].includes(".push") ? "push" : boundName}\` captures the return of \`.${method}()\` on timeline \`${timelineVars[0]}\`, but a timeline's \`.to()\`/\`.from()\`/\`.set()\` returns THE TIMELINE ITSELF, not the tween it created. Every captured value is the same master timeline, so a tween-scoped call on it \u2014 \`.kill()\` above all \u2014 hits the whole composition: it interrupts the timeline and detaches it from its parent, which freezes a parent-driven seek while an explicit \`tl.progress()\` keeps working. Only \`gsap.to()\` returns a tween.`,
84072
+ fixHint: "To build a group of keyframes you can later discard, put them in a nested timeline: `const nested = gsap.timeline(); nested.to(...); tl.add(nested, 0);` \u2014 then `nested.kill()` replaces exactly those keyframes and cannot reach `tl`. Children keep their absolute times when the nest is added at 0. To hold a single real tween, create it with `gsap.to(...)` and place it with `tl.add(tween, at)`.",
84073
+ snippet: truncateSnippet(source.slice(contextStart, match.index + match[0].length + 60))
84074
+ });
84075
+ }
84076
+ }
84077
+ return findings;
84078
+ },
84001
84079
  // gsap_group_selector_keyframes
84002
84080
  ({ scripts }) => {
84003
84081
  const findings = [];
@@ -87504,6 +87582,14 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
87504
87582
  if (format === "png") args.push("-compression_level", "1");
87505
87583
  args.push("-y", outputPattern);
87506
87584
  const processResult = await runFfmpeg(args, { signal, timeout: ffmpegProcessTimeout });
87585
+ if (processResult.failureReason === "external_interruption") {
87586
+ throw new VideoSourceExtractionError(
87587
+ "external_interruption",
87588
+ true,
87589
+ "Video frame extraction interrupted by host lifecycle",
87590
+ `FFmpeg exited with code ${processResult.exitCode}: ${processResult.stderr.slice(-500)}`
87591
+ );
87592
+ }
87507
87593
  if (processResult.terminationReason === "abort") {
87508
87594
  throw new VideoSourceExtractionError("cancelled", false, "Video extraction cancelled");
87509
87595
  }
@@ -89716,7 +89802,10 @@ function ffmpegFailure(stage, result, elementId) {
89716
89802
  let reason = "ffmpeg_failed";
89717
89803
  let owner = "system";
89718
89804
  let retryable = false;
89719
- if (result.terminationReason === "abort") {
89805
+ if (result.failureReason === "external_interruption") {
89806
+ reason = "external_interruption";
89807
+ retryable = true;
89808
+ } else if (result.terminationReason === "abort") {
89720
89809
  reason = "cancelled";
89721
89810
  owner = "user";
89722
89811
  } else if (result.terminationReason === "deadline" || result.terminationReason === "inactivity") {
@@ -90041,7 +90130,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
90041
90130
  let result = await runMix(false);
90042
90131
  let degradedAutomation = false;
90043
90132
  const hasAutomation = tracks.some((track) => (track.volumeKeyframes?.length ?? 0) > 0);
90044
- if (!result.success && !signal?.aborted && hasAutomation) {
90133
+ if (!result.success && result.failureReason !== "external_interruption" && !signal?.aborted && hasAutomation) {
90045
90134
  const retry = await runMix(true);
90046
90135
  if (retry.success) {
90047
90136
  result = retry;
@@ -92991,6 +93080,7 @@ __export(src_exports, {
92991
93080
  initializeSession: () => initializeSession,
92992
93081
  injectVideoFramesBatch: () => injectVideoFramesBatch,
92993
93082
  isDrawElementVerificationError: () => isDrawElementVerificationError,
93083
+ isExternalFfmpegInterruption: () => isExternalFfmpegInterruption,
92994
93084
  isFatalCaptureFailure: () => isFatalCaptureFailure,
92995
93085
  isHdrColorSpace: () => isHdrColorSpace,
92996
93086
  isHttpUrl: () => isHttpUrl,
@@ -97382,7 +97472,8 @@ function detectH264EncoderMode(ffmpegPath, gpuRequested) {
97382
97472
  const encoders = execFileSync3(ffmpegPath, ["-hide_banner", "-encoders"], {
97383
97473
  encoding: "utf-8",
97384
97474
  stdio: ["ignore", "pipe", "pipe"],
97385
- timeout: 5e3
97475
+ timeout: 5e3,
97476
+ windowsHide: true
97386
97477
  });
97387
97478
  return resolveH264EncoderMode(encoders, gpuRequested);
97388
97479
  }
@@ -100651,7 +100742,8 @@ function killProcessTree(pid, signal = "SIGTERM") {
100651
100742
  try {
100652
100743
  execFileSync7("taskkill", windowsProcessTreeKillArgs(pid), {
100653
100744
  stdio: "ignore",
100654
- timeout: 5e3
100745
+ timeout: 5e3,
100746
+ windowsHide: true
100655
100747
  });
100656
100748
  } catch {
100657
100749
  }
@@ -100689,9 +100781,14 @@ function processIdentity(pid) {
100689
100781
  "-NoProfile",
100690
100782
  "-NonInteractive",
100691
100783
  "-Command",
100692
- `(Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}').CreationDate.ToFileTimeUtc()`
100784
+ `$p = Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}' -ErrorAction SilentlyContinue; if ($p) { $p.CreationDate.ToFileTimeUtc() }`
100693
100785
  ],
100694
- { encoding: "utf8", timeout: 2e3 }
100786
+ {
100787
+ encoding: "utf8",
100788
+ timeout: 2e3,
100789
+ stdio: ["pipe", "pipe", "ignore"],
100790
+ windowsHide: true
100791
+ }
100695
100792
  ).trim();
100696
100793
  return created ? `windows:${created}` : null;
100697
100794
  }
@@ -100718,9 +100815,14 @@ function processParentPid(pid) {
100718
100815
  "-NoProfile",
100719
100816
  "-NonInteractive",
100720
100817
  "-Command",
100721
- `(Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}').ParentProcessId`
100818
+ `$p = Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}' -ErrorAction SilentlyContinue; if ($p) { $p.ParentProcessId }`
100722
100819
  ],
100723
- { encoding: "utf8", timeout: 2e3 }
100820
+ {
100821
+ encoding: "utf8",
100822
+ timeout: 2e3,
100823
+ stdio: ["pipe", "pipe", "ignore"],
100824
+ windowsHide: true
100825
+ }
100724
100826
  ) : execFileSync7("ps", ["-o", "ppid=", "-p", String(pid)], {
100725
100827
  encoding: "utf8",
100726
100828
  timeout: 2e3
@@ -102310,7 +102412,7 @@ var init_chunk_W2SBTCO2 = __esm({
102310
102412
  }
102311
102413
  });
102312
102414
 
102313
- // ../studio-server/dist/chunk-6H3V3WGJ.js
102415
+ // ../studio-server/dist/chunk-NJISQQTN.js
102314
102416
  import { existsSync as existsSync34, statSync as statSync10 } from "fs";
102315
102417
  import { relative as relative8, resolve as resolve18, sep as sep5 } from "path";
102316
102418
  import { execFile as execFile6 } from "child_process";
@@ -102514,8 +102616,8 @@ async function scanProjectMediaCodecMap(projectDir, htmlSources, options = {}) {
102514
102616
  return map;
102515
102617
  }
102516
102618
  var execFileRunner, VIDEO_EXT, IMAGE_EXT, AUDIO_EXT, ALPHA_PIX_FMT_RE, BROWSER_HOSTILE_CODECS, PROXY_VARIANT_CONFIG, defaultProbeCache, MAX_PROBE_CACHE_ENTRIES, VIDEO_SRC_RE, PROBE_CONCURRENCY2;
102517
- var init_chunk_6H3V3WGJ = __esm({
102518
- "../studio-server/dist/chunk-6H3V3WGJ.js"() {
102619
+ var init_chunk_NJISQQTN = __esm({
102620
+ "../studio-server/dist/chunk-NJISQQTN.js"() {
102519
102621
  "use strict";
102520
102622
  init_assets();
102521
102623
  init_assetResolution();
@@ -102524,7 +102626,7 @@ var init_chunk_6H3V3WGJ = __esm({
102524
102626
  execFile6(
102525
102627
  command2,
102526
102628
  args,
102527
- { timeout: options?.timeout, maxBuffer: options?.maxBuffer },
102629
+ { timeout: options?.timeout, maxBuffer: options?.maxBuffer, windowsHide: true },
102528
102630
  (error, stdout2, stderr) => {
102529
102631
  if (error && error.code === "ENOENT") {
102530
102632
  resolvePromise({ status: null, stdout: "", stderr: "", error });
@@ -102574,7 +102676,7 @@ var init_chunk_6H3V3WGJ = __esm({
102574
102676
  }
102575
102677
  });
102576
102678
 
102577
- // ../studio-server/dist/chunk-ZPI6QXJH.js
102679
+ // ../studio-server/dist/chunk-5MUVDQOD.js
102578
102680
  import { spawn as spawn8 } from "child_process";
102579
102681
  import { createHash as createHash8, randomUUID as randomUUID8 } from "crypto";
102580
102682
  import {
@@ -102769,7 +102871,8 @@ function ensureHdrFilters(ffmpegPath) {
102769
102871
  if (hdrFilterCheck?.ffmpegPath === ffmpegPath) return hdrFilterCheck.promise;
102770
102872
  const promise = new Promise((resolveCheck, rejectCheck) => {
102771
102873
  const proc = spawn8(ffmpegPath, ["-hide_banner", "-filters"], {
102772
- stdio: ["ignore", "pipe", "pipe"]
102874
+ stdio: ["ignore", "pipe", "pipe"],
102875
+ windowsHide: true
102773
102876
  });
102774
102877
  let stdout2 = "";
102775
102878
  proc.stdout?.on("data", (chunk) => {
@@ -102868,7 +102971,8 @@ async function runFfmpeg2(sourcePath, outputPath, variant) {
102868
102971
  const proc = spawn8(ffmpegPath, args, {
102869
102972
  stdio: ["ignore", "ignore", "pipe"],
102870
102973
  timeout: TRANSCODE_TIMEOUT_MS,
102871
- killSignal: "SIGKILL"
102974
+ killSignal: "SIGKILL",
102975
+ windowsHide: true
102872
102976
  });
102873
102977
  let stderrTail = "";
102874
102978
  proc.stderr?.on("data", (chunk) => {
@@ -102940,10 +103044,10 @@ async function resolveProxy(projectDir, absoluteSourcePath, variant = "h264") {
102940
103044
  return promise;
102941
103045
  }
102942
103046
  var DEFAULT_MAX_BYTES, DEFAULT_STALE_TEMP_MS, DEFAULT_MIN_SWEEP_INTERVAL_MS, PROXY_EXTENSIONS, lastSweepAt, PROXY_PARAMS_VERSION, CACHE_DIR_NAME, MAX_CONCURRENT_TRANSCODES, MAX_QUEUED_TRANSCODES, STDERR_TAIL_MAX_CHARS, TRANSCODE_TIMEOUT_MS, FAILURE_CACHE_TTL_MS, MAX_FAILURE_CACHE_ENTRIES, DEFAULT_PROXY_WAIT_TIMEOUT_MS, ProxyTranscodeError, FfmpegUnavailableError, FfmpegMissingFilterError, ProxyCapacityError, ProxySourceOutsideProjectError, ProxyWaitTimeoutError, activeTranscodes, waitQueue, inFlight, failedTranscodes, hdrFilterCheck;
102943
- var init_chunk_ZPI6QXJH = __esm({
102944
- "../studio-server/dist/chunk-ZPI6QXJH.js"() {
103047
+ var init_chunk_5MUVDQOD = __esm({
103048
+ "../studio-server/dist/chunk-5MUVDQOD.js"() {
102945
103049
  "use strict";
102946
- init_chunk_6H3V3WGJ();
103050
+ init_chunk_NJISQQTN();
102947
103051
  init_ffBinaries();
102948
103052
  DEFAULT_MAX_BYTES = 10 * 1024 * 1024 * 1024;
102949
103053
  DEFAULT_STALE_TEMP_MS = 60 * 60 * 1e3;
@@ -103011,7 +103115,7 @@ var init_chunk_ZPI6QXJH = __esm({
103011
103115
  }
103012
103116
  });
103013
103117
 
103014
- // ../studio-server/dist/chunk-LHYV3WLZ.js
103118
+ // ../studio-server/dist/chunk-KW5URJHI.js
103015
103119
  import { resolve as resolve20 } from "path";
103016
103120
  function isAutoProxyEnabled(adapter2) {
103017
103121
  return adapter2.autoProxy !== false;
@@ -103058,11 +103162,11 @@ async function injectMediaCodecMap(html, adapter2, projectDir, compSrcPath, prob
103058
103162
  if (!isAutoProxyEnabled(adapter2)) return html;
103059
103163
  return injectMediaCodecMapIntoHtml(html, projectDir, [{ html, compSrcPath }], probeCache);
103060
103164
  }
103061
- var init_chunk_LHYV3WLZ = __esm({
103062
- "../studio-server/dist/chunk-LHYV3WLZ.js"() {
103165
+ var init_chunk_KW5URJHI = __esm({
103166
+ "../studio-server/dist/chunk-KW5URJHI.js"() {
103063
103167
  "use strict";
103064
- init_chunk_ZPI6QXJH();
103065
- init_chunk_6H3V3WGJ();
103168
+ init_chunk_5MUVDQOD();
103169
+ init_chunk_NJISQQTN();
103066
103170
  }
103067
103171
  });
103068
103172
 
@@ -110146,7 +110250,7 @@ function decodeAudioPeaks(audioPath) {
110146
110250
  "-vn",
110147
110251
  "pipe:1"
110148
110252
  ],
110149
- { stdio: ["ignore", "pipe", "ignore"] }
110253
+ { stdio: ["ignore", "pipe", "ignore"], windowsHide: true }
110150
110254
  );
110151
110255
  const chunks = [];
110152
110256
  proc.stdout?.on("data", (chunk) => chunks.push(chunk));
@@ -110184,16 +110288,11 @@ function validateUploadedMedia(filePath, runner = spawnSync) {
110184
110288
  if (!isVideo2 && !isAudio) {
110185
110289
  return { ok: true };
110186
110290
  }
110187
- const result = runner("ffprobe", [
110188
- "-v",
110189
- "error",
110190
- "-show_entries",
110191
- "stream=codec_type",
110192
- "-of",
110193
- "json",
110194
- "--",
110195
- filePath
110196
- ]);
110291
+ const result = runner(
110292
+ "ffprobe",
110293
+ ["-v", "error", "-show_entries", "stream=codec_type", "-of", "json", "--", filePath],
110294
+ { windowsHide: true }
110295
+ );
110197
110296
  if (result.error?.code === "ENOENT") {
110198
110297
  return { ok: true };
110199
110298
  }
@@ -114170,9 +114269,9 @@ var init_dist9 = __esm({
114170
114269
  init_chunk_X62ASOGO();
114171
114270
  init_chunk_XVPRRS7M();
114172
114271
  init_chunk_W2SBTCO2();
114173
- init_chunk_LHYV3WLZ();
114174
- init_chunk_ZPI6QXJH();
114175
- init_chunk_6H3V3WGJ();
114272
+ init_chunk_KW5URJHI();
114273
+ init_chunk_5MUVDQOD();
114274
+ init_chunk_NJISQQTN();
114176
114275
  init_chunk_6XMC64FJ();
114177
114276
  init_chunk_4ETS2LXI();
114178
114277
  init_dist3();
@@ -115385,7 +115484,8 @@ function whichBinary2(name) {
115385
115484
  const output = execSync4(cmd, {
115386
115485
  encoding: "utf-8",
115387
115486
  stdio: ["pipe", "pipe", "pipe"],
115388
- timeout: 5e3
115487
+ timeout: 5e3,
115488
+ windowsHide: true
115389
115489
  });
115390
115490
  const first = output.split(/\r?\n/).map((s2) => s2.trim()).find(Boolean);
115391
115491
  return first || void 0;
@@ -115981,7 +116081,7 @@ async function captureRegionCrop(page, region, scale) {
115981
116081
  }
115982
116082
  async function runFfmpegOnce(ffmpegPath, args, timeoutMs) {
115983
116083
  return await new Promise((resolvePromise) => {
115984
- const ff = spawn10(ffmpegPath, args);
116084
+ const ff = spawn10(ffmpegPath, args, { windowsHide: true });
115985
116085
  let stderr = "";
115986
116086
  let timedOut = false;
115987
116087
  const timer = setTimeout(() => {
@@ -117134,6 +117234,31 @@ var init_renderEventPublisher = __esm({
117134
117234
  }
117135
117235
  });
117136
117236
 
117237
+ // ../producer/src/services/render/encoderInterruption.ts
117238
+ function encoderFailureError(prefix, result) {
117239
+ const message = `${prefix}: ${result.error ?? "unknown encoder failure"}`;
117240
+ return result.failureReason === "external_interruption" ? new EncoderInterruptedError(prefix, message) : new Error(message);
117241
+ }
117242
+ var ENCODER_INTERRUPTED, EncoderInterruptedError;
117243
+ var init_encoderInterruption = __esm({
117244
+ "../producer/src/services/render/encoderInterruption.ts"() {
117245
+ "use strict";
117246
+ ENCODER_INTERRUPTED = "ENCODER_INTERRUPTED";
117247
+ EncoderInterruptedError = class extends Error {
117248
+ code = ENCODER_INTERRUPTED;
117249
+ owner = "system";
117250
+ retryable = true;
117251
+ /** Retained inside the producer for diagnostics; never copied to the wire envelope. */
117252
+ diagnosticMessage;
117253
+ constructor(prefix, diagnosticMessage) {
117254
+ super(`${prefix}: encoder process was interrupted by the host lifecycle`);
117255
+ this.name = "EncoderInterruptedError";
117256
+ this.diagnosticMessage = diagnosticMessage;
117257
+ }
117258
+ };
117259
+ }
117260
+ });
117261
+
117137
117262
  // ../producer/src/services/render/renderExecutionContext.ts
117138
117263
  function scopedLogger(logger, fields) {
117139
117264
  const merge = (meta) => ({ ...fields, ...meta });
@@ -117864,7 +117989,7 @@ var init_perfSummary = __esm({
117864
117989
 
117865
117990
  // ../producer/src/services/render/captureStageError.ts
117866
117991
  function wrapCaptureStageError(error, browserConsole) {
117867
- if (error instanceof CaptureStageError) return error;
117992
+ if (error instanceof CaptureStageError || error instanceof EncoderInterruptedError) return error;
117868
117993
  return new CaptureStageError({ cause: error, browserConsole });
117869
117994
  }
117870
117995
  function getCaptureStageBrowserConsole(error) {
@@ -117877,6 +118002,7 @@ var init_captureStageError = __esm({
117877
118002
  "use strict";
117878
118003
  init_errorMessage2();
117879
118004
  init_src();
118005
+ init_encoderInterruption();
117880
118006
  CaptureStageError = class extends CaptureFailure {
117881
118007
  browserConsole;
117882
118008
  constructor(input2) {
@@ -122994,6 +123120,12 @@ async function runAnimatedGifTranscode(request) {
122994
123120
  const timeout = request.timeoutMs ?? 3e5;
122995
123121
  const result = await runFfmpeg(request.args, { timeout });
122996
123122
  if (result.success) return;
123123
+ if (result.failureReason === "external_interruption") {
123124
+ throw encoderFailureError("Animated GIF transcode failed", {
123125
+ error: `exit ${result.exitCode}: ${result.stderr.slice(-500)}`,
123126
+ failureReason: result.failureReason
123127
+ });
123128
+ }
122997
123129
  if (result.terminationReason === "deadline") {
122998
123130
  throw new Error(`Animated GIF transcode timed out after ${timeout}ms`);
122999
123131
  }
@@ -123149,6 +123281,7 @@ var init_animatedGifPrep = __esm({
123149
123281
  init_dist3();
123150
123282
  init_src();
123151
123283
  init_urlDownloader2();
123284
+ init_encoderInterruption();
123152
123285
  PREPARED_GIF_SUBDIR = "_animated_gif";
123153
123286
  CACHE_SCHEMA = "hfgif-v1";
123154
123287
  MAX_LOOP_ITERATIONS = 1e3;
@@ -125620,9 +125753,18 @@ function resolveVideoExtractionPolicy(env = process.env) {
125620
125753
  return { failureMode, maxTransientRetries };
125621
125754
  }
125622
125755
  function assertVideoExtractionSucceeded(result) {
125756
+ throwIfEncoderInterrupted(result);
125623
125757
  const error = buildVideoExtractionStageError(result);
125624
125758
  if (error) throw error;
125625
125759
  }
125760
+ function throwIfEncoderInterrupted(result) {
125761
+ const interrupted = result.errors.find((failure) => failure.kind === "external_interruption");
125762
+ if (!interrupted) return;
125763
+ throw encoderFailureError("Video frame extraction failed", {
125764
+ error: String(interrupted.error),
125765
+ failureReason: "external_interruption"
125766
+ });
125767
+ }
125626
125768
  function buildVideoExtractionStageError(result) {
125627
125769
  if (result.success && result.errors.length === 0) return null;
125628
125770
  const counts = /* @__PURE__ */ new Map();
@@ -125660,6 +125802,15 @@ function isHdrProbeFailure(failure) {
125660
125802
  }
125661
125803
  function throwHdrProbeFailures(failures, mode) {
125662
125804
  if (failures.length === 0) return;
125805
+ const interrupted = failures.find(
125806
+ (failure) => failure.classified.kind === "external_interruption"
125807
+ );
125808
+ if (interrupted) {
125809
+ throw encoderFailureError("Video HDR probe failed", {
125810
+ error: interrupted.error instanceof Error ? interrupted.error.message : String(interrupted.error),
125811
+ failureReason: "external_interruption"
125812
+ });
125813
+ }
125663
125814
  if (mode === "enforce") {
125664
125815
  throw buildHdrProbeStageError(failures.map((failure) => failure.classified));
125665
125816
  }
@@ -125792,6 +125943,7 @@ async function runExtractVideosStage(input2) {
125792
125943
  );
125793
125944
  extractionResult.phaseBreakdown.transientRetries = (extractionResult.phaseBreakdown.transientRetries ?? 0) + hdrProbeTransientRetries;
125794
125945
  assertNotAborted();
125946
+ throwIfEncoderInterrupted(extractionResult);
125795
125947
  failureToEnforce = applyVideoExtractionFailurePolicy(extractionResult, extractionPolicy, log2);
125796
125948
  materializeExtractedFramesForCompiledDir(extractionResult.extracted, compiledDir, {
125797
125949
  materializeSymlinks
@@ -125848,6 +126000,7 @@ var init_extractVideosStage = __esm({
125848
126000
  init_src();
125849
126001
  init_renderOrchestrator();
125850
126002
  init_shared();
126003
+ init_encoderInterruption();
125851
126004
  VideoExtractionStageError = class extends Error {
125852
126005
  constructor(code, retryable, failures) {
125853
126006
  const total = failures.reduce((sum, failure) => sum + failure.count, 0);
@@ -125925,6 +126078,15 @@ async function runAudioStage(input2) {
125925
126078
  };
125926
126079
  }
125927
126080
  assertNotAborted();
126081
+ const interrupted = audioResult.failures?.find(
126082
+ (failure) => failure.reason === "external_interruption"
126083
+ );
126084
+ if (interrupted) {
126085
+ throw encoderFailureError("Audio processing failed", {
126086
+ error: interrupted.detail,
126087
+ failureReason: "external_interruption"
126088
+ });
126089
+ }
125928
126090
  hasAudio = audioResult.success;
125929
126091
  audioFailures = audioResult.failures;
125930
126092
  if (!hasAudio) {
@@ -125954,6 +126116,7 @@ var init_audioStage = __esm({
125954
126116
  "../producer/src/services/render/stages/audioStage.ts"() {
125955
126117
  "use strict";
125956
126118
  init_src();
126119
+ init_encoderInterruption();
125957
126120
  }
125958
126121
  });
125959
126122
 
@@ -126874,9 +127037,10 @@ async function extractHdrVideoFrames(args) {
126874
127037
  srcPath,
126875
127038
  stderr: result.stderr.slice(-400)
126876
127039
  });
126877
- throw new Error(
126878
- `HDR frame extraction failed for video "${videoId}". Aborting render to avoid shipping black HDR layers.`
126879
- );
127040
+ throw encoderFailureError(`HDR frame extraction failed for video "${videoId}"`, {
127041
+ error: `Aborting render to avoid shipping black HDR layers: ${result.stderr.slice(-400)}`,
127042
+ failureReason: result.failureReason
127043
+ });
126880
127044
  }
126881
127045
  const frameSize = dims.width * dims.height * 6;
126882
127046
  const fd = openSync6(rawPath, constants3.O_RDONLY | NO_FOLLOW_FLAG);
@@ -126966,6 +127130,7 @@ var init_captureHdrResources = __esm({
126966
127130
  init_src();
126967
127131
  init_dist3();
126968
127132
  init_hdrCompositor();
127133
+ init_encoderInterruption();
126969
127134
  NO_FOLLOW_FLAG = constants3.O_NOFOLLOW ?? 0;
126970
127135
  HDR_EXTRACTION_HEADROOM_FRACTION = 0.9;
126971
127136
  HDR_EXTRACTION_CGROUP_BUDGET_FRACTION = 0.5;
@@ -127181,7 +127346,10 @@ function ensureFrameWritten(frameWritten, frameIndex, encoder) {
127181
127346
  if (frameWritten) return;
127182
127347
  const reason = encoder?.getExitError();
127183
127348
  const base2 = `Streaming encoder exited before frame ${frameIndex} was written`;
127184
- throw new Error(reason ? `${base2}: ${reason}` : base2);
127349
+ throw encoderFailureError(base2, {
127350
+ error: reason,
127351
+ failureReason: encoder?.getExitFailureReason?.()
127352
+ });
127185
127353
  }
127186
127354
  function cleanupEndedHdrVideos(args) {
127187
127355
  if (process.env.KEEP_TEMP === "1") return;
@@ -127215,6 +127383,7 @@ var init_captureHdrFrameShared = __esm({
127215
127383
  init_hdrCompositor();
127216
127384
  init_captureHdrResources();
127217
127385
  init_hdrPerf();
127386
+ init_encoderInterruption();
127218
127387
  }
127219
127388
  });
127220
127389
 
@@ -127696,7 +127865,7 @@ async function runCaptureStreamingStage(input2) {
127696
127865
  streamingEncoderClosed = true;
127697
127866
  assertNotAborted();
127698
127867
  if (!encodeResult.success) {
127699
- throw new Error(`Streaming encode failed: ${encodeResult.error}`);
127868
+ throw encoderFailureError("Streaming encode failed", encodeResult);
127700
127869
  }
127701
127870
  return {
127702
127871
  success: true,
@@ -127728,6 +127897,7 @@ var init_captureStreamingStage = __esm({
127728
127897
  init_perfSummary();
127729
127898
  init_captureHdrFrameShared();
127730
127899
  init_shared();
127900
+ init_encoderInterruption();
127731
127901
  DEFAULT_DE_STALL_MS = 6e4;
127732
127902
  DE_STALL_POLL_MS = 5e3;
127733
127903
  }
@@ -128669,7 +128839,7 @@ async function runCaptureHdrStage(input2) {
128669
128839
  hdrEncoderClosed = true;
128670
128840
  assertNotAborted();
128671
128841
  if (!hdrEncodeResult.success) {
128672
- throw new Error(`HDR encode failed: ${hdrEncodeResult.error}`);
128842
+ throw encoderFailureError("HDR encode failed", hdrEncodeResult);
128673
128843
  }
128674
128844
  captureDurationMs = Date.now() - stageStart;
128675
128845
  encodeMs = hdrEncodeResult.durationMs;
@@ -128721,6 +128891,7 @@ var init_captureHdrStage = __esm({
128721
128891
  init_captureHdrSequentialLoop();
128722
128892
  init_captureHdrHybridLoop();
128723
128893
  init_captureStageError();
128894
+ init_encoderInterruption();
128724
128895
  }
128725
128896
  });
128726
128897
 
@@ -128812,7 +128983,8 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input2) {
128812
128983
  durationMs: Date.now() - startTime,
128813
128984
  framesEncoded: 0,
128814
128985
  fileSize: 0,
128815
- error: formatFfmpegError(paletteResult.exitCode, paletteResult.stderr)
128986
+ error: formatFfmpegError(paletteResult.exitCode, paletteResult.stderr),
128987
+ failureReason: paletteResult.failureReason
128816
128988
  };
128817
128989
  }
128818
128990
  const gifResult = await runFfmpeg(buildGifPaletteuseArgs(argsInput), {
@@ -128826,7 +128998,8 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input2) {
128826
128998
  durationMs: Date.now() - startTime,
128827
128999
  framesEncoded: 0,
128828
129000
  fileSize: 0,
128829
- error: formatFfmpegError(gifResult.exitCode, gifResult.stderr)
129001
+ error: formatFfmpegError(gifResult.exitCode, gifResult.stderr),
129002
+ failureReason: gifResult.failureReason
128830
129003
  };
128831
129004
  }
128832
129005
  const fileSize = existsSync55(outputPath) ? statSync16(outputPath).size : 0;
@@ -128905,7 +129078,7 @@ async function runEncodeStage(input2) {
128905
129078
  });
128906
129079
  assertNotAborted();
128907
129080
  if (!encodeResult2.success) {
128908
- throw new Error(`Encoding failed: ${encodeResult2.error}`);
129081
+ throw encoderFailureError("Encoding failed", encodeResult2);
128909
129082
  }
128910
129083
  return { encodeMs: Date.now() - stage5Start };
128911
129084
  }
@@ -128950,7 +129123,7 @@ async function runEncodeStage(input2) {
128950
129123
  );
128951
129124
  assertNotAborted();
128952
129125
  if (!encodeResult.success) {
128953
- throw new Error(`Encoding failed: ${encodeResult.error}`);
129126
+ throw encoderFailureError("Encoding failed", encodeResult);
128954
129127
  }
128955
129128
  return { encodeMs: Date.now() - stage5Start };
128956
129129
  }
@@ -128961,6 +129134,7 @@ var init_encodeStage = __esm({
128961
129134
  init_paths();
128962
129135
  init_gifEncodeArgs();
128963
129136
  init_shared();
129137
+ init_encoderInterruption();
128964
129138
  }
128965
129139
  });
128966
129140
 
@@ -129095,7 +129269,8 @@ async function padOrTrimAudioToVideoFrameCount(input2) {
129095
129269
  targetDurationSeconds,
129096
129270
  sourceDurationSeconds: audioInfo.durationSeconds,
129097
129271
  operation: plan2.operation,
129098
- error: ffmpegResult.error
129272
+ error: ffmpegResult.error,
129273
+ failureReason: ffmpegResult.failureReason
129099
129274
  };
129100
129275
  }
129101
129276
  }
@@ -129197,7 +129372,8 @@ async function defaultRunFfmpeg(args, signal) {
129197
129372
  if (result.success) return { success: true };
129198
129373
  return {
129199
129374
  success: false,
129200
- error: `[audioPadTrim] ${formatFfmpegError(result.exitCode, result.stderr)}`
129375
+ error: `[audioPadTrim] ${formatFfmpegError(result.exitCode, result.stderr)}`,
129376
+ failureReason: result.failureReason
129201
129377
  };
129202
129378
  }
129203
129379
  async function runFfprobeJson(args, signal) {
@@ -129264,11 +129440,12 @@ async function runAssembleStage(input2) {
129264
129440
  const normalizeResult = await padOrTrimAudioToVideoFrameCount({
129265
129441
  videoPath: videoOnlyPath,
129266
129442
  audioPath: audioOutputPath,
129267
- outputPath: normalizedAudioPath
129443
+ outputPath: normalizedAudioPath,
129444
+ signal: abortSignal
129268
129445
  });
129269
129446
  assertNotAborted();
129270
129447
  if (!normalizeResult.success) {
129271
- throw new Error(`Audio duration normalization failed: ${normalizeResult.error}`);
129448
+ throw encoderFailureError("Audio duration normalization failed", normalizeResult);
129272
129449
  }
129273
129450
  const muxResult = await muxVideoWithAudio(
129274
129451
  videoOnlyPath,
@@ -129282,7 +129459,7 @@ async function runAssembleStage(input2) {
129282
129459
  );
129283
129460
  assertNotAborted();
129284
129461
  if (!muxResult.success) {
129285
- throw new Error(`Audio muxing failed: ${muxResult.error}`);
129462
+ throw encoderFailureError("Audio muxing failed", muxResult);
129286
129463
  }
129287
129464
  } else {
129288
129465
  const faststartResult = await applyFaststart(
@@ -129294,7 +129471,7 @@ async function runAssembleStage(input2) {
129294
129471
  );
129295
129472
  assertNotAborted();
129296
129473
  if (!faststartResult.success) {
129297
- throw new Error(`Faststart failed: ${faststartResult.error}`);
129474
+ throw encoderFailureError("Faststart failed", faststartResult);
129298
129475
  }
129299
129476
  }
129300
129477
  return { assembleMs: Date.now() - stage6Start };
@@ -129304,6 +129481,7 @@ var init_assembleStage = __esm({
129304
129481
  "use strict";
129305
129482
  init_src();
129306
129483
  init_audioPadTrim();
129484
+ init_encoderInterruption();
129307
129485
  init_shared();
129308
129486
  }
129309
129487
  });
@@ -129855,7 +130033,7 @@ function resolveParallelRouterRetryPlan(args) {
129855
130033
  };
129856
130034
  }
129857
130035
  function shouldRetryViaPinnedFallback(args) {
129858
- if (args.isCancellation) return false;
130036
+ if (args.isCancellation || args.isEncoderInterrupted) return false;
129859
130037
  if (args.isVerifyError) return true;
129860
130038
  return args.deWorkerInversion === "inverted" || args.deParallelRouter === "routed";
129861
130039
  }
@@ -130936,6 +131114,7 @@ async function executeRenderPipeline(input2) {
130936
131114
  if (!shouldRetryViaPinnedFallback({
130937
131115
  isVerifyError,
130938
131116
  isCancellation,
131117
+ isEncoderInterrupted: err instanceof EncoderInterruptedError,
130939
131118
  deWorkerInversion,
130940
131119
  deParallelRouter
130941
131120
  }))
@@ -131287,6 +131466,12 @@ async function executeRenderPipeline(input2) {
131287
131466
  await eventPublisher.flush();
131288
131467
  throw error instanceof RenderCancelledError ? error : new RenderCancelledError("render_cancelled");
131289
131468
  }
131469
+ if (error instanceof EncoderInterruptedError) {
131470
+ log2.warn("[Render] encoder process interrupted by host lifecycle", {
131471
+ code: error.code,
131472
+ diagnostic: error.diagnosticMessage.slice(-2e3)
131473
+ });
131474
+ }
131290
131475
  const memoryGuidance = describeMemoryExhaustion(error, {
131291
131476
  width: captureCompositionWidth,
131292
131477
  height: captureCompositionHeight,
@@ -131377,6 +131562,7 @@ var init_renderOrchestrator = __esm({
131377
131562
  init_shared();
131378
131563
  init_cleanup();
131379
131564
  init_renderEventPublisher();
131565
+ init_encoderInterruption();
131380
131566
  init_renderExecutionContext();
131381
131567
  init_artifactTransaction();
131382
131568
  init_capturePlan();
@@ -132760,7 +132946,8 @@ var init_server = __esm({
132760
132946
  "NOT_MEDIA_PAYLOAD",
132761
132947
  "INVALID_VIDEO_METADATA",
132762
132948
  "VIDEO_SOURCE_UNRENDERABLE",
132763
- "VIDEO_EXTRACTION_FAILED"
132949
+ "VIDEO_EXTRACTION_FAILED",
132950
+ "ENCODER_INTERRUPTED"
132764
132951
  ]);
132765
132952
  entryScript = process.argv[1] ? resolve35(process.argv[1]) : "";
132766
132953
  isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
@@ -134958,9 +135145,10 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
134958
135145
  remuxArgs.push("-y", concatOutputPath);
134959
135146
  const remuxResult = await runFfmpeg(remuxArgs, { signal: abortSignal });
134960
135147
  if (!remuxResult.success) {
134961
- throw new Error(
134962
- `[assemble] ffmpeg single-chunk remux failed (exit ${remuxResult.exitCode}): ${remuxResult.stderr.slice(-400)}`
134963
- );
135148
+ throw encoderFailureError("[assemble] ffmpeg single-chunk remux failed", {
135149
+ error: `exit ${remuxResult.exitCode}: ${remuxResult.stderr.slice(-400)}`,
135150
+ failureReason: remuxResult.failureReason
135151
+ });
134964
135152
  }
134965
135153
  } else {
134966
135154
  const concatListPath = join75(workDir, "concat-list.txt");
@@ -134983,9 +135171,10 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
134983
135171
  concatArgs.push("-y", concatOutputPath);
134984
135172
  const concatResult = await runFfmpeg(concatArgs, { signal: abortSignal });
134985
135173
  if (!concatResult.success) {
134986
- throw new Error(
134987
- `[assemble] ffmpeg concat-copy failed (exit ${concatResult.exitCode}): ${concatResult.stderr.slice(-400)}`
134988
- );
135174
+ throw encoderFailureError("[assemble] ffmpeg concat-copy failed", {
135175
+ error: `exit ${concatResult.exitCode}: ${concatResult.stderr.slice(-400)}`,
135176
+ failureReason: concatResult.failureReason
135177
+ });
134989
135178
  }
134990
135179
  }
134991
135180
  let postConcatPath = concatOutputPath;
@@ -135026,9 +135215,10 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
135026
135215
  cfrArgs.push("-y", cfrOutputPath);
135027
135216
  const cfrResult = await runFfmpeg(cfrArgs, { signal: abortSignal });
135028
135217
  if (!cfrResult.success) {
135029
- throw new Error(
135030
- `[assemble] ffmpeg cfr re-encode failed (exit ${cfrResult.exitCode}): ${cfrResult.stderr.slice(-400)}`
135031
- );
135218
+ throw encoderFailureError("[assemble] ffmpeg cfr re-encode failed", {
135219
+ error: `exit ${cfrResult.exitCode}: ${cfrResult.stderr.slice(-400)}`,
135220
+ failureReason: cfrResult.failureReason
135221
+ });
135032
135222
  }
135033
135223
  postConcatPath = cfrOutputPath;
135034
135224
  log2.info("[assemble] cfr re-encode applied", {
@@ -135047,7 +135237,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
135047
135237
  signal: abortSignal
135048
135238
  });
135049
135239
  if (!padTrimResult.success) {
135050
- throw new Error(`[assemble] audio pad/trim failed: ${padTrimResult.error}`);
135240
+ throw encoderFailureError("[assemble] audio pad/trim failed", padTrimResult);
135051
135241
  }
135052
135242
  normalizedAudioPath = paddedAudioPath;
135053
135243
  log2.info("[assemble] audio normalized for mux", {
@@ -135069,7 +135259,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
135069
135259
  { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen }
135070
135260
  );
135071
135261
  if (!muxResult.success) {
135072
- throw new Error(`[assemble] audio mux failed: ${muxResult.error}`);
135262
+ throw encoderFailureError("[assemble] audio mux failed", muxResult);
135073
135263
  }
135074
135264
  }
135075
135265
  const faststartResult = await applyFaststart(
@@ -135083,7 +135273,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
135083
135273
  }
135084
135274
  );
135085
135275
  if (!faststartResult.success) {
135086
- throw new Error(`[assemble] faststart failed: ${faststartResult.error}`);
135276
+ throw encoderFailureError("[assemble] faststart failed", faststartResult);
135087
135277
  }
135088
135278
  } finally {
135089
135279
  try {
@@ -135154,6 +135344,7 @@ var init_assemble = __esm({
135154
135344
  init_logger();
135155
135345
  init_paths();
135156
135346
  init_audioPadTrim();
135347
+ init_encoderInterruption();
135157
135348
  init_planProtocol();
135158
135349
  init_planV2();
135159
135350
  }
@@ -136367,7 +136558,7 @@ async function render2(options) {
136367
136558
  }
136368
136559
  }
136369
136560
  function spawnFfmpeg(ffmpegPath, args, label2, stdio) {
136370
- const proc = spawn12(ffmpegPath, args, { stdio });
136561
+ const proc = spawn12(ffmpegPath, args, { stdio, windowsHide: true });
136371
136562
  let stderrBuf = "";
136372
136563
  proc.stderr?.on("data", (d2) => {
136373
136564
  stderrBuf += d2.toString();
@@ -136513,7 +136704,11 @@ function configuredMissingDetail(envName) {
136513
136704
  }
136514
136705
  function readToolVersion(binaryPath) {
136515
136706
  try {
136516
- const raw = execFileSync9(binaryPath, ["-version"], { encoding: "utf-8", timeout: 5e3 }).split("\n")[0] ?? "";
136707
+ const raw = execFileSync9(binaryPath, ["-version"], {
136708
+ encoding: "utf-8",
136709
+ timeout: 5e3,
136710
+ windowsHide: true
136711
+ }).split("\n")[0] ?? "";
136517
136712
  const version2 = parseToolVersion(raw);
136518
136713
  return { ok: true, detail: version2 ? `${version2} at ${binaryPath}` : binaryPath };
136519
136714
  } catch (error) {
@@ -140985,9 +141180,9 @@ var init_catalog = __esm({
140985
141180
  var init_mediaProxyPreview = __esm({
140986
141181
  "../studio-server/dist/helpers/mediaProxyPreview.js"() {
140987
141182
  "use strict";
140988
- init_chunk_LHYV3WLZ();
140989
- init_chunk_ZPI6QXJH();
140990
- init_chunk_6H3V3WGJ();
141183
+ init_chunk_KW5URJHI();
141184
+ init_chunk_5MUVDQOD();
141185
+ init_chunk_NJISQQTN();
140991
141186
  }
140992
141187
  });
140993
141188
 
@@ -141105,8 +141300,8 @@ var init_compositionServer = __esm({
141105
141300
  var init_proxyTranscoder = __esm({
141106
141301
  "../studio-server/dist/helpers/proxyTranscoder.js"() {
141107
141302
  "use strict";
141108
- init_chunk_ZPI6QXJH();
141109
- init_chunk_6H3V3WGJ();
141303
+ init_chunk_5MUVDQOD();
141304
+ init_chunk_NJISQQTN();
141110
141305
  }
141111
141306
  });
141112
141307
 
@@ -141114,7 +141309,7 @@ var init_proxyTranscoder = __esm({
141114
141309
  var init_mediaCodecMap = __esm({
141115
141310
  "../studio-server/dist/helpers/mediaCodecMap.js"() {
141116
141311
  "use strict";
141117
- init_chunk_6H3V3WGJ();
141312
+ init_chunk_NJISQQTN();
141118
141313
  }
141119
141314
  });
141120
141315
 
@@ -145050,7 +145245,7 @@ function probeWebmAlpha(filePath) {
145050
145245
  "--",
145051
145246
  filePath
145052
145247
  ],
145053
- { encoding: "utf-8", timeout: 15e3 }
145248
+ { encoding: "utf-8", timeout: 15e3, windowsHide: true }
145054
145249
  );
145055
145250
  const parsed = JSON.parse(raw);
145056
145251
  const stream = parsed.streams?.[0];
@@ -145094,7 +145289,12 @@ function sampledAlphaIsFullyOpaque(filePath) {
145094
145289
  "rawvideo",
145095
145290
  "-"
145096
145291
  ],
145097
- { timeout: 3e4, maxBuffer: 4096, stdio: ["ignore", "pipe", "pipe"] }
145292
+ {
145293
+ timeout: 3e4,
145294
+ maxBuffer: 4096,
145295
+ stdio: ["ignore", "pipe", "pipe"],
145296
+ windowsHide: true
145297
+ }
145098
145298
  );
145099
145299
  if (buf.length === 0 || buf.length > MAX_SAMPLE_BYTES || buf.length % BYTES_PER_SAMPLE_FRAME !== 0) {
145100
145300
  return void 0;
@@ -149424,18 +149624,22 @@ function geometryIssueAnchor(candidate, time) {
149424
149624
  rect: candidate.rect
149425
149625
  };
149426
149626
  }
149427
- function captionCenterInZone(rect, zone, canvas) {
149428
- const cx = rect.left + rect.width / 2;
149429
- const cy = rect.top + rect.height / 2;
149430
- const inside = cx >= zone.x0 * canvas.width && cx <= zone.x1 * canvas.width && cy >= zone.y0 * canvas.height && cy <= zone.y1 * canvas.height;
149431
- return { inside, cy };
149627
+ function captionBoxOverlapsZone(box, zone, canvas) {
149628
+ const zx0 = zone.x0 * canvas.width;
149629
+ const zy0 = zone.y0 * canvas.height;
149630
+ const zx1 = zone.x1 * canvas.width;
149631
+ const zy1 = zone.y1 * canvas.height;
149632
+ const bx1 = box.left + box.width;
149633
+ const by1 = box.top + box.height;
149634
+ const overlaps = box.left < zx1 && bx1 > zx0 && box.top < zy1 && by1 > zy0;
149635
+ return { overlaps, cy: box.top + box.height / 2 };
149432
149636
  }
149433
149637
  function captionFinding(candidate, options, canvas, time) {
149434
149638
  const zone = options.captionZone;
149435
149639
  if (!zone || candidate.kind !== "text" || !candidateIsSized(candidate, canvas)) return null;
149436
149640
  if ("data-layout-allow-caption-zone" in candidate.dataAttributes) return null;
149437
- const { inside, cy } = captionCenterInZone(candidate.rect, zone, canvas);
149438
- if (!inside) return null;
149641
+ const { overlaps, cy } = captionBoxOverlapsZone(candidate.elementRect, zone, canvas);
149642
+ if (!overlaps) return null;
149439
149643
  const text2 = candidate.text.slice(0, 48);
149440
149644
  const pctFromBottom = Math.round((canvas.height - cy) / canvas.height * 100);
149441
149645
  return {
@@ -149445,7 +149649,7 @@ function captionFinding(candidate, options, canvas, time) {
149445
149649
  code: "caption_zone_collision",
149446
149650
  severity: zone.severity === "error" ? "error" : "warning",
149447
149651
  text: text2,
149448
- message: `<${candidate.tag}> "${text2}" is centred in the reserved caption band (~${pctFromBottom}% up from the bottom).`,
149652
+ message: `<${candidate.tag}> "${text2}" overlaps the reserved caption band (~${pctFromBottom}% up from the bottom).`,
149449
149653
  fixHint: "Keep main content outside the configured caption band, or mark intentional lower-third copy with data-layout-allow-caption-zone."
149450
149654
  }
149451
149655
  };
@@ -203015,18 +203219,25 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203015
203219
  }
203016
203220
  const openRouterKey = process.env.OPENROUTER_API_KEY;
203017
203221
  const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
203018
- if (!openRouterKey && !geminiKey) {
203222
+ const vertexProject = process.env.HYPERFRAMES_VERTEX_PROJECT_ID;
203223
+ const vertexServiceAccount = process.env.HYPERFRAMES_VERTEX_SERVICE_ACCOUNT;
203224
+ const useVertex = Boolean(vertexProject && vertexServiceAccount);
203225
+ if (!openRouterKey && !useVertex && !geminiKey) {
203019
203226
  reportOutcome();
203020
203227
  return geminiCaptions;
203021
203228
  }
203022
- const useOpenRouter = Boolean(openRouterKey);
203023
- const providerName = useOpenRouter ? "OpenRouter" : "Gemini";
203024
- const model = useOpenRouter ? process.env.HYPERFRAMES_OPENROUTER_MODEL || "google/gemini-3.1-flash-lite" : process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
203229
+ const provider = openRouterKey ? "openrouter" : useVertex ? "vertex" : "gemini";
203230
+ const providerName = { openrouter: "OpenRouter", vertex: "Vertex AI", gemini: "Gemini" }[provider];
203231
+ const model = {
203232
+ openrouter: process.env.HYPERFRAMES_OPENROUTER_MODEL || "google/gemini-3.1-flash-lite",
203233
+ vertex: process.env.HYPERFRAMES_VERTEX_MODEL || "gemini-2.5-flash",
203234
+ gemini: process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview"
203235
+ }[provider];
203025
203236
  const requestTimeoutMs = resolveVisionRequestTimeoutMs();
203026
203237
  progress("design", `Captioning images with ${providerName} vision...`);
203027
203238
  try {
203028
203239
  let captionOne;
203029
- if (openRouterKey) {
203240
+ if (provider === "openrouter") {
203030
203241
  captionOne = async ({ mimeType, base64, prompt, maxTokens, timeoutMs }) => {
203031
203242
  return runBoundedVisionRequest(async (signal) => {
203032
203243
  const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
@@ -203062,9 +203273,31 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203062
203273
  }, timeoutMs);
203063
203274
  };
203064
203275
  } else {
203065
- if (!geminiKey) return geminiCaptions;
203066
203276
  const { GoogleGenAI: GoogleGenAI2 } = await Promise.resolve().then(() => (init_node4(), node_exports));
203067
- const ai = new GoogleGenAI2({ apiKey: geminiKey });
203277
+ let ai;
203278
+ if (provider === "vertex") {
203279
+ if (!vertexProject || !vertexServiceAccount) return geminiCaptions;
203280
+ let credentials;
203281
+ try {
203282
+ credentials = JSON.parse(vertexServiceAccount);
203283
+ } catch {
203284
+ warnings.push(
203285
+ "HYPERFRAMES_VERTEX_SERVICE_ACCOUNT is not valid JSON; skipped vision captioning."
203286
+ );
203287
+ internalError = true;
203288
+ reportOutcome();
203289
+ return geminiCaptions;
203290
+ }
203291
+ ai = new GoogleGenAI2({
203292
+ vertexai: true,
203293
+ project: vertexProject,
203294
+ location: process.env.HYPERFRAMES_VERTEX_LOCATION || "us-central1",
203295
+ googleAuthOptions: { credentials }
203296
+ });
203297
+ } else {
203298
+ if (!geminiKey) return geminiCaptions;
203299
+ ai = new GoogleGenAI2({ apiKey: geminiKey });
203300
+ }
203068
203301
  captionOne = async ({ mimeType, base64, prompt, maxTokens, timeoutMs }) => {
203069
203302
  const response = await runBoundedVisionRequest(
203070
203303
  (signal) => ai.models.generateContent({
@@ -203077,6 +203310,12 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203077
203310
  ],
203078
203311
  config: {
203079
203312
  maxOutputTokens: maxTokens,
203313
+ // A one-line factual caption needs no reasoning, and leaving thinking on is
203314
+ // not merely wasteful: thinking tokens are drawn from maxOutputTokens, so the
203315
+ // model can spend the whole budget and return empty text. That surfaces as a
203316
+ // successful request with no caption — the capture then logs "Captioned N/N
203317
+ // images" followed by "0 images captioned", which is what production shows.
203318
+ thinkingConfig: { thinkingBudget: 0 },
203080
203319
  abortSignal: signal,
203081
203320
  httpOptions: { timeout: timeoutMs }
203082
203321
  }
@@ -203168,6 +203407,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203168
203407
  reportOutcome();
203169
203408
  return geminiCaptions;
203170
203409
  }
203410
+ const hostConcurrency = sharp3.concurrency();
203171
203411
  progress("design", `Rasterizing + captioning ${svgFiles.length} SVGs via vision API...`);
203172
203412
  const SVG_BATCH = 20;
203173
203413
  const SVG_RENDER_SIZE = 256;
@@ -203180,10 +203420,11 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203180
203420
  }
203181
203421
  const timeoutMs = Math.max(1, Math.min(requestTimeoutMs, remainingMs));
203182
203422
  const batch = svgFiles.slice(i2, i2 + SVG_BATCH);
203183
- const results = await Promise.allSettled(
203184
- batch.map(async ({ relPath }) => {
203423
+ const rasterized = [];
203424
+ sharp3.concurrency(1);
203425
+ try {
203426
+ for (const { relPath } of batch) {
203185
203427
  const filePath = join121(assetsDir, relPath);
203186
- let pngBase64;
203187
203428
  try {
203188
203429
  const svgSource = readFileSync84(filePath, "utf-8");
203189
203430
  const lightFillHits = (svgSource.match(/fill\s*=\s*["'](#fff(fff)?|white|#[ef][ef][ef]|#[ef]{6})["']/gi) || []).length;
@@ -203195,11 +203436,18 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203195
203436
  fit: "inside",
203196
203437
  withoutEnlargement: false
203197
203438
  }).flatten({ background: bg }).png().toBuffer();
203198
- pngBase64 = pngBuffer.toString("base64");
203439
+ rasterized.push({ relPath, pngBase64: pngBuffer.toString("base64") });
203199
203440
  } catch {
203200
203441
  svgsSkipped++;
203201
- return { file: relPath, caption: "" };
203442
+ rasterized.push({ relPath, pngBase64: null });
203202
203443
  }
203444
+ }
203445
+ } finally {
203446
+ sharp3.concurrency(hostConcurrency);
203447
+ }
203448
+ const results = await Promise.allSettled(
203449
+ rasterized.map(async ({ relPath, pngBase64 }) => {
203450
+ if (pngBase64 === null) return { file: relPath, caption: "" };
203203
203451
  const caption = await captionOne({
203204
203452
  mimeType: "image/png",
203205
203453
  base64: pngBase64,
@@ -204651,8 +204899,8 @@ ${extracted.bodyHtml}
204651
204899
  try {
204652
204900
  const lines = generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCaptions);
204653
204901
  if (lines.length > 0) {
204654
- const hasVisionKey = !!(!skipVision && (process.env.OPENROUTER_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY));
204655
- const header = hasVisionKey ? "# Asset Descriptions\n\nOne line per file. Read this instead of opening every image individually.\n\nTo find a specific brand or icon, **grep this file for the brand name in the description text** (e.g. `grep -i 'autodesk' asset-descriptions.md`). The Gemini Vision captions identify what's actually in each file \u2014 that's the agent's selector.\n\nThe `logo-<hash>.svg` filename prefix is a cheap structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). It is NOT a content claim \u2014 many `logo-*` files are nav icons or decorative shapes. Trust the captions, not the filename prefix.\n\n" : "# Asset Descriptions\n\n\u26A0\uFE0F GEMINI_API_KEY not set \u2014 descriptions below are catalog-derived (alt text, headings, section context, filename) instead of Vision-generated. To get richer Vision descriptions on the next capture, set GEMINI_API_KEY (or GOOGLE_API_KEY) and re-run.\n\nThe `logo-<hash>.svg` filename prefix is a structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). To pick the actual brand logo without Vision, open the `logo-*` candidates in a previewer or rasterize them with `sharp` before referencing \u2014 composing a fake logo ships off-brand in the final video.\n\n";
204902
+ const hasVisionKey = !!(!skipVision && (process.env.OPENROUTER_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || process.env.HYPERFRAMES_VERTEX_PROJECT_ID && process.env.HYPERFRAMES_VERTEX_SERVICE_ACCOUNT));
204903
+ const header = hasVisionKey ? "# Asset Descriptions\n\nOne line per file. Read this instead of opening every image individually.\n\nTo find a specific brand or icon, **grep this file for the brand name in the description text** (e.g. `grep -i 'autodesk' asset-descriptions.md`). The Gemini Vision captions identify what's actually in each file \u2014 that's the agent's selector.\n\nThe `logo-<hash>.svg` filename prefix is a cheap structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). It is NOT a content claim \u2014 many `logo-*` files are nav icons or decorative shapes. Trust the captions, not the filename prefix.\n\n" : "# Asset Descriptions\n\n\u26A0\uFE0F No vision credentials \u2014 descriptions below are catalog-derived (alt text, headings, section context, filename) instead of Vision-generated. To get richer Vision descriptions on the next capture, set GEMINI_API_KEY (or GOOGLE_API_KEY), or HYPERFRAMES_VERTEX_PROJECT_ID plus HYPERFRAMES_VERTEX_SERVICE_ACCOUNT for Vertex service-account auth, and re-run.\n\nThe `logo-<hash>.svg` filename prefix is a structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). To pick the actual brand logo without Vision, open the `logo-*` candidates in a previewer or rasterize them with `sharp` before referencing \u2014 composing a fake logo ships off-brand in the final video.\n\n";
204656
204904
  writeFileSync56(
204657
204905
  join126(outputDir, "extracted", "asset-descriptions.md"),
204658
204906
  header + lines.map((l) => "- " + l).join("\n") + "\n",