/** * Audio-platform registry — enumerates all known MusicBackend implementations * and provides availability helpers. * * A backend is available iff: * - every var in requiredEnv is set (non-empty) in env, AND * - (!requiresVertex || at least one of GOOGLE_CLOUD_PROJECT / VERTEX_PROJECT * / GCLOUD_PROJECT is set in env), AND * - (!requiresGeminiKey || at least one of GEMINI_API_KEYS / GOOGLE_API_KEYS * / GOOGLE_API_KEY is set in env) * * The Gemini-key gate (requiresGeminiKey) covers API-key Gemini products such * as the Lyria 3 music backend: they declare an empty requiredEnv (no single * fixed var) and resolve a key from the pool sources. Gating on the flag — not * the backend id — keeps the rule decoupled from backend identity. * * Vertex credential liveness (ADC token validity) is NOT checked here — that * would require a network call. The check is intentionally env-only so it is * synchronous, pure, and testable. */ import { sunoBackend } from './suno-backend.js'; import { lyriaBackend } from './native-lyria.js'; import { lyria3Backend } from './native-lyria3.js'; import { flowMusicBackend } from './native-flowmusic.js'; import { geminiTts } from './native-gemini-tts.js'; import { elevenLabsTts } from './native-elevenlabs-tts.js'; import { elevenLabsSfx } from './native-elevenlabs-sfx.js'; import type { MusicBackend, SfxBackend, TtsBackend } from './types.js'; /** All registered music backends, in priority order. */ export const MUSIC_BACKENDS: MusicBackend[] = [sunoBackend, lyriaBackend, lyria3Backend, flowMusicBackend]; /** * All registered TTS backends, in priority order. `gemini-tts` stays first (the * documented default), with `elevenlabs-tts` as the Gemini-free alternative — * selected explicitly via `--backend elevenlabs-tts` (or auto-picked when it is * the only available backend). */ export const TTS_BACKENDS: TtsBackend[] = [geminiTts, elevenLabsTts]; /** * Gemini API key sources (same precedence as src/video/gemini-key-pool.ts). * The gemini-tts backend is an API-key product; availability is satisfied iff * at least one of these env vars is set (non-empty) in `env`. */ const GEMINI_KEY_VARS = ['GEMINI_API_KEYS', 'GOOGLE_API_KEYS', 'GOOGLE_API_KEY'] as const; const VERTEX_PROJECT_VARS = [ 'GOOGLE_CLOUD_PROJECT', 'VERTEX_PROJECT', 'GCLOUD_PROJECT', ] as const; /** Shared useapi.net bearer token (same var the dreamina/runway routes use). */ const USEAPI_TOKEN_VAR = 'USEAPI_API_TOKEN'; /** * Returns true if the backend's runtime prerequisites are satisfied in `env`. * Pure and synchronous — no network calls. */ export function isMusicBackendAvailable( b: MusicBackend, env: NodeJS.ProcessEnv = process.env, ): boolean { // All declared env vars must be set and non-empty. for (const varName of b.requiredEnv) { const val = env[varName]; if (!val || val.trim() === '') return false; } // Vertex backends additionally require a project env var. if (b.requiresVertex) { const hasProject = VERTEX_PROJECT_VARS.some((v) => { const val = env[v]; return val && val.trim() !== ''; }); if (!hasProject) return false; } // API-key Gemini backends (e.g. lyria3) additionally require a Gemini key // from any of the pool sources. Gated on the flag, not the backend id. if (b.requiresGeminiKey) { const hasKey = GEMINI_KEY_VARS.some((v) => { const val = env[v]; return val && val.trim() !== ''; }); if (!hasKey) return false; } // useapi-transport backends (e.g. flowmusic) require the shared useapi.net // bearer token. Gated on the flag, not the backend id. if (b.requiresUseApi) { const token = env[USEAPI_TOKEN_VAR]; if (!token || token.trim() === '') return false; } return true; } /** * Returns all music backends that are currently available in `env`. */ export function listAvailableMusicBackends( env: NodeJS.ProcessEnv = process.env, ): MusicBackend[] { return MUSIC_BACKENDS.filter((b) => isMusicBackendAvailable(b, env)); } /** * Returns the MusicBackend with the given `id`, or throws if not found. * Does NOT check availability — the caller decides whether to gate on it. */ export function getMusicBackend(id: string): MusicBackend { const backend = MUSIC_BACKENDS.find((b) => b.id === id); if (!backend) { const known = MUSIC_BACKENDS.map((b) => b.id).join(', '); throw new Error(`Unknown music backend '${id}'. Known backends: ${known}`); } return backend; } /** * Returns true if the TTS backend's runtime prerequisites are satisfied in * `env`. Pure and synchronous — no network calls. * * Backends that set `requiresGeminiKey` (e.g. gemini-tts) declare an empty * requiredEnv (an API-key product, not a fixed env-var product), so * availability additionally requires a Gemini key from the pool sources: at * least one of GEMINI_API_KEYS / GOOGLE_API_KEYS / GOOGLE_API_KEY must be set * and non-empty. Gating on the flag (not the backend id) keeps this rule * decoupled from backend identity and extensible to future TTS backends. */ export function isTtsBackendAvailable( b: TtsBackend, env: NodeJS.ProcessEnv = process.env, ): boolean { // All declared env vars must be set and non-empty. for (const varName of b.requiredEnv) { const val = env[varName]; if (!val || val.trim() === '') return false; } // Backends requiring a Gemini API key need one from any of the pool sources. if (b.requiresGeminiKey) { const hasKey = GEMINI_KEY_VARS.some((v) => { const val = env[v]; return val && val.trim() !== ''; }); if (!hasKey) return false; } return true; } /** * Returns all TTS backends that are currently available in `env`. */ export function listAvailableTtsBackends( env: NodeJS.ProcessEnv = process.env, ): TtsBackend[] { return TTS_BACKENDS.filter((b) => isTtsBackendAvailable(b, env)); } /** * Returns the TtsBackend with the given `id`, or throws if not found. * Does NOT check availability — the caller decides whether to gate on it. */ export function getTtsBackend(id: string): TtsBackend { const backend = TTS_BACKENDS.find((b) => b.id === id); if (!backend) { const known = TTS_BACKENDS.map((b) => b.id).join(', '); throw new Error(`Unknown TTS backend '${id}'. Known backends: ${known}`); } return backend; } // --------------------------------------------------------------------------- // SFX registry // --------------------------------------------------------------------------- /** All registered SFX backends, in priority order. */ export const SFX_BACKENDS: SfxBackend[] = [elevenLabsSfx]; /** * Returns true if the SFX backend's runtime prerequisites are satisfied in * `env`. Pure and synchronous — no network calls. */ export function isSfxBackendAvailable( b: SfxBackend, env: NodeJS.ProcessEnv = process.env, ): boolean { for (const varName of b.requiredEnv) { const val = env[varName]; if (!val || val.trim() === '') return false; } return true; } /** * Returns all SFX backends that are currently available in `env`. */ export function listAvailableSfxBackends( env: NodeJS.ProcessEnv = process.env, ): SfxBackend[] { return SFX_BACKENDS.filter((b) => isSfxBackendAvailable(b, env)); } /** * Returns the SfxBackend with the given `id`, or throws if not found. * Does NOT check availability — the caller decides whether to gate on it. */ export function getSfxBackend(id: string): SfxBackend { const backend = SFX_BACKENDS.find((b) => b.id === id); if (!backend) { const known = SFX_BACKENDS.map((b) => b.id).join(', '); throw new Error(`Unknown SFX backend '${id}'. Known backends: ${known}`); } return backend; }