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 +361 -113
- package/dist/fontLocalizeCli.js +191 -32
- package/dist/hyperframes-player.global.js +1 -1
- package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +1 -1
- package/dist/studio/assets/{hyperframes-player-CIXNA_bj.js → hyperframes-player-oZ6g4tHJ.js} +1 -1
- package/dist/studio/assets/{index-9VfE7bZe.js → index-DW-H7Y2b.js} +1 -1
- package/dist/studio/assets/{index-CgGY8dAv.js → index-Tiu74FPg.js} +3 -3
- package/dist/studio/assets/{index-44gfDvkh.js → index-UyvIa_cE.js} +1 -1
- package/dist/studio/index.html +1 -1
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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"
|
|
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.
|
|
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
|
-
|
|
100784
|
+
`$p = Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}' -ErrorAction SilentlyContinue; if ($p) { $p.CreationDate.ToFileTimeUtc() }`
|
|
100693
100785
|
],
|
|
100694
|
-
{
|
|
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
|
-
|
|
100818
|
+
`$p = Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}' -ErrorAction SilentlyContinue; if ($p) { $p.ParentProcessId }`
|
|
100722
100819
|
],
|
|
100723
|
-
{
|
|
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-
|
|
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
|
|
102518
|
-
"../studio-server/dist/chunk-
|
|
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-
|
|
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
|
|
102944
|
-
"../studio-server/dist/chunk-
|
|
103047
|
+
var init_chunk_5MUVDQOD = __esm({
|
|
103048
|
+
"../studio-server/dist/chunk-5MUVDQOD.js"() {
|
|
102945
103049
|
"use strict";
|
|
102946
|
-
|
|
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-
|
|
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
|
|
103062
|
-
"../studio-server/dist/chunk-
|
|
103165
|
+
var init_chunk_KW5URJHI = __esm({
|
|
103166
|
+
"../studio-server/dist/chunk-KW5URJHI.js"() {
|
|
103063
103167
|
"use strict";
|
|
103064
|
-
|
|
103065
|
-
|
|
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(
|
|
110188
|
-
"
|
|
110189
|
-
"error",
|
|
110190
|
-
|
|
110191
|
-
|
|
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
|
-
|
|
114174
|
-
|
|
114175
|
-
|
|
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
|
|
126878
|
-
`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
134962
|
-
`
|
|
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
|
|
134987
|
-
`
|
|
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
|
|
135030
|
-
`
|
|
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
|
|
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
|
|
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
|
|
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"], {
|
|
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
|
-
|
|
140989
|
-
|
|
140990
|
-
|
|
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
|
-
|
|
141109
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
|
149428
|
-
const
|
|
149429
|
-
const
|
|
149430
|
-
const
|
|
149431
|
-
|
|
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 {
|
|
149438
|
-
if (!
|
|
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}"
|
|
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
|
-
|
|
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
|
|
203023
|
-
const providerName =
|
|
203024
|
-
const model =
|
|
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 (
|
|
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
|
-
|
|
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
|
|
203184
|
-
|
|
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
|
|
203439
|
+
rasterized.push({ relPath, pngBase64: pngBuffer.toString("base64") });
|
|
203199
203440
|
} catch {
|
|
203200
203441
|
svgsSkipped++;
|
|
203201
|
-
|
|
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
|
|
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",
|