import fs from 'fs'; import path from 'path'; import { parseFile as mmParseFile } from 'music-metadata'; import { getPlaylistDir } from './config.js'; import { loadYearsIndex } from './years.js'; /** * List tracks from a specific playlist * @param {string} [playlistId='default'] - The playlist ID to list tracks from * @returns {Promise} Array of track objects */ export async function listTracks(playlistId = 'default') { const years = loadYearsIndex(playlistId); const targetDir = getPlaylistDir(playlistId); const files = fs .readdirSync(targetDir) .filter((f) => /\.(mp3|wav|m4a|ogg|opus)$/i.test(f)) .filter((f) => { // If both base.ext and base.opus exist, list only once (prefer .opus) const ext = path.extname(f).toLowerCase(); if (ext === '.opus') return true; const opusTwin = f.replace(/\.[^.]+$/i, '.opus'); return !fs.existsSync(path.join(targetDir, opusTwin)); }); const tracks = await Promise.all( files.map(async (f) => { // Prefer .opus for playback if exists const ext = path.extname(f).toLowerCase(); const opusName = ext === '.opus' ? f : f.replace(/\.[^.]+$/i, '.opus'); const chosen = fs.existsSync(path.join(targetDir, opusName)) ? opusName : f; const fp = path.join(targetDir, chosen); // Get metadata from JSON first (priority), then from audio file as fallback const jsonMeta = years[f] || years[chosen]; let year = jsonMeta?.year ?? null; let title = jsonMeta?.title ?? path.parse(f).name; let artist = jsonMeta?.artist ?? ''; // Only parse audio file if JSON doesn't have title or artist metadata // Note: year is ONLY taken from years.json, never from audio file if (!jsonMeta || !jsonMeta.title || !jsonMeta.artist) { try { const meta = await mmParseFile(fp, { duration: false }); title = jsonMeta?.title || meta.common.title || title; artist = jsonMeta?.artist || meta.common.artist || artist; // year remains from years.json only - do not use meta.common.year } catch {} } return { id: chosen, file: chosen, title, artist, year }; }) ); return tracks; } export function registerTracksApi(app) { app.get('/api/tracks', async (req, res) => { try { // Support optional playlist parameter (defaults to 'default') const playlistId = req.query.playlist || 'default'; const tracks = await listTracks(playlistId); res.json({ tracks, playlist: playlistId }); } catch (e) { res.status(500).json({ error: e.message }); } }); }