'Permission denied']); exit; } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['error' => 'Method not allowed']); exit; } $key = trim($_POST['key'] ?? ''); if (!preg_match('/^[a-z0-9_]+$/', $key) || strlen($key) > 100) { http_response_code(400); echo json_encode(['error' => 'Invalid audio key']); exit; } if (!isset($_FILES['audio']) || $_FILES['audio']['error'] !== UPLOAD_ERR_OK) { $codes = [ UPLOAD_ERR_INI_SIZE => 'File exceeds server limit', UPLOAD_ERR_FORM_SIZE => 'File exceeds form limit', UPLOAD_ERR_PARTIAL => 'File only partially uploaded', UPLOAD_ERR_NO_FILE => 'No file uploaded', ]; $code = $_FILES['audio']['error'] ?? UPLOAD_ERR_NO_FILE; http_response_code(400); echo json_encode(['error' => $codes[$code] ?? 'Upload error']); exit; } $file = $_FILES['audio']; $max_size = 50 * 1024 * 1024; // 50 MB if ($file['size'] > $max_size) { http_response_code(400); echo json_encode(['error' => 'File too large (max 50 MB)']); exit; } $finfo = new finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($file['tmp_name']); $allowed = [ 'audio/mpeg' => 'mp3', 'audio/mp3' => 'mp3', 'audio/mp4' => 'm4a', 'audio/x-m4a' => 'm4a', 'audio/ogg' => 'ogg', 'audio/wav' => 'wav', 'audio/x-wav' => 'wav', 'audio/wave' => 'wav', 'audio/webm' => 'webm', ]; if (!isset($allowed[$mime])) { http_response_code(400); echo json_encode(['error' => 'Invalid format. Allowed: MP3, M4A, OGG, WAV']); exit; } $ext = $allowed[$mime]; $audio_dir = UPLOADS_DIR . 'audio/'; if (!is_dir($audio_dir)) { mkdir($audio_dir, 0755, true); } // Delete any existing file for this key (regardless of extension) foreach (glob($audio_dir . $key . '.*') ?: [] as $old) { unlink($old); } $dest = $audio_dir . $key . '.' . $ext; if (!move_uploaded_file($file['tmp_name'], $dest)) { http_response_code(500); echo json_encode(['error' => 'Failed to save file']); exit; } echo json_encode(['key' => $key, 'ext' => $ext]);