All checks were successful
Build and Push Docker Image / docker (push) Successful in 5s
117 lines
4.0 KiB
JavaScript
117 lines
4.0 KiB
JavaScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import { parseFile as mmParseFile } from 'music-metadata';
|
|
import { DATA_DIR } from '../config.js';
|
|
import { loadYearsIndex } from '../years.js';
|
|
import { shuffle } from './state.js';
|
|
|
|
/**
|
|
* Get list of available playlists (subdirectories in data folder).
|
|
* Also includes a special "default" playlist if there are audio files in the root data directory.
|
|
* @returns {Array<{id: string, name: string, trackCount: number}>}
|
|
*/
|
|
export function getAvailablePlaylists() {
|
|
try {
|
|
const entries = fs.readdirSync(DATA_DIR, { withFileTypes: true });
|
|
const playlists = [];
|
|
|
|
// Check for files in root directory (legacy/default playlist)
|
|
const rootFiles = entries.filter(
|
|
(e) => e.isFile() && /\.(mp3|wav|m4a|ogg|opus)$/i.test(e.name)
|
|
);
|
|
if (rootFiles.length > 0) {
|
|
playlists.push({
|
|
id: 'default',
|
|
name: 'Default (Root Folder)',
|
|
trackCount: rootFiles.length,
|
|
});
|
|
}
|
|
|
|
// Check subdirectories for playlists
|
|
const subdirs = entries.filter((e) => e.isDirectory());
|
|
for (const dir of subdirs) {
|
|
const dirPath = path.join(DATA_DIR, dir.name);
|
|
const dirFiles = fs.readdirSync(dirPath).filter((f) => /\.(mp3|wav|m4a|ogg|opus)$/i.test(f));
|
|
if (dirFiles.length > 0) {
|
|
playlists.push({
|
|
id: dir.name,
|
|
name: dir.name,
|
|
trackCount: dirFiles.length,
|
|
});
|
|
}
|
|
}
|
|
|
|
return playlists;
|
|
} catch (error) {
|
|
console.error('Error reading playlists:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a deck of tracks from a specific playlist.
|
|
* @param {string} [playlistId='default'] - The playlist ID to load from
|
|
* @returns {Promise<Array>} Shuffled array of track objects
|
|
*/
|
|
export async function loadDeck(playlistId = 'default') {
|
|
// Load the years index for this specific playlist
|
|
const years = loadYearsIndex(playlistId);
|
|
|
|
// Determine the directory to load from
|
|
const targetDir = playlistId === 'default' ? DATA_DIR : path.join(DATA_DIR, playlistId);
|
|
|
|
// Validate that the directory exists
|
|
if (!fs.existsSync(targetDir)) {
|
|
console.error(`Playlist directory not found: ${targetDir}`);
|
|
return [];
|
|
}
|
|
|
|
const files = fs.readdirSync(targetDir).filter((f) => /\.(mp3|wav|m4a|ogg|opus)$/i.test(f));
|
|
|
|
if (files.length === 0) {
|
|
console.warn(`No audio files found in playlist: ${playlistId}`);
|
|
return [];
|
|
}
|
|
|
|
// Process files in batches to avoid "too many open files" error
|
|
const BATCH_SIZE = 50; // Process 50 files at a time
|
|
const tracks = [];
|
|
|
|
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
const batch = files.slice(i, i + BATCH_SIZE);
|
|
const batchTracks = await Promise.all(
|
|
batch.map(async (f) => {
|
|
const fp = path.join(targetDir, f);
|
|
// For file paths, we need to include the playlist subdirectory if not default
|
|
const relativeFile = playlistId === 'default' ? f : path.join(playlistId, f);
|
|
|
|
// Get metadata from JSON first (priority), then from audio file as fallback
|
|
const jsonMeta = years[f];
|
|
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 (err) {
|
|
// Log error but continue processing
|
|
console.warn(`Failed to parse metadata for ${f}:`, err.message);
|
|
}
|
|
}
|
|
|
|
return { id: f, file: relativeFile, title, artist, year };
|
|
})
|
|
);
|
|
tracks.push(...batchTracks);
|
|
}
|
|
|
|
console.log(`Loaded ${tracks.length} tracks from playlist: ${playlistId}`);
|
|
return shuffle(tracks);
|
|
}
|