- Adjusted formatting of callback responses in the ffmpeg test file for better readability. - Ensured consistent indentation and line breaks for JSON strings in mock responses. This enhances code clarity and maintainability in the test suite.
368 lines
10 KiB
TypeScript
368 lines
10 KiB
TypeScript
import { beforeEach, expect, test } from "bun:test";
|
|
import { convert, resetNvidiaGpuCache } from "../../src/converters/ffmpeg";
|
|
|
|
let calls: string[][] = [];
|
|
|
|
function mockExecFile(
|
|
_cmd: string,
|
|
args: string[],
|
|
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
|
) {
|
|
calls.push(args);
|
|
if (args.includes("fail.mov")) {
|
|
callback(new Error("mock failure"), "", "Fake stderr: fail");
|
|
} else if (_cmd === "nvidia-smi") {
|
|
// Mock nvidia-smi - assume GPU is available for tests
|
|
callback(
|
|
null,
|
|
"GPU 0: NVIDIA GeForce RTX 3080 (UUID: GPU-12345678-1234-1234-1234-123456789012)\n",
|
|
"",
|
|
);
|
|
} else if (_cmd === "ffprobe") {
|
|
// Mock ffprobe responses for codec detection
|
|
// Return H.264 codec for .mp4 files, no video stream for images
|
|
if (args.includes("in.mp4") || args.includes("in.mkv") || args.includes("in.avi")) {
|
|
callback(
|
|
null,
|
|
JSON.stringify({
|
|
streams: [
|
|
{
|
|
codec_type: "video",
|
|
codec_name: "h264",
|
|
},
|
|
],
|
|
}),
|
|
"",
|
|
);
|
|
} else if (args.includes("in.jpg") || args.includes("in.png")) {
|
|
// Image files have no video stream
|
|
callback(
|
|
null,
|
|
JSON.stringify({
|
|
streams: [
|
|
{
|
|
codec_type: "audio",
|
|
codec_name: "pcm",
|
|
},
|
|
],
|
|
}),
|
|
"",
|
|
);
|
|
} else {
|
|
// Default: assume H.264 for video files
|
|
callback(
|
|
null,
|
|
JSON.stringify({
|
|
streams: [
|
|
{
|
|
codec_type: "video",
|
|
codec_name: "h264",
|
|
},
|
|
],
|
|
}),
|
|
"",
|
|
);
|
|
}
|
|
} else {
|
|
callback(null, "Fake stdout", "");
|
|
}
|
|
}
|
|
|
|
beforeEach(() => {
|
|
calls = [];
|
|
delete process.env.FFMPEG_ARGS;
|
|
delete process.env.FFMPEG_PREFER_HARDWARE;
|
|
// Reset the GPU availability cache between tests
|
|
resetNvidiaGpuCache();
|
|
});
|
|
|
|
test("converts a normal file", async () => {
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
const result = await convert("in.mp4", "mp4", "avi", "out.avi", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
expect(result).toBe("Done");
|
|
expect(calls[0]).toEqual(expect.arrayContaining(["-i", "in.mp4", "out.avi"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
});
|
|
|
|
test("adds resize for ico output", async () => {
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
const result = await convert("in.png", "png", "ico", "out.ico", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
expect(result).toBe("Done: resized to 256x256");
|
|
expect(calls[0]).toEqual(
|
|
expect.arrayContaining(["-filter:v", expect.stringContaining("scale=")]),
|
|
);
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
});
|
|
|
|
test("uses libaom-av1 for av1.mp4", async () => {
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.mkv", "mkv", "av1.mp4", "out.mp4", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libaom-av1"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
});
|
|
|
|
test("uses libx264 for h264.mp4", async () => {
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.mkv", "mkv", "h264.mp4", "out.mp4", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx264"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
});
|
|
|
|
test("uses libx265 for h265.mp4", async () => {
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.mkv", "mkv", "h265.mp4", "out.mp4", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx265"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
});
|
|
|
|
test("uses libx266 for h266.mp4", async () => {
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.mkv", "mkv", "h266.mp4", "out.mp4", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx266"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
});
|
|
|
|
test("uses h264_nvenc for h264.mp4 when hardware preferred", async () => {
|
|
process.env.FFMPEG_PREFER_HARDWARE = "true";
|
|
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.mkv", "mkv", "h264.mp4", "out.mp4", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
// calls[0] is nvidia-smi, calls[1] is ffprobe, calls[2] is ffmpeg
|
|
expect(calls[2]).toEqual(expect.arrayContaining(["-c:v", "h264_nvenc"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
|
|
delete process.env.FFMPEG_PREFER_HARDWARE;
|
|
});
|
|
|
|
test("uses hevc_nvenc for h265.mp4 when hardware preferred", async () => {
|
|
process.env.FFMPEG_PREFER_HARDWARE = "true";
|
|
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.mkv", "mkv", "h265.mp4", "out.mp4", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
// calls[0] is nvidia-smi, calls[1] is ffprobe, calls[2] is ffmpeg
|
|
expect(calls[2]).toEqual(expect.arrayContaining(["-c:v", "hevc_nvenc"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
|
|
delete process.env.FFMPEG_PREFER_HARDWARE;
|
|
});
|
|
|
|
test("uses libx264 for h264.mp4 when hardware not preferred", async () => {
|
|
process.env.FFMPEG_PREFER_HARDWARE = "false";
|
|
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.mkv", "mkv", "h264.mp4", "out.mp4", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx264"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
|
|
delete process.env.FFMPEG_PREFER_HARDWARE;
|
|
});
|
|
|
|
test("adds CUDA hwaccel for video input when hardware preferred", async () => {
|
|
process.env.FFMPEG_PREFER_HARDWARE = "true";
|
|
delete process.env.FFMPEG_ARGS;
|
|
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.mp4", "mp4", "avi", "out.avi", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
// calls[0] is nvidia-smi, calls[1] is ffprobe, calls[2] is ffmpeg
|
|
expect(calls[2]).toEqual(expect.arrayContaining(["-hwaccel", "cuda"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
|
|
delete process.env.FFMPEG_PREFER_HARDWARE;
|
|
});
|
|
|
|
test("does not add CUDA hwaccel for image input when hardware preferred", async () => {
|
|
process.env.FFMPEG_PREFER_HARDWARE = "true";
|
|
delete process.env.FFMPEG_ARGS;
|
|
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.jpg", "jpg", "png", "out.png", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
// calls[0] is nvidia-smi, calls[1] is ffprobe, calls[2] is ffmpeg
|
|
expect(calls[2]).not.toEqual(expect.arrayContaining(["-hwaccel", "cuda"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
|
|
delete process.env.FFMPEG_PREFER_HARDWARE;
|
|
});
|
|
|
|
test("does not add CUDA hwaccel if FFMPEG_ARGS already specifies hwaccel", async () => {
|
|
process.env.FFMPEG_PREFER_HARDWARE = "true";
|
|
process.env.FFMPEG_ARGS = "-hwaccel vaapi";
|
|
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("in.mp4", "mp4", "avi", "out.avi", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
// When FFMPEG_ARGS already has hwaccel, no ffprobe call is made
|
|
// calls[0] is nvidia-smi, calls[1] is ffmpeg
|
|
// Should use vaapi from FFMPEG_ARGS, not add cuda
|
|
expect(calls[1]).toEqual(expect.arrayContaining(["-hwaccel", "vaapi"]));
|
|
expect(calls[0]).not.toEqual(expect.arrayContaining(["-hwaccel", "cuda"]));
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
|
|
delete process.env.FFMPEG_PREFER_HARDWARE;
|
|
delete process.env.FFMPEG_ARGS;
|
|
});
|
|
|
|
test("respects FFMPEG_ARGS", async () => {
|
|
process.env.FFMPEG_ARGS = "-hide_banner -y";
|
|
|
|
const originalConsoleLog = console.log;
|
|
|
|
let loggedMessage = "";
|
|
console.log = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
await convert("input.mov", "mov", "mp4", "output.mp4", undefined, mockExecFile);
|
|
|
|
console.log = originalConsoleLog;
|
|
|
|
expect(calls[0]?.slice(0, 2)).toEqual(["-hide_banner", "-y"]);
|
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
|
});
|
|
|
|
test("fails on exec error", async () => {
|
|
const originalConsoleError = console.error;
|
|
|
|
let loggedMessage = "";
|
|
console.error = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
expect(convert("fail.mov", "mov", "mp4", "output.mp4", undefined, mockExecFile)).rejects.toThrow(
|
|
"mock failure",
|
|
);
|
|
|
|
console.error = originalConsoleError;
|
|
|
|
expect(loggedMessage).toBe("stderr: Fake stderr: fail");
|
|
});
|
|
|
|
test("logs stderr when execFile returns only stderr and no error", async () => {
|
|
const originalConsoleError = console.error;
|
|
|
|
let loggedMessage = "";
|
|
console.error = (msg) => {
|
|
loggedMessage = msg;
|
|
};
|
|
|
|
// Mock execFile to call back with no error, no stdout, but with stderr
|
|
const mockExecFileStderrOnly = (
|
|
_cmd: string,
|
|
_args: string[],
|
|
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
|
) => {
|
|
callback(null, "", "Only stderr output");
|
|
};
|
|
|
|
await convert("input.mov", "mov", "mp4", "output.mp4", undefined, mockExecFileStderrOnly);
|
|
|
|
console.error = originalConsoleError;
|
|
|
|
expect(loggedMessage).toBe("stderr: Only stderr output");
|
|
});
|