Spaces:
Sleeping
Sleeping
| /** | |
| * Local File Storage | |
| * | |
| * Stores uploaded files locally in the uploads/ directory. | |
| * Files are served via Express static middleware. | |
| */ | |
| import fs from "fs/promises"; | |
| import path from "path"; | |
| import { fileURLToPath } from "url"; | |
| import { nanoid } from "nanoid"; | |
| // Get directory name in ESM | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| // Base directory for uploads (relative to project root) | |
| const UPLOADS_DIR = path.resolve(__dirname, "../uploads"); | |
| // Subdirectories | |
| const TRACKS_DIR = path.join(UPLOADS_DIR, "tracks"); | |
| const STEMS_DIR = path.join(UPLOADS_DIR, "stems"); | |
| /** | |
| * Ensure upload directories exist | |
| */ | |
| export async function ensureUploadDirs(): Promise<void> { | |
| await fs.mkdir(TRACKS_DIR, { recursive: true }); | |
| await fs.mkdir(STEMS_DIR, { recursive: true }); | |
| } | |
| /** | |
| * Get the uploads base directory path | |
| */ | |
| export function getUploadsDir(): string { | |
| return UPLOADS_DIR; | |
| } | |
| /** | |
| * Save an uploaded track file | |
| * | |
| * @param userId User ID | |
| * @param fileName Original filename | |
| * @param data File data as Buffer | |
| * @returns Object with fileKey (relative path) and filePath (absolute path) | |
| */ | |
| export async function saveTrackFile( | |
| userId: number, | |
| fileName: string, | |
| data: Buffer | |
| ): Promise<{ fileKey: string; filePath: string; fileUrl: string }> { | |
| await ensureUploadDirs(); | |
| // Create user-specific directory | |
| const userDir = path.join(TRACKS_DIR, String(userId)); | |
| await fs.mkdir(userDir, { recursive: true }); | |
| // Generate unique filename | |
| const ext = path.extname(fileName) || ".mp3"; | |
| const uniqueName = `${nanoid(16)}${ext}`; | |
| const filePath = path.join(userDir, uniqueName); | |
| // Write file | |
| await fs.writeFile(filePath, data); | |
| // Generate relative key for database storage | |
| const fileKey = `tracks/${userId}/${uniqueName}`; | |
| // URL path for serving (will be served via /uploads/ route) | |
| const fileUrl = `/uploads/${fileKey}`; | |
| return { fileKey, filePath, fileUrl }; | |
| } | |
| /** | |
| * Save a stem file from ML processing | |
| * | |
| * @param trackId Track ID | |
| * @param stemType Stem type (vocals, drums, bass, other) | |
| * @param sourcePath Source file path from ML output | |
| * @returns Object with fileKey, filePath, and fileUrl | |
| */ | |
| export async function saveStemFile( | |
| trackId: number, | |
| stemType: string, | |
| sourcePath: string | |
| ): Promise<{ fileKey: string; filePath: string; fileUrl: string }> { | |
| await ensureUploadDirs(); | |
| // Create track-specific stems directory | |
| const trackStemsDir = path.join(STEMS_DIR, String(trackId)); | |
| await fs.mkdir(trackStemsDir, { recursive: true }); | |
| // Get extension from source | |
| const ext = path.extname(sourcePath) || ".mp3"; | |
| const uniqueName = `${stemType}-${nanoid(8)}${ext}`; | |
| const filePath = path.join(trackStemsDir, uniqueName); | |
| // Copy file from ML output to uploads directory | |
| await fs.copyFile(sourcePath, filePath); | |
| // Generate relative key for database storage | |
| const fileKey = `stems/${trackId}/${uniqueName}`; | |
| // URL path for serving | |
| const fileUrl = `/uploads/${fileKey}`; | |
| return { fileKey, filePath, fileUrl }; | |
| } | |
| /** | |
| * Get the absolute path for a file key | |
| */ | |
| export function getFilePath(fileKey: string): string { | |
| return path.join(UPLOADS_DIR, fileKey); | |
| } | |
| /** | |
| * Check if a file exists | |
| */ | |
| export async function fileExists(fileKey: string): Promise<boolean> { | |
| try { | |
| await fs.access(getFilePath(fileKey)); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Delete a file | |
| */ | |
| export async function deleteFile(fileKey: string): Promise<void> { | |
| try { | |
| await fs.unlink(getFilePath(fileKey)); | |
| } catch { | |
| // Ignore if file doesn't exist | |
| } | |
| } | |
| /** | |
| * Get file stats | |
| */ | |
| export async function getFileStats(fileKey: string): Promise<{ size: number } | null> { | |
| try { | |
| const stats = await fs.stat(getFilePath(fileKey)); | |
| return { size: stats.size }; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| /** | |
| * Create a temp directory for ML processing output | |
| */ | |
| export async function createTempDir(prefix: string): Promise<string> { | |
| const tempDir = path.join(UPLOADS_DIR, "temp", `${prefix}-${nanoid(8)}`); | |
| await fs.mkdir(tempDir, { recursive: true }); | |
| return tempDir; | |
| } | |
| /** | |
| * Clean up a temp directory | |
| */ | |
| export async function cleanupTempDir(tempDir: string): Promise<void> { | |
| try { | |
| await fs.rm(tempDir, { recursive: true, force: true }); | |
| } catch { | |
| // Ignore errors | |
| } | |
| } | |