added inintla stuff
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1208
backend/package-lock.json
generated
Normal file
1208
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
backend/package.json
Normal file
14
backend/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "music-backend",
|
||||
"version": "1.0.0",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"music-metadata": "^8.0.0",
|
||||
"cors": "^2.8.5"
|
||||
}
|
||||
}
|
||||
105
backend/server.js
Normal file
105
backend/server.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// server.js
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const fs = require('fs/promises');
|
||||
const mm = require('music-metadata');
|
||||
const cors = require('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}`));
|
||||
Reference in New Issue
Block a user