mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
fix(cli): hide Windows child process consoles
Add windowsHide: true to all 16 issue-scoped Studio server, lint, and CLI child-process calls so Windows console-subsystem children do not flash visible consoles. Regression tests mock child_process and inspect the actual options passed at each scoped call site. #3476 detached Node telemetry and #3430 Chrome headless shell remain intentionally out of scope. Co-authored-by: miguel.sierra <229591595+miguel-heygen@users.noreply.github.com>
This commit is contained in:
co-authored by
miguel.sierra
parent
097d901d70
commit
1d4dd0e352
@@ -341,7 +341,7 @@ function spawnFfmpeg(
|
||||
label: string,
|
||||
stdio: StdioTuple,
|
||||
): FfmpegProc {
|
||||
const proc = spawn(ffmpegPath, args, { stdio });
|
||||
const proc = spawn(ffmpegPath, args, { stdio, windowsHide: true });
|
||||
let stderrBuf = "";
|
||||
proc.stderr?.on("data", (d: Buffer) => {
|
||||
stderrBuf += d.toString();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { Readable } from "node:stream";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { spawnMock, processFrameMock, closeSessionMock } = vi.hoisted(() => ({
|
||||
spawnMock: vi.fn(),
|
||||
processFrameMock: vi.fn(async () => ({ fg: Buffer.alloc(4) })),
|
||||
closeSessionMock: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("node:child_process", () => ({ spawn: spawnMock }));
|
||||
vi.mock("../browser/ffmpeg.js", () => ({
|
||||
findFFmpeg: () => "/fake/bin/ffmpeg",
|
||||
findFFprobe: () => "/fake/bin/ffprobe",
|
||||
getFFmpegInstallHint: () => "install ffmpeg",
|
||||
}));
|
||||
vi.mock("./inference.js", () => ({
|
||||
createSession: async () => ({
|
||||
provider: "test",
|
||||
process: processFrameMock,
|
||||
close: closeSessionMock,
|
||||
}),
|
||||
}));
|
||||
vi.mock("@hyperframes/engine", () => ({
|
||||
DEFAULT_VP9_CPU_USED: 4,
|
||||
renderProvenanceArgs: () => [],
|
||||
extractMediaMetadata: async () => ({ width: 1, height: 1, fps: 1, durationSeconds: 1 }),
|
||||
}));
|
||||
|
||||
import { render } from "./pipeline.js";
|
||||
|
||||
function fakeFfmpeg(stdout: Readable) {
|
||||
const proc = new EventEmitter() as EventEmitter & {
|
||||
stdout: Readable;
|
||||
stderr: EventEmitter;
|
||||
stdin: EventEmitter & { write: ReturnType<typeof vi.fn>; end: ReturnType<typeof vi.fn> };
|
||||
kill: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
proc.stdout = stdout;
|
||||
proc.stderr = new EventEmitter();
|
||||
proc.stdin = Object.assign(new EventEmitter(), { write: vi.fn(() => true), end: vi.fn() });
|
||||
proc.kill = vi.fn();
|
||||
queueMicrotask(() => proc.emit("exit", 0, null));
|
||||
return proc;
|
||||
}
|
||||
|
||||
describe("background-removal FFmpeg child-process options", () => {
|
||||
it("hides every FFmpeg console window", async () => {
|
||||
spawnMock
|
||||
.mockImplementationOnce(() => fakeFfmpeg(Readable.from([Buffer.alloc(3)])))
|
||||
.mockImplementationOnce(() => fakeFfmpeg(Readable.from([])));
|
||||
|
||||
await render({ inputPath: "/tmp/input.mp4", outputPath: "/tmp/output.webm" });
|
||||
|
||||
expect(spawnMock).toHaveBeenCalledTimes(2);
|
||||
for (const call of spawnMock.mock.calls) {
|
||||
expect(call[2]).toEqual(expect.objectContaining({ windowsHide: true }));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -75,7 +75,7 @@ describe("resolveH264EncoderMode", () => {
|
||||
expect(mockExecFile).toHaveBeenCalledWith(
|
||||
"/custom/ffmpeg",
|
||||
["-hide_banner", "-encoders"],
|
||||
expect.objectContaining({ encoding: "utf-8" }),
|
||||
expect.objectContaining({ encoding: "utf-8", windowsHide: true }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ export function detectH264EncoderMode(ffmpegPath: string, gpuRequested: boolean)
|
||||
encoding: "utf-8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
});
|
||||
return resolveH264EncoderMode(encoders, gpuRequested);
|
||||
}
|
||||
|
||||
@@ -210,6 +210,25 @@ describe("findBrowser — cache resolution", () => {
|
||||
expect(result).toEqual({ executablePath: HF_BINARY, source: "cache" });
|
||||
});
|
||||
|
||||
it("hides the Windows console used by the where lookup", async () => {
|
||||
Object.defineProperty(process, "platform", { value: "win32", configurable: true });
|
||||
installFsMocks({ existing: new Set() });
|
||||
installPuppeteerBrowsersMock();
|
||||
const execSync = vi.fn((command: string) =>
|
||||
command === "where google-chrome" ? "C:\\Chrome\\chrome.exe\n" : "",
|
||||
);
|
||||
vi.doMock("node:child_process", () => ({ execSync, spawnSync: vi.fn() }));
|
||||
|
||||
const { findBrowser } = await import("./manager.js");
|
||||
const result = await findBrowser();
|
||||
|
||||
expect(result).toEqual({ executablePath: "C:\\Chrome\\chrome.exe", source: "system" });
|
||||
expect(execSync).toHaveBeenCalledWith(
|
||||
"where google-chrome",
|
||||
expect.objectContaining({ windowsHide: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not resolve to a hyperframes-cache build from an older CHROME_VERSION pin", async () => {
|
||||
// A build downloaded by a prior hyperframes version (this pin has moved
|
||||
// 131 -> 151 -> 152 across releases) must not satisfy resolution, or an
|
||||
|
||||
@@ -254,6 +254,7 @@ function whichBinary(name: string): string | undefined {
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
});
|
||||
const first = output
|
||||
.split(/\r?\n/)
|
||||
|
||||
@@ -35,6 +35,10 @@ describe("runEnvironmentChecks", () => {
|
||||
expect(result.outcomes.find((outcome) => outcome.name === "FFprobe")?.ok).toBe(true);
|
||||
expect(result.ffmpegPath).toBe(process.execPath);
|
||||
expect(result.ffprobePath).toBe(process.execPath);
|
||||
expect(execFileSync).toHaveBeenCalledTimes(2);
|
||||
for (const call of execFileSync.mock.calls) {
|
||||
expect(call[2]).toEqual(expect.objectContaining({ windowsHide: true }));
|
||||
}
|
||||
});
|
||||
|
||||
it("reports ffprobe as a render-blocking error when the explicit path is missing", async () => {
|
||||
|
||||
@@ -61,8 +61,11 @@ type ToolVersionResult = { ok: true; detail: string } | { ok: false; detail: str
|
||||
function readToolVersion(binaryPath: string): ToolVersionResult {
|
||||
try {
|
||||
const raw =
|
||||
execFileSync(binaryPath, ["-version"], { encoding: "utf-8", timeout: 5000 }).split("\n")[0] ??
|
||||
"";
|
||||
execFileSync(binaryPath, ["-version"], {
|
||||
encoding: "utf-8",
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
}).split("\n")[0] ?? "";
|
||||
const version = parseToolVersion(raw);
|
||||
return { ok: true, detail: version ? `${version} at ${binaryPath}` : binaryPath };
|
||||
} catch (error) {
|
||||
|
||||
@@ -510,7 +510,7 @@ export async function runFfmpegOnce(
|
||||
timeoutMs: number,
|
||||
): Promise<FfmpegRunResult> {
|
||||
return await new Promise((resolvePromise) => {
|
||||
const ff = spawn(ffmpegPath, args);
|
||||
const ff = spawn(ffmpegPath, args, { windowsHide: true });
|
||||
let stderr = "";
|
||||
let timedOut = false;
|
||||
const timer = setTimeout(() => {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { spawnMock } = vi.hoisted(() => ({ spawnMock: vi.fn() }));
|
||||
|
||||
vi.mock("node:child_process", () => ({ spawn: spawnMock }));
|
||||
|
||||
import { runFfmpegOnce } from "./captureCompositionFrame.js";
|
||||
|
||||
describe("runFfmpegOnce child-process options", () => {
|
||||
it("hides the FFmpeg console window", async () => {
|
||||
const proc = new EventEmitter() as EventEmitter & {
|
||||
stderr: EventEmitter;
|
||||
kill: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
proc.stderr = new EventEmitter();
|
||||
proc.kill = vi.fn();
|
||||
spawnMock.mockReturnValue(proc);
|
||||
|
||||
const result = runFfmpegOnce("/fake/bin/ffmpeg", ["-version"], 1000);
|
||||
proc.emit("close", 0);
|
||||
await result;
|
||||
|
||||
expect(spawnMock.mock.calls[0]?.[2]).toEqual(expect.objectContaining({ windowsHide: true }));
|
||||
});
|
||||
});
|
||||
@@ -50,6 +50,7 @@ export function killProcessTree(pid: number, signal: NodeJS.Signals = "SIGTERM")
|
||||
execFileSync("taskkill", windowsProcessTreeKillArgs(pid), {
|
||||
stdio: "ignore",
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
});
|
||||
} catch {
|
||||
// Process already exited or taskkill could not inspect it.
|
||||
@@ -103,7 +104,7 @@ export function processIdentity(pid: number): string | null {
|
||||
"-Command",
|
||||
`(Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}').CreationDate.ToFileTimeUtc()`,
|
||||
],
|
||||
{ encoding: "utf8", timeout: 2000 },
|
||||
{ encoding: "utf8", timeout: 2000, windowsHide: true },
|
||||
).trim();
|
||||
return created ? `windows:${created}` : null;
|
||||
}
|
||||
@@ -142,7 +143,7 @@ function processParentPid(pid: number): number | null {
|
||||
"-Command",
|
||||
`(Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}').ParentProcessId`,
|
||||
],
|
||||
{ encoding: "utf8", timeout: 2000 },
|
||||
{ encoding: "utf8", timeout: 2000, windowsHide: true },
|
||||
)
|
||||
: execFileSync("ps", ["-o", "ppid=", "-p", String(pid)], {
|
||||
encoding: "utf8",
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { execFileSyncMock } = vi.hoisted(() => ({ execFileSyncMock: vi.fn() }));
|
||||
|
||||
vi.mock("node:child_process", () => ({
|
||||
execFileSync: execFileSyncMock,
|
||||
execSync: vi.fn(),
|
||||
}));
|
||||
|
||||
import { isProcessDescendant, killProcessTree, processIdentity } from "./orphanCleanup.js";
|
||||
|
||||
describe("Windows orphan-cleanup child-process options", () => {
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(process, "platform", { value: "win32", configurable: true });
|
||||
execFileSyncMock.mockImplementation((command: string, args: string[]) => {
|
||||
if (command === "taskkill") return Buffer.alloc(0);
|
||||
const script = args.at(-1) ?? "";
|
||||
if (script.includes("CreationDate")) return "123456\n";
|
||||
if (script.includes("ProcessId = 400")) return "300\n";
|
||||
if (script.includes("ProcessId = 300")) return "200\n";
|
||||
return "1\n";
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true });
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("hides taskkill and PowerShell console windows", () => {
|
||||
killProcessTree(4321);
|
||||
expect(processIdentity(4321)).toBe("windows:123456");
|
||||
expect(isProcessDescendant(400, 200)).toBe(true);
|
||||
|
||||
expect(execFileSyncMock).toHaveBeenCalledTimes(4);
|
||||
for (const call of execFileSyncMock.mock.calls) {
|
||||
expect(call[2]).toEqual(expect.objectContaining({ windowsHide: true }));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -86,13 +86,14 @@ describe("sampledAlphaIsFullyOpaque", () => {
|
||||
}
|
||||
|
||||
async function importWithMocks(
|
||||
execImpl: (cmd: string, args: readonly string[], opts?: unknown) => Buffer,
|
||||
execImpl: (cmd: string, args: readonly string[], opts?: unknown) => string | Buffer,
|
||||
ffmpegPath: string | null = FAKE_FFMPEG,
|
||||
ffprobePath: string | null = null,
|
||||
) {
|
||||
vi.doMock("node:child_process", () => ({ execFileSync: execImpl }));
|
||||
vi.doMock("../browser/ffmpeg.js", () => ({
|
||||
findFFmpeg: () => ffmpegPath,
|
||||
findFFprobe: () => null,
|
||||
findFFprobe: () => ffprobePath,
|
||||
}));
|
||||
return await import("./webmAlphaCheck.js");
|
||||
}
|
||||
@@ -165,8 +166,10 @@ describe("sampledAlphaIsFullyOpaque", () => {
|
||||
// Without `-c:v libvpx-vp9` BEFORE `-i`, the check would falsely report
|
||||
// alpha=255 on WebMs whose alpha is genuinely present.
|
||||
let capturedArgs: readonly string[] | undefined;
|
||||
const { sampledAlphaIsFullyOpaque } = await importWithMocks((_cmd, args) => {
|
||||
let capturedOptions: unknown;
|
||||
const { sampledAlphaIsFullyOpaque } = await importWithMocks((_cmd, args, options) => {
|
||||
capturedArgs = args;
|
||||
capturedOptions = options;
|
||||
return opaqueBuffer(3);
|
||||
});
|
||||
sampledAlphaIsFullyOpaque(FAKE_WEBM);
|
||||
@@ -182,5 +185,23 @@ describe("sampledAlphaIsFullyOpaque", () => {
|
||||
expect(argList).toContain("-frames:v");
|
||||
expect(argList[argList.indexOf("-frames:v") + 1]).toBe("3");
|
||||
expect(argList).toContain("rawvideo");
|
||||
expect(capturedOptions).toEqual(expect.objectContaining({ windowsHide: true }));
|
||||
});
|
||||
|
||||
it("hides the ffprobe console window", async () => {
|
||||
let capturedOptions: unknown;
|
||||
const { warnIfWebmAlphaDropped } = await importWithMocks(
|
||||
(_cmd, _args, options) => {
|
||||
capturedOptions = options;
|
||||
return JSON.stringify({ streams: [{ codec_name: "vp9", tags: {} }] });
|
||||
},
|
||||
null,
|
||||
"/fake/bin/ffprobe",
|
||||
);
|
||||
vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
warnIfWebmAlphaDropped(FAKE_WEBM, "webm", false);
|
||||
|
||||
expect(capturedOptions).toEqual(expect.objectContaining({ windowsHide: true }));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,7 +89,7 @@ function probeWebmAlpha(filePath: string): WebmAlphaProbe {
|
||||
"--",
|
||||
filePath,
|
||||
],
|
||||
{ encoding: "utf-8", timeout: 15_000 },
|
||||
{ encoding: "utf-8", timeout: 15_000, windowsHide: true },
|
||||
);
|
||||
const parsed = JSON.parse(raw) as {
|
||||
streams?: Array<{ codec_name?: string; tags?: Record<string, string> }>;
|
||||
@@ -159,7 +159,12 @@ export function sampledAlphaIsFullyOpaque(filePath: string): boolean | undefined
|
||||
"rawvideo",
|
||||
"-",
|
||||
],
|
||||
{ timeout: 30_000, maxBuffer: 4096, stdio: ["ignore", "pipe", "pipe"] },
|
||||
{
|
||||
timeout: 30_000,
|
||||
maxBuffer: 4096,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
},
|
||||
);
|
||||
if (
|
||||
buf.length === 0 ||
|
||||
|
||||
@@ -27,7 +27,7 @@ const PROBE_CONCURRENCY = 8;
|
||||
|
||||
function execFileAsync(file: string, args: string[]): Promise<string> {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
execFile(file, args, { timeout: PROBE_TIMEOUT_MS }, (error, stdout) => {
|
||||
execFile(file, args, { timeout: PROBE_TIMEOUT_MS, windowsHide: true }, (error, stdout) => {
|
||||
if (error) reject(error);
|
||||
else resolvePromise(stdout.toString());
|
||||
});
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { execFileMock } = vi.hoisted(() => ({ execFileMock: vi.fn() }));
|
||||
|
||||
vi.mock("node:child_process", () => {
|
||||
const mocked = { execFile: execFileMock };
|
||||
return { ...mocked, default: mocked };
|
||||
});
|
||||
vi.mock("@hyperframes/parsers/ff-binaries", () => ({
|
||||
findFfBinary: () => "/fake/bin/ffprobe",
|
||||
}));
|
||||
|
||||
import { lintHevcPreviewCodec } from "./hevcPreviewLint.js";
|
||||
|
||||
describe("HEVC preview probe options", () => {
|
||||
it("hides the ffprobe console window", async () => {
|
||||
execFileMock.mockImplementation(
|
||||
(
|
||||
_command: string,
|
||||
_args: string[],
|
||||
_options: unknown,
|
||||
callback: (...args: unknown[]) => void,
|
||||
) => {
|
||||
callback(null, JSON.stringify({ streams: [{ codec_name: "hevc" }] }));
|
||||
},
|
||||
);
|
||||
|
||||
await lintHevcPreviewCodec(new Map([["/tmp/clip.mp4", "clip.mp4"]]));
|
||||
|
||||
expect(execFileMock.mock.calls[0]?.[2]).toEqual(expect.objectContaining({ windowsHide: true }));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { execFileMock, spawnMock } = vi.hoisted(() => ({
|
||||
execFileMock: vi.fn(),
|
||||
spawnMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:child_process", () => {
|
||||
const mocked = { execFile: execFileMock, spawn: spawnMock };
|
||||
return { ...mocked, default: mocked };
|
||||
});
|
||||
vi.mock("@hyperframes/parsers/ff-binaries", () => ({
|
||||
findFfBinary: (name: string) => `/fake/bin/${name}`,
|
||||
}));
|
||||
|
||||
import { probeMediaMetadata } from "./mediaMetadata.js";
|
||||
import { decodeAudioPeaks } from "./waveform.js";
|
||||
|
||||
describe("Studio child-process options", () => {
|
||||
it("hides the ffprobe console window", async () => {
|
||||
execFileMock.mockImplementation(
|
||||
(
|
||||
_command: string,
|
||||
_args: string[],
|
||||
_options: unknown,
|
||||
callback: (...args: unknown[]) => void,
|
||||
) => {
|
||||
callback(
|
||||
null,
|
||||
JSON.stringify({ streams: [{ codec_type: "video", codec_name: "h264" }] }),
|
||||
"",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await probeMediaMetadata("/tmp/clip.mp4");
|
||||
|
||||
expect(execFileMock.mock.calls[0]?.[2]).toEqual(expect.objectContaining({ windowsHide: true }));
|
||||
});
|
||||
|
||||
it("hides the waveform FFmpeg console window", async () => {
|
||||
const proc = new EventEmitter() as EventEmitter & { stdout: EventEmitter };
|
||||
proc.stdout = new EventEmitter();
|
||||
spawnMock.mockReturnValue(proc);
|
||||
|
||||
const peaks = decodeAudioPeaks("/tmp/audio.wav");
|
||||
proc.stdout.emit("data", Buffer.from(new Float32Array([0.5]).buffer));
|
||||
proc.emit("close", 0);
|
||||
await peaks;
|
||||
|
||||
expect(spawnMock.mock.calls[0]?.[2]).toEqual(expect.objectContaining({ windowsHide: true }));
|
||||
});
|
||||
});
|
||||
@@ -28,7 +28,7 @@ const execFileRunner: FfprobeRunner = (command, args, options) =>
|
||||
execFile(
|
||||
command,
|
||||
args,
|
||||
{ timeout: options?.timeout, maxBuffer: options?.maxBuffer },
|
||||
{ timeout: options?.timeout, maxBuffer: options?.maxBuffer, windowsHide: true },
|
||||
(error, stdout, stderr) => {
|
||||
if (error && error.code === "ENOENT") {
|
||||
resolvePromise({ status: null, stdout: "", stderr: "", error });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { validateUploadedMedia, validateUploadedMediaBuffer } from "./mediaValidation.js";
|
||||
|
||||
describe("validateUploadedMedia", () => {
|
||||
@@ -11,13 +11,18 @@ describe("validateUploadedMedia", () => {
|
||||
});
|
||||
|
||||
it("accepts video files with a video stream", () => {
|
||||
expect(
|
||||
validateUploadedMedia("/tmp/test.mp4", () => ({
|
||||
status: 0,
|
||||
stdout: JSON.stringify({ streams: [{ codec_type: "video" }] }),
|
||||
stderr: "",
|
||||
})),
|
||||
).toEqual({ ok: true });
|
||||
const runner = vi.fn(() => ({
|
||||
status: 0,
|
||||
stdout: JSON.stringify({ streams: [{ codec_type: "video" }] }),
|
||||
stderr: "",
|
||||
}));
|
||||
|
||||
expect(validateUploadedMedia("/tmp/test.mp4", runner)).toEqual({ ok: true });
|
||||
expect(runner).toHaveBeenCalledWith(
|
||||
"ffprobe",
|
||||
expect.any(Array),
|
||||
expect.objectContaining({ windowsHide: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects video files with no supported video stream", () => {
|
||||
|
||||
@@ -9,6 +9,7 @@ const AUDIO_EXT = /\.(mp3|wav|ogg|m4a|aac)$/i;
|
||||
type FfprobeRunner = (
|
||||
command: string,
|
||||
args: string[],
|
||||
options: { windowsHide: boolean },
|
||||
) => {
|
||||
status: number | null;
|
||||
stdout: string | Buffer;
|
||||
@@ -26,16 +27,11 @@ export function validateUploadedMedia(
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
const result = runner("ffprobe", [
|
||||
"-v",
|
||||
"error",
|
||||
"-show_entries",
|
||||
"stream=codec_type",
|
||||
"-of",
|
||||
"json",
|
||||
"--",
|
||||
filePath,
|
||||
]);
|
||||
const result = runner(
|
||||
"ffprobe",
|
||||
["-v", "error", "-show_entries", "stream=codec_type", "-of", "json", "--", filePath],
|
||||
{ windowsHide: true },
|
||||
);
|
||||
|
||||
if (result.error?.code === "ENOENT") {
|
||||
return { ok: true };
|
||||
|
||||
@@ -28,14 +28,15 @@ function createFakeProc(): FakeProc {
|
||||
return proc;
|
||||
}
|
||||
|
||||
type SpawnCall = { command: string; args: string[]; proc: FakeProc };
|
||||
type SpawnImpl = (command: string, args: string[]) => FakeProc;
|
||||
type SpawnOptions = { windowsHide?: boolean };
|
||||
type SpawnCall = { command: string; args: string[]; options?: SpawnOptions; proc: FakeProc };
|
||||
type SpawnImpl = (command: string, args: string[], options?: SpawnOptions) => FakeProc;
|
||||
|
||||
function createSpawnSpy(): { spawn: SpawnImpl; calls: SpawnCall[] } {
|
||||
const calls: SpawnCall[] = [];
|
||||
const spawn: SpawnImpl = (command, args) => {
|
||||
const spawn: SpawnImpl = (command, args, options) => {
|
||||
const proc = createFakeProc();
|
||||
calls.push({ command, args, proc });
|
||||
calls.push({ command, args, options, proc });
|
||||
return proc;
|
||||
};
|
||||
return { spawn, calls };
|
||||
@@ -222,6 +223,7 @@ describe("resolveProxy", () => {
|
||||
await flush();
|
||||
|
||||
expect(calls[0]!.args).toEqual(["-hide_banner", "-filters"]);
|
||||
expect(calls[0]!.options?.windowsHide).toBe(true);
|
||||
calls[0]!.proc.stdout.emit(
|
||||
"data",
|
||||
Buffer.from(" ..C zscale V->V zimg scale\n T.C tonemap V->V tone map\n"),
|
||||
@@ -459,6 +461,7 @@ describe("resolveProxy", () => {
|
||||
const retry = resolveProxy(projectDir, sourcePath);
|
||||
await flush();
|
||||
expect(calls).toHaveLength(2);
|
||||
expect(calls[1]!.options?.windowsHide).toBe(true);
|
||||
succeed(calls[1]!);
|
||||
await expect(retry).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -289,6 +289,7 @@ function ensureHdrFilters(ffmpegPath: string): Promise<void> {
|
||||
const promise = new Promise<void>((resolveCheck, rejectCheck) => {
|
||||
const proc = spawn(ffmpegPath, ["-hide_banner", "-filters"], {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
});
|
||||
let stdout = "";
|
||||
proc.stdout?.on("data", (chunk: Buffer) => {
|
||||
@@ -411,6 +412,7 @@ async function runFfmpeg(
|
||||
stdio: ["ignore", "ignore", "pipe"],
|
||||
timeout: TRANSCODE_TIMEOUT_MS,
|
||||
killSignal: "SIGKILL",
|
||||
windowsHide: true,
|
||||
});
|
||||
let stderrTail = "";
|
||||
proc.stderr?.on("data", (chunk: Buffer) => {
|
||||
|
||||
@@ -67,7 +67,7 @@ export function decodeAudioPeaks(audioPath: string): Promise<number[]> {
|
||||
"-vn",
|
||||
"pipe:1",
|
||||
],
|
||||
{ stdio: ["ignore", "pipe", "ignore"] },
|
||||
{ stdio: ["ignore", "pipe", "ignore"], windowsHide: true },
|
||||
);
|
||||
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
Reference in New Issue
Block a user