fixed readme
This commit is contained in:
43
backend/convert-to-mp3.py
Normal file
43
backend/convert-to-mp3.py
Normal 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
1208
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
backend/package.json
Normal file
15
backend/package.json
Normal 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
104
backend/server.js
Normal 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}`));
|
||||
Reference in New Issue
Block a user