feat: add endpoint to serve embedded cover art from audio files
All checks were successful
Build and Push Docker Image / docker (push) Successful in 9s

This commit is contained in:
2025-09-04 20:58:28 +02:00
parent 93f20ab1d5
commit 80f8c4ca90
2 changed files with 48 additions and 3 deletions

View File

@@ -1,9 +1,13 @@
import fs from 'fs';
import path from 'path';
import mime from 'mime';
import { parseFile as mmParseFile } from 'music-metadata';
import { DATA_DIR } from '../config.js';
export function registerAudioRoutes(app) {
// Simple in-memory cover cache: name -> { mime, buf }
const coverCache = new Map();
app.head('/audio/:name', (req, res) => {
const name = req.params.name;
const filePath = path.join(DATA_DIR, name);
@@ -55,4 +59,36 @@ export function registerAudioRoutes(app) {
fs.createReadStream(filePath).pipe(res);
}
});
// Serve embedded cover art from audio files, if present
app.get('/cover/:name', async (req, res) => {
try {
const name = req.params.name;
const filePath = path.join(DATA_DIR, name);
const resolved = path.resolve(filePath);
if (!resolved.startsWith(DATA_DIR)) return res.status(400).send('Invalid path');
if (!fs.existsSync(resolved)) return res.status(404).send('Not found');
if (coverCache.has(resolved)) {
const c = coverCache.get(resolved);
res.setHeader('Content-Type', c.mime || 'image/jpeg');
res.setHeader('Cache-Control', 'no-store');
return res.status(200).end(c.buf);
}
const meta = await mmParseFile(resolved, { duration: false });
const pic = meta.common?.picture?.[0];
if (!pic || !pic.data || !pic.data.length) {
return res.status(404).send('No cover');
}
const mimeType = pic.format || 'image/jpeg';
const buf = Buffer.from(pic.data);
coverCache.set(resolved, { mime: mimeType, buf });
res.setHeader('Content-Type', mimeType);
res.setHeader('Cache-Control', 'no-store');
return res.status(200).end(buf);
} catch (e) {
return res.status(500).send('Error reading cover');
}
});
}