Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/__tests__/ffmpeg-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,47 @@ describe("ffmpeg command flow", () => {
expect(commandString).toBe("ffmpeg -i https://cdn.rendobar.com/video.mp4 -c:v libx264 -preset fast -vf scale=1920:1080 output.mp4");
});
});

// ── --compute flag → submit params ─────────────────────────────
//
// Mirrors how `rb ffmpeg` constructs the job params object: `compute` is only
// included when the user passed a value, and that value is validated against
// auto|cpu|gpu before submission.

type Compute = "auto" | "cpu" | "gpu";

function isCompute(value: string): value is Compute {
return value === "auto" || value === "cpu" || value === "gpu";
}

function buildParams(command: string, timeout: number, compute: Compute | null): Record<string, unknown> {
return { command, timeout, ...(compute ? { compute } : {}) };
}

describe("ffmpeg --compute flag", () => {
it("includes compute in params when --compute gpu is passed", () => {
const compute = "gpu";
expect(isCompute(compute)).toBe(true);
const params = buildParams("ffmpeg -i in.mp4 out.mp4", 120, compute);
expect(params.compute).toBe("gpu");
});

it("accepts auto and cpu as valid compute modes", () => {
expect(isCompute("auto")).toBe(true);
expect(isCompute("cpu")).toBe(true);
expect(buildParams("ffmpeg -i in.mp4 out.mp4", 120, "cpu").compute).toBe("cpu");
});

it("rejects an invalid compute value", () => {
expect(isCompute("turbo")).toBe(false);
expect(isCompute("GPU")).toBe(false);
expect(isCompute("")).toBe(false);
});

it("omits compute from params when no --compute flag is passed", () => {
const params = buildParams("ffmpeg -i in.mp4 out.mp4", 120, null);
expect("compute" in params).toBe(false);
expect(params.command).toBe("ffmpeg -i in.mp4 out.mp4");
expect(params.timeout).toBe(120);
});
});
25 changes: 23 additions & 2 deletions src/commands/ffmpeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ function localManifestPath(written: string[], manifestRemotePath: string): strin

// ── Flags ──────────────────────────────────────────────────────

type Compute = "auto" | "cpu" | "gpu";
const COMPUTE_MODES: readonly Compute[] = ["auto", "cpu", "gpu"];

function isCompute(value: string): value is Compute {
return (COMPUTE_MODES as readonly string[]).includes(value);
}

interface GlobalFlags {
json: boolean;
urlOnly: boolean;
Expand All @@ -62,6 +69,7 @@ interface GlobalFlags {
output: string | null;
outputDir: string | null;
timeout: number;
compute: Compute | null;
}

function extractGlobalFlags(): GlobalFlags {
Expand All @@ -75,6 +83,7 @@ function extractGlobalFlags(): GlobalFlags {
output: null,
outputDir: null,
timeout: 120,
compute: null,
};
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
Expand All @@ -94,6 +103,14 @@ function extractGlobalFlags(): GlobalFlags {
const val = parseInt(argv[i + 1]!, 10);
if (!Number.isNaN(val) && val > 0) flags.timeout = Math.min(val, 900);
i++;
} else if (arg === "--compute" && i + 1 < argv.length) {
const val = argv[i + 1]!; // Guarded by i + 1 < argv.length
if (!isCompute(val)) {
process.stderr.write(pc.red(` ✗ Invalid --compute value "${val}". Expected one of: auto, cpu, gpu.\n`));
process.exit(2);
}
flags.compute = val;
i++;
}
}
return flags;
Expand All @@ -104,7 +121,7 @@ function extractFfmpegArgs(): string[] {
const ffmpegIdx = argv.indexOf("ffmpeg");
if (ffmpegIdx === -1) return [];
const globalFlags = new Set(["--json", "--url-only", "--quiet", "--no-wait", "--no-download"]);
const globalFlagsWithValue = new Set(["--timeout", "--output", "--output-dir"]);
const globalFlagsWithValue = new Set(["--timeout", "--output", "--output-dir", "--compute"]);
const result: string[] = [];
for (let i = ffmpegIdx + 1; i < argv.length; i++) {
const arg = argv[i]!;
Expand Down Expand Up @@ -135,6 +152,7 @@ ${pc.bold("Flags:")}
--quiet No output, exit code only
--no-wait Submit and exit immediately (prints job ID)
--timeout N Max execution time in seconds (default: 120, max: 900)
--compute <mode> Run on cpu or gpu hardware (auto, cpu, gpu; gpu needs Pro)

${pc.dim("Outputs download to your folder by default — like running ffmpeg locally.")}
${pc.dim("Local files are auto-uploaded before job submission.")}
Expand Down Expand Up @@ -224,7 +242,10 @@ export default defineCommand({

const job = await steps.step("Submitting", async () => {
return client.jobs.create(
{ type: "ffmpeg", params: { command, timeout: flags.timeout } },
{
type: "ffmpeg",
params: { command, timeout: flags.timeout, ...(flags.compute ? { compute: flags.compute } : {}) },
},
{ signal: controller.signal },
);
});
Expand Down