'MEDIA_ROOT is not set']); exit; } $baseDir = realpath(trim($mediaRoot)); if ($baseDir === false || !is_dir($baseDir)) { echo json_encode(['error' => 'MEDIA_ROOT base directory not found']); exit; } $directories = [ 'videos' => $baseDir . DIRECTORY_SEPARATOR . 'Videos', 'music' => $baseDir . DIRECTORY_SEPARATOR . 'Music', ]; // Extensions per tab (don’t mix) $extensionsByType = [ 'videos' => ['mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'webm', 'm4v'], 'music' => ['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac', 'wma', 'opus'], ]; if (!isset($directories[$type])) { echo json_encode(['error' => 'Invalid media type']); exit; } $targetDir = $directories[$type]; $validExtensions = $extensionsByType[$type]; // Fail fast if dir missing if (!is_dir($targetDir)) { echo json_encode(['error' => 'Directory not found']); exit; } /** * Recursively scan a directory and build a folder->(folders/files) structure. * Files are returned as paths relative to $baseDir. */ function scanDirectory(string $dir, string $baseDir, array $validExtensions): array { $result = []; // Resolve base once (prevents per-file realpath overhead and handles normalization) $realBase = realpath($baseDir); if ($realBase === false) { return []; } $realBase = rtrim($realBase, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; // scandir() can return false on permissions $items = @scandir($dir); if ($items === false) return []; foreach ($items as $item) { // Skip dot/hidden entries if ($item === '.' || $item === '..' || ($item !== '' && $item[0] === '.')) { continue; } $fullPath = $dir . DIRECTORY_SEPARATOR . $item; if (is_dir($fullPath)) { $sub = scanDirectory($fullPath, $baseDir, $validExtensions); if (!empty($sub)) { $result[$item] = $sub; } continue; } $ext = strtolower(pathinfo($item, PATHINFO_EXTENSION)); if (!in_array($ext, $validExtensions, true)) { continue; } // Normalize and enforce: file must live under baseDir $realFile = realpath($fullPath); if ($realFile === false) { continue; } if (strpos($realFile, $realBase) !== 0) { continue; } // Return relative path from baseDir $relativePath = substr($realFile, strlen($realBase)); $result[$item] = $relativePath; } // Sort: folders first, then files, alphabetically uksort($result, function ($a, $b) use ($result) { $aIsDir = is_array($result[$a]); $bIsDir = is_array($result[$b]); if ($aIsDir && !$bIsDir) return -1; if (!$aIsDir && $bIsDir) return 1; // IMPORTANT: keys can be ints if the filename is numeric (e.g. "01", "2026") return strcasecmp((string)$a, (string)$b); }); return $result; } $tree = scanDirectory($targetDir, $baseDir, $validExtensions); echo json_encode( empty($tree) ? ['message' => 'No media files found'] : $tree, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );