fixed readme

This commit is contained in:
2026-04-27 22:12:43 -05:00
commit 60b19146e6
13 changed files with 3813 additions and 0 deletions

43
backend/convert-to-mp3.py Normal file
View File

@@ -0,0 +1,43 @@
# convert_to_mp3.py
import os
from pydub import AudioSegment
# Directory relative to this script
MUSIC_DIR = os.path.join(os.path.dirname(__file__), "music")
# Supported input formats
INPUT_FORMATS = [".flac", ".wav", ".m4a", ".ogg"]
def convert_to_mp3(file_path):
file_root, ext = os.path.splitext(file_path)
ext = ext.lower()
if ext not in INPUT_FORMATS:
return # skip unsupported formats
mp3_path = f"{file_root}.mp3"
print(f"Converting {file_path}{mp3_path}")
# Load audio
audio = AudioSegment.from_file(file_path)
# Export as MP3
audio.export(mp3_path, format="mp3", bitrate="320k")
# Delete original file
os.remove(file_path)
print(f"Deleted original: {file_path}")
def main():
for root, _, files in os.walk(MUSIC_DIR):
for file in files:
full_path = os.path.join(root, file)
convert_to_mp3(full_path)
if __name__ == "__main__":
main()

1208
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

15
backend/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "music-backend",
"version": "1.0.0",
"type": "module",
"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"
}
}

104
backend/server.js Normal file
View File

@@ -0,0 +1,104 @@
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}`));