media.sync, media.proxy, media.analyze, and media.cluster for Media Studio: incremental metadata sync with deletion detection, 720p CFR proxies with 1 s keyframes, tone mapping, posters and sprites, sidecar analyses stored per model version with scored segments, and sessions, themes, and person suggestions. Proxy and analysis work is pumped in a small per-space window, jobs step aside when their GPU or CPU permit is taken, and a full disk or an unreachable sidecar pauses work instead of failing assets. Re-clustering keeps collections the user merged or split. Fixes photo posters, which never rendered because ffmpeg yields no frame when seeking a single image. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NLSCTnhge4umMnemGXWr1w
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
import { tmpdir } from "node:os";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
assertDiskSpace,
|
|
defaultMediaJobLimits,
|
|
freeBytes,
|
|
isDiskFull,
|
|
MediaDiskFullError,
|
|
MediaJobLimiter,
|
|
} from "./media-jobs.js";
|
|
|
|
describe("media job limits", () => {
|
|
it("defaults to one GPU job and a bounded number of CPU jobs", () => {
|
|
expect(defaultMediaJobLimits(1)).toEqual({ gpu: 1, cpu: 1 });
|
|
expect(defaultMediaJobLimits(4)).toEqual({ gpu: 1, cpu: 2 });
|
|
expect(defaultMediaJobLimits(64)).toEqual({ gpu: 1, cpu: 2 });
|
|
});
|
|
|
|
it("hands out permits up to the limit per class and never waits", () => {
|
|
const limiter = new MediaJobLimiter({ gpu: 1, cpu: 2 });
|
|
const gpu = limiter.tryAcquire("gpu");
|
|
expect(gpu).not.toBeNull();
|
|
expect(limiter.tryAcquire("gpu")).toBeNull();
|
|
const cpu = [limiter.tryAcquire("cpu"), limiter.tryAcquire("cpu")];
|
|
expect(cpu.every(Boolean)).toBe(true);
|
|
expect(limiter.tryAcquire("cpu")).toBeNull();
|
|
|
|
gpu?.();
|
|
gpu?.();
|
|
expect(limiter.running("gpu")).toBe(0);
|
|
expect(limiter.tryAcquire("gpu")).not.toBeNull();
|
|
cpu[0]?.();
|
|
expect(limiter.running("cpu")).toBe(1);
|
|
});
|
|
|
|
it("rejects limits below one", () => {
|
|
expect(() => new MediaJobLimiter({ gpu: 0, cpu: 1 })).toThrow();
|
|
expect(() => new MediaJobLimiter({ gpu: 1, cpu: 1.5 })).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("disk backpressure", () => {
|
|
it("reports free space and pauses below the reserve", async () => {
|
|
const dir = tmpdir();
|
|
expect(await freeBytes(dir)).toBeGreaterThan(0);
|
|
await expect(assertDiskSpace(dir, 0)).resolves.toBeUndefined();
|
|
await expect(assertDiskSpace(dir, Number.MAX_SAFE_INTEGER)).rejects.toBeInstanceOf(
|
|
MediaDiskFullError,
|
|
);
|
|
});
|
|
|
|
it("recognizes a full disk from errno or ffmpeg output", () => {
|
|
expect(isDiskFull(Object.assign(new Error("write"), { code: "ENOSPC" }))).toBe(true);
|
|
expect(isDiskFull(new Error("ffmpeg failed: No space left on device"))).toBe(true);
|
|
expect(isDiskFull(new Error("Invalid data found"))).toBe(false);
|
|
expect(isDiskFull(null)).toBe(false);
|
|
});
|
|
});
|