Add the Media Studio adapters behind the frozen provider contracts: a deterministic offline inference emulator driven by file-name markers that renders real redactions with ffmpeg, a synthetic emulator library, the bearer-auth HTTP client for the local media sidecar with validated responses, typed errors, busy retries and timeouts, a lazily paginated folder source that never follows symlinks, and the configuration factory. A shared conformance suite runs against the emulator and, in the Docker integration job, against the sidecar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
import { homedir } from "node:os";
|
|
import path from "node:path";
|
|
import type { MediaInferenceProvider, MediaSourceProvider } from "@rakazo/adapter-kit";
|
|
import { expandHome } from "@rakazo/media/node";
|
|
import { EmulatorMediaInferenceProvider } from "./media-emulator.js";
|
|
import { EmulatorMediaSourceProvider } from "./media-emulator-source.js";
|
|
import { FolderMediaSourceProvider } from "./media-folder-source.js";
|
|
import { SidecarMediaInferenceProvider } from "./media-sidecar.js";
|
|
|
|
export interface MediaConfig {
|
|
/** Media Studio runs: a sidecar is configured or the scripted runtime uses the emulator. */
|
|
enabled: boolean;
|
|
workerUrl: string | null;
|
|
workerToken: string | null;
|
|
profilesDir: string;
|
|
/** `${DATA_DIR}/media`, absolute. Every derived file and sidecar path lives here. */
|
|
mediaRoot: string;
|
|
}
|
|
|
|
export type MediaInferenceKind = "sidecar" | "emulator";
|
|
|
|
/** Emulators replace the sidecar whenever the scripted runtime is pinned (tests, e2e). */
|
|
export function mediaEmulatorEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
return env.AGENT_RUNTIME === "scripted";
|
|
}
|
|
|
|
export function mediaConfigFromEnv(env: NodeJS.ProcessEnv = process.env): MediaConfig {
|
|
const workerUrl = env.MEDIA_WORKER_URL?.trim() || null;
|
|
const workerToken = env.MEDIA_WORKER_TOKEN?.trim() || null;
|
|
return {
|
|
enabled: mediaEmulatorEnabled(env) || Boolean(workerUrl && workerToken),
|
|
workerUrl,
|
|
workerToken,
|
|
profilesDir: path.resolve(
|
|
expandHome(env.MEDIA_PROFILES_DIR?.trim() || "~/.evoo/media-profiles", homedir()),
|
|
),
|
|
mediaRoot: path.resolve(env.DATA_DIR ?? "./data", "media"),
|
|
};
|
|
}
|
|
|
|
export function resolveMediaInferenceKind(
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
): MediaInferenceKind | null {
|
|
if (mediaEmulatorEnabled(env)) return "emulator";
|
|
const config = mediaConfigFromEnv(env);
|
|
return config.workerUrl && config.workerToken ? "sidecar" : null;
|
|
}
|
|
|
|
/** The configured inference provider, or null when Media Studio is not set up. */
|
|
export function createMediaInferenceProvider(
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
): MediaInferenceProvider | null {
|
|
const config = mediaConfigFromEnv(env);
|
|
switch (resolveMediaInferenceKind(env)) {
|
|
case "emulator":
|
|
return new EmulatorMediaInferenceProvider({ root: config.mediaRoot });
|
|
case "sidecar":
|
|
return new SidecarMediaInferenceProvider({
|
|
baseUrl: config.workerUrl as string,
|
|
token: config.workerToken as string,
|
|
});
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Emulator clips are cached inside the media root so inference can read them. */
|
|
export function emulatorFixturesDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
return path.join(mediaConfigFromEnv(env).mediaRoot, "emulator-library");
|
|
}
|
|
|
|
/**
|
|
* Server-side source adapter for a persisted MediaSource kind. Client-pushed
|
|
* kinds (apple_photos, ios_library) have no server adapter.
|
|
*/
|
|
export function createMediaSourceProvider(
|
|
kind: string,
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
): MediaSourceProvider {
|
|
switch (kind) {
|
|
case "folder":
|
|
return new FolderMediaSourceProvider();
|
|
case "emulator":
|
|
if (!mediaEmulatorEnabled(env)) break;
|
|
return new EmulatorMediaSourceProvider({ fixturesDir: emulatorFixturesDir(env) });
|
|
default:
|
|
break;
|
|
}
|
|
throw new Error(`No server-side media source for "${kind}". Use folder.`);
|
|
}
|