Initial commit: KeySelector implementation
This commit is contained in:
commit
48cdda01ca
30 files changed
+2272
No files matched your search
@@ -0,0 +1,30 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const path = require('path');
|
||||
|
||||
const recognizeRoute = require('./routes/recognize');
|
||||
const metadataRoute = require('./routes/metadata');
|
||||
const suggestionsRoute = require('./routes/suggestions');
|
||||
const coverRoute = require('./routes/cover');
|
||||
require('./workers/poll-quota'); // Start quota worker
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Serve static frontend
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
app.use('/shared', express.static(path.join(__dirname, '../shared')));
|
||||
|
||||
// API Routes
|
||||
app.use('/api/recognize', recognizeRoute);
|
||||
app.use('/api/metadata', metadataRoute);
|
||||
app.use('/api/suggestions', suggestionsRoute);
|
||||
app.use('/api/cover', coverRoute);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`KeySelector backend listening on port ${port}`);
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "keyselector",
|
||||
"version": "1.0.0",
|
||||
"description": "Browser-basiertes DJ-Tool",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"node-fetch": "^3.3.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
const express = require('express');
|
||||
const fetch = require('node-fetch');
|
||||
const router = express.Router();
|
||||
|
||||
// Fetch cover from iTunes API to avoid frontend CORS issues
|
||||
router.get('/', async (req, res) => {
|
||||
const q = req.query.q;
|
||||
if (!q) return res.status(400).json({ error: 'Missing q parameter' });
|
||||
|
||||
try {
|
||||
const url = `https://itunes.apple.com/search?term=${encodeURIComponent(q)}&media=music&entity=song&limit=1`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.results && data.results[0] && data.results[0].artworkUrl100) {
|
||||
const src = data.results[0].artworkUrl100.replace('100x100bb', '300x300bb');
|
||||
return res.json({ url: src });
|
||||
}
|
||||
|
||||
res.json({ url: null });
|
||||
} catch (err) {
|
||||
console.error('iTunes cover fetch error', err);
|
||||
res.status(500).json({ error: 'Server error fetching cover' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,21 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// Mock metadata route
|
||||
router.get('/', async (req, res) => {
|
||||
const track = req.query.track || 'Mock Track';
|
||||
|
||||
// In production, this would call GetSongBPM and Last.fm
|
||||
// to fetch BPM, Camelot Key, and genre/mood tags.
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
key: '4B',
|
||||
bpm: 120,
|
||||
tags: ['Electronic', 'Synthpop']
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,19 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// Mock recognize route
|
||||
router.post('/', async (req, res) => {
|
||||
// In production, this would receive an audio buffer, fingerpint it,
|
||||
// and call the AcoustID API.
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
title: 'Mock Track',
|
||||
artist: 'Mock Artist',
|
||||
matchScore: '95%'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,22 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
// const { mixingRules } = require('../../shared/mixing-rules');
|
||||
|
||||
// Mock suggestions route
|
||||
router.get('/', async (req, res) => {
|
||||
const key = req.query.key;
|
||||
const bpm = parseInt(req.query.bpm) || 120;
|
||||
|
||||
// In production, this queries the PostgreSQL database populated with AcousticBrainz data
|
||||
// to find tracks matching the compatible Camelot keys.
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
data: [
|
||||
{ title: 'Suggestion 1', artist: 'Artist 1', key: '4A', bpm: bpm + 2 },
|
||||
{ title: 'Suggestion 2', artist: 'Artist 2', key: '3B', bpm: bpm - 1 }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,14 @@
|
||||
// Simple worker to simulate quota polling
|
||||
const ntfyUrl = process.env.NTFY_URL;
|
||||
|
||||
function checkQuota() {
|
||||
// In production, check API quotas for Last.fm, GetSongBPM, AcoustID
|
||||
// and send a push notification via ntfy if they run low.
|
||||
console.log('[Worker] Checking API quotas...');
|
||||
}
|
||||
|
||||
// Check daily
|
||||
setInterval(checkQuota, 24 * 60 * 60 * 1000);
|
||||
|
||||
// Run once on startup
|
||||
setTimeout(checkQuota, 5000);
|
||||
Reference in new issue
Block a user