import fs from 'fs' import path from 'path' import { createHash } from 'crypto' const RENDER_CACHE_ROOT = path.join(process.cwd(), '.studio-workspace', 'render-cache') export interface RenderCacheWorkspace { key: string rootDir: string tempDir: string mediaDir: string codeFile: string } function normalizeRenderCacheKey(renderCacheKey: string): string { const trimmed = renderCacheKey.trim() if (!trimmed) { throw new Error('renderCacheKey must not be empty') } return trimmed } function toDirectoryName(renderCacheKey: string): string { const normalized = normalizeRenderCacheKey(renderCacheKey) const readable = normalized .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, 48) || 'render-cache' const digest = createHash('sha1').update(normalized).digest('hex').slice(0, 12) return `${readable}-${digest}` } function ensureWorkspace(rootDir: string): void { fs.mkdirSync(rootDir, { recursive: true }) } export function resolveRenderCacheWorkspace( renderCacheKey: string, scope: 'video' | 'image', blockIndex?: number ): RenderCacheWorkspace { const directoryName = toDirectoryName(renderCacheKey) const scopeSuffix = scope === 'video' ? 'video' : `image-${String(blockIndex ?? 0).padStart(3, '0')}` const rootDir = path.join(RENDER_CACHE_ROOT, directoryName, scopeSuffix) const mediaDir = path.join(rootDir, 'media') const codeFile = path.join(rootDir, 'scene.py') ensureWorkspace(mediaDir) return { key: renderCacheKey, rootDir, tempDir: rootDir, mediaDir, codeFile } } export function buildClassicRenderCacheKey( clientId: string | undefined, outputMode: 'video' | 'image', requestedKey?: string ): string { const normalizedRequested = requestedKey?.trim() if (normalizedRequested) { return normalizedRequested } const normalizedClientId = clientId?.trim() || 'anonymous' return `classic-${normalizedClientId}-${outputMode}` } export function buildStudioRenderCacheKey( sessionId: string, outputMode: 'video' | 'image', workId?: string ): string { const normalizedWorkId = workId?.trim() if (normalizedWorkId) { return `studio-${sessionId}-${outputMode}-${normalizedWorkId}` } return `studio-${sessionId}-${outputMode}` }