import express from 'express'; import path from 'path'; import fs from 'fs/promises'; import * as mm from 'music-metadata'; import cors from 'cors'; const app = express(); app.use(cors()); // adjust for production if needed // Base music directory const MUSIC_DIR = path.resolve(__dirname, 'music'); const SUPPORTED = ['.flac', '.mp3', '.wav', '.m4a', '.ogg']; // Serve static music files app.use('/music', express.static(MUSIC_DIR, { setHeaders: (res) => { res.setHeader('Cache-Control', 'public, max-age=86400'); } })); // Recursive scan for all music files async function scanDir(dir) { let results = []; const files = await fs.readdir(dir, { withFileTypes: true }); for (const file of files) { const fullPath = path.join(dir, file.name); if (file.isDirectory()) { results = results.concat(await scanDir(fullPath)); } else if (SUPPORTED.includes(path.extname(file.name).toLowerCase())) { results.push(fullPath); } } return results; } // Parse filename into metadata function parseFilename(filename) { // Expected: Title__Movie__Genre__YYYY-MM-DD.ext const base = path.basename(filename); const noExt = base.replace(/\.[^.]+$/, ''); const parts = noExt.split('__'); return { title: parts[0] || 'Unknown', movie: parts[1] && parts[1] !== 'None' ? parts[1] : null, genre: parts[2] || null, date: parts[3] || null, filename: base, }; } // API endpoint: list all tracks app.get('/api/tracks', async (req, res) => { try { const files = await scanDir(MUSIC_DIR); const tracks = []; for (const f of files) { const stat = await fs.stat(f); const metadata = parseFilename(f); metadata.path = `/music/${encodeURIComponent(path.relative(MUSIC_DIR, f))}`; metadata.size = stat.size; metadata.ext = path.extname(f).toLowerCase(); metadata.duration = null; metadata.tags = {}; // Try reading embedded metadata (optional) try { const mmData = await mm.parseFile(f, { duration: true }); if (mmData.format.duration) metadata.duration = Math.round(mmData.format.duration); if (mmData.common) { metadata.tags = mmData.common; metadata.title = mmData.common.title || metadata.title; metadata.movie = mmData.common.album || metadata.movie; if (!metadata.genre && mmData.common.genre) { metadata.genre = Array.isArray(mmData.common.genre) ? mmData.common.genre.join(', ') : mmData.common.genre; } if (!metadata.date && mmData.common.date) metadata.date = mmData.common.date; } } catch (err) { // ignore metadata errors } tracks.push(metadata); } // Sort: first by date (desc), then by filename tracks.sort((a, b) => { if (a.date && b.date) return String(b.date).localeCompare(String(a.date)); return a.filename.localeCompare(b.filename); }); res.json({ tracks }); } catch (err) { console.error(err); res.status(500).json({ error: 'Failed to read music folder' }); } }); // Start server const PORT = process.env.PORT || 3001; app.listen(PORT, () => console.log(`Music API server running at http://localhost:${PORT}`));