const express = require('express'); const mm = require('music-metadata'); const util = require('util'); const fs = require('fs'); const path = require('path'); const sharp = require('sharp'); const app = express(); const PORT = process.env.PORT || 7860; app.use(express.static('public')); app.use('/music', express.static('music')); const SUPPORTED_FORMATS = ['.mp3', '.mp4', '.wav', '.flac', '.ogg', '.opus']; // Add or remove formats as needed function isSupportedFormat(filename) { const ext = path.extname(filename).toLowerCase(); return SUPPORTED_FORMATS.includes(ext); } app.get('/tracks', async (req, res) => { const searchQuery = (req.query.search || '').toLowerCase(); const pageNumber = parseInt(req.query.page) || 1; const pageSize = 5; fs.readdir('music', async (err, files) => { if (err) { console.error('Error reading music directory', err); return res.sendStatus(500); } // If there's a search query, filter files by it let filteredFiles = files.filter(file => isSupportedFormat(file)); if (searchQuery) { filteredFiles = filteredFiles.filter(file => file.toLowerCase().includes(searchQuery)); } const pageStart = (pageNumber - 1) * pageSize; const pageEnd = pageStart + pageSize; // Slice files for pagination after filtering, ONLY if not searching. If searching, ignore pagination and send first 5 results. const pagedFiles = filteredFiles.slice(searchQuery ? 0 : pageStart, searchQuery ? pageSize : pageEnd); const trackDetails = []; for (const file of pagedFiles) { const filePath = path.join(__dirname, 'music', file); try { const metadata = await mm.parseFile(filePath, { native: true }); let artwork = ''; // Fallback path for default icon const fallbackIconPath = path.join(__dirname, 'music', 'icon.png'); try { let imageBuffer; if (metadata.common.picture && metadata.common.picture[0]) { imageBuffer = await sharp(metadata.common.picture[0].data) .resize(256, 256, { fit: 'inside' }) .toBuffer(); } else { // Use the default icon if no album art is present imageBuffer = await sharp(fallbackIconPath) .resize(256, 256, { fit: 'inside' }) .toBuffer(); } // Determine the image format const imageFormat = metadata.common.picture && metadata.common.picture[0] ? metadata.common.picture[0].format : 'image/png'; artwork = `data:${imageFormat};base64,${imageBuffer.toString('base64')}`; } catch (e) { console.error(`Error processing artwork for file: ${file}`, e); } trackDetails.push({filename: file, artwork}); } catch (error) { console.error(`Error reading metadata for file: ${file}`, error); } } res.json({tracks: trackDetails, total: filteredFiles.length}); }); }); app.listen(PORT, () => console.log(`Server running on port ${PORT}`));