feat(console): split audio files into music and beep folders

Add an Audio/music path for uploaded music, keep the existing general audio directory as the Beep group, and update the console and music page so the file manager now shows Configuration, Music, Beep, and Station with matching upload and preview routes.
This commit is contained in:
2026-08-01 07:18:14 +08:00
parent 1554adf5f1
commit 1f4be7c10b
3 changed files with 92 additions and 21 deletions
+59 -12
View File
@@ -39,6 +39,7 @@ if (!fs.existsSync(localHlsDir)) fs.mkdirSync(localHlsDir, { recursive: true });
const uploadDir = path.join(__dirname, 'public', 'uploads');
const audioRootDir = path.join(appRootDir, 'Audio');
const audioMusicDir = path.join(audioRootDir, 'music');
const audioGeneralDir = path.join(audioRootDir, 'general');
const audioStationDir = path.join(audioRootDir, 'station');
const listFilePath = path.join(appRootDir, 'list.txt');
@@ -48,6 +49,7 @@ const MUSIC_AUDIO_EXTENSIONS = [...STREAMABLE_AUDIO_EXTENSIONS, '.dfpwm'];
if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true });
if (!fs.existsSync(audioRootDir)) fs.mkdirSync(audioRootDir, { recursive: true });
if (!fs.existsSync(audioMusicDir)) fs.mkdirSync(audioMusicDir, { recursive: true });
if (!fs.existsSync(audioGeneralDir)) fs.mkdirSync(audioGeneralDir, { recursive: true });
if (!fs.existsSync(audioStationDir)) fs.mkdirSync(audioStationDir, { recursive: true });
if (!fs.existsSync(tempBroadcastDir)) fs.mkdirSync(tempBroadcastDir, { recursive: true });
@@ -57,6 +59,8 @@ const storage = multer.diskStorage({
destination: function (req, file, cb) {
if (file.fieldname === 'listFile') {
cb(null, appRootDir);
} else if (file.fieldname === 'musicAudioFiles') {
cb(null, audioMusicDir);
} else if (file.fieldname === 'generalAudioFiles') {
cb(null, audioGeneralDir);
} else if (file.fieldname === 'stationAudioFiles') {
@@ -106,6 +110,10 @@ app.get('/media/audio/general/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
sendBinaryFile(res, path.join(audioGeneralDir, name));
});
app.get('/media/audio/music/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
sendBinaryFile(res, path.join(audioMusicDir, name));
});
app.get('/media/audio/station/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
@@ -122,6 +130,7 @@ app.get('/media', (req, res) => {
success: true,
message: 'Use a concrete media path',
routes: [
'/media/audio/music/:file',
'/media/audio/general/:file',
'/media/audio/station/:file',
'/media/temp/:file'
@@ -481,15 +490,21 @@ function getManagedMediaUrl(kind, fileName) {
function listMusicTracks() {
const out = new Set();
if (!fs.existsSync(audioGeneralDir)) return [];
const scanDirs = [audioMusicDir];
if (fs.existsSync(audioMusicDir) && fs.readdirSync(audioMusicDir).length === 0) {
scanDirs.push(audioGeneralDir);
}
for (const file of fs.readdirSync(audioGeneralDir)) {
if (!file || file.startsWith('.')) continue;
const full = path.join(audioGeneralDir, file);
if (!fs.existsSync(full) || !fs.statSync(full).isFile()) continue;
if (!hasSupportedAudioExtension(file)) continue;
const base = normalizeBaseName(file);
if (base) out.add(base);
for (const dir of scanDirs) {
if (!fs.existsSync(dir)) continue;
for (const file of fs.readdirSync(dir)) {
if (!file || file.startsWith('.')) continue;
const full = path.join(dir, file);
if (!fs.existsSync(full) || !fs.statSync(full).isFile()) continue;
if (!hasSupportedAudioExtension(file)) continue;
const base = normalizeBaseName(file);
if (base) out.add(base);
}
}
return Array.from(out).sort((a, b) => a.localeCompare(b));
@@ -533,6 +548,7 @@ function getPlaybackAudioByBaseName(baseName) {
for (const ext of STREAMABLE_AUDIO_EXTENSIONS) {
const candidates = [
path.join(audioMusicDir, `${base}${ext}`),
path.join(audioGeneralDir, `${base}${ext}`),
path.join(audioStationDir, `${base}${ext}`)
];
@@ -546,13 +562,18 @@ function getPlaybackAudioByBaseName(baseName) {
return {
sourcePath: p,
audioFormat: 'stream',
streamUrl: getManagedMediaUrl(p.startsWith(audioStationDir) ? 'audio/station' : 'audio/general', path.basename(p)),
streamUrl: getManagedMediaUrl(
p.startsWith(audioStationDir) ? 'audio/station' :
(p.startsWith(audioMusicDir) ? 'audio/music' : 'audio/general'),
path.basename(p)
),
durationSec
};
}
}
for (const p of [
path.join(audioMusicDir, `${base}.dfpwm`),
path.join(audioGeneralDir, `${base}.dfpwm`),
path.join(audioStationDir, `${base}.dfpwm`)
]) {
@@ -725,7 +746,9 @@ function resolveManagedPath(inputPath) {
}
const prefixes = [
{ prefix: 'Audio/music/', baseDir: audioMusicDir, logicalPrefix: 'Audio/music/' },
{ prefix: 'Audio/general/', baseDir: audioGeneralDir, logicalPrefix: 'Audio/general/' },
{ prefix: 'uploads/audio/music/', baseDir: audioMusicDir, logicalPrefix: 'Audio/music/' },
{ prefix: 'uploads/audio/general/', baseDir: audioGeneralDir, logicalPrefix: 'Audio/general/' },
{ prefix: 'Audio/station/', baseDir: audioStationDir, logicalPrefix: 'Audio/station/' },
{ prefix: 'uploads/audio/station/', baseDir: audioStationDir, logicalPrefix: 'Audio/station/' }
@@ -758,6 +781,24 @@ function getManagedFiles() {
});
}
if (fs.existsSync(audioMusicDir)) {
fs.readdirSync(audioMusicDir).forEach(file => {
if (file.endsWith('.dfpwm')) {
const wavName = file.replace(/\.dfpwm$/, '.wav');
if (fs.existsSync(path.join(audioMusicDir, wavName))) return;
}
const filePath = path.join(audioMusicDir, file);
const stats = fs.statSync(filePath);
files.push({
name: file,
path: `Audio/music/${file}`,
type: 'music',
size: stats.size,
mtime: stats.mtimeMs
});
});
}
if (fs.existsSync(audioGeneralDir)) {
fs.readdirSync(audioGeneralDir).forEach(file => {
if (file.endsWith('.dfpwm')) {
@@ -769,7 +810,7 @@ function getManagedFiles() {
files.push({
name: file,
path: `Audio/general/${file}`,
type: 'general',
type: 'beep',
size: stats.size,
mtime: stats.mtimeMs
});
@@ -1847,8 +1888,14 @@ app.post('/api/upload/list', upload.single('listFile'), (req, res) => {
app.post('/api/upload/general-audio', upload.array('generalAudioFiles'), (req, res) => {
const files = req.files || [];
log('SUCCESS', `Uploaded ${files.length} general audio files`);
res.json({ success: true, message: `${files.length} general audio files uploaded` });
log('SUCCESS', `Uploaded ${files.length} beep audio files`);
res.json({ success: true, message: `${files.length} beep audio files uploaded` });
});
app.post('/api/upload/music-audio', upload.array('musicAudioFiles'), (req, res) => {
const files = req.files || [];
log('SUCCESS', `Uploaded ${files.length} music audio files`);
res.json({ success: true, message: `${files.length} music audio files uploaded` });
});
app.post('/api/upload/station-audio', upload.array('stationAudioFiles'), (req, res) => {