feat(console): add voice folder to file manager

Add an Audio/voice directory with upload and preview support, and expose it in the console file manager so the structure now shows Configuration, Music, Voice, Beep, and Station.
This commit is contained in:
2026-08-01 07:21:47 +08:00
parent 1f4be7c10b
commit fd53eaef97
2 changed files with 43 additions and 0 deletions
+8
View File
@@ -813,6 +813,8 @@
<input type="file" id="upload-config" accept=".txt" onchange="performUpload('listFile', '/api/upload/list', this)">
<input type="file" id="upload-music" multiple accept=".wav,.mp3,.ogg,.flac,.m4a,.aac,.opus,.dfpwm"
onchange="performUpload('musicAudioFiles', '/api/upload/music-audio', this)">
<input type="file" id="upload-voice" multiple accept=".wav,.mp3,.ogg,.flac,.m4a,.aac,.opus,.dfpwm"
onchange="performUpload('voiceAudioFiles', '/api/upload/voice-audio', this)">
<input type="file" id="upload-general" multiple accept=".wav,.mp3,.ogg,.flac,.m4a,.aac,.opus,.dfpwm"
onchange="performUpload('generalAudioFiles', '/api/upload/general-audio', this)">
<input type="file" id="upload-station" multiple accept=".wav,.mp3,.ogg,.flac,.m4a,.aac,.opus,.dfpwm"
@@ -1143,6 +1145,7 @@
const nameMap = {
config: 'Configuration',
music: 'Music',
voice: 'Voice',
beep: 'Beep',
station: 'Station'
};
@@ -1160,6 +1163,7 @@
const folders = [
{ id: 'config', name: 'Configuration', count: allFiles.filter(f => f.type === 'config').length },
{ id: 'music', name: 'Music', count: allFiles.filter(f => f.type === 'music').length },
{ id: 'voice', name: 'Voice', count: allFiles.filter(f => f.type === 'voice').length },
{ id: 'beep', name: 'Beep', count: allFiles.filter(f => f.type === 'beep').length },
{ id: 'station', name: 'Station', count: allFiles.filter(f => f.type === 'station').length }
];
@@ -1187,6 +1191,7 @@
const typeLabelMap = {
config: 'Configuration',
music: 'Music',
voice: 'Voice',
beep: 'Beep',
station: 'Station'
};
@@ -1264,6 +1269,7 @@
function triggerUpload() {
if (currentPath === 'config') document.getElementById('upload-config').click();
else if (currentPath === 'music') document.getElementById('upload-music').click();
else if (currentPath === 'voice') document.getElementById('upload-voice').click();
else if (currentPath === 'beep') document.getElementById('upload-general').click();
else if (currentPath === 'station') document.getElementById('upload-station').click();
}
@@ -1366,6 +1372,8 @@
let source = '/' + path;
if (path.startsWith('Audio/music/')) {
source = `/media/audio/music/${encodeURIComponent(path.slice('Audio/music/'.length))}`;
} else if (path.startsWith('Audio/voice/')) {
source = `/media/audio/voice/${encodeURIComponent(path.slice('Audio/voice/'.length))}`;
} else if (path.startsWith('Audio/general/')) {
source = `/media/audio/general/${encodeURIComponent(path.slice('Audio/general/'.length))}`;
} else if (path.startsWith('Audio/station/')) {
+35
View File
@@ -40,6 +40,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 audioVoiceDir = path.join(audioRootDir, 'voice');
const audioGeneralDir = path.join(audioRootDir, 'general');
const audioStationDir = path.join(audioRootDir, 'station');
const listFilePath = path.join(appRootDir, 'list.txt');
@@ -50,6 +51,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(audioVoiceDir)) fs.mkdirSync(audioVoiceDir, { 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 });
@@ -61,6 +63,8 @@ const storage = multer.diskStorage({
cb(null, appRootDir);
} else if (file.fieldname === 'musicAudioFiles') {
cb(null, audioMusicDir);
} else if (file.fieldname === 'voiceAudioFiles') {
cb(null, audioVoiceDir);
} else if (file.fieldname === 'generalAudioFiles') {
cb(null, audioGeneralDir);
} else if (file.fieldname === 'stationAudioFiles') {
@@ -114,6 +118,10 @@ 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/voice/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
sendBinaryFile(res, path.join(audioVoiceDir, name));
});
app.get('/media/audio/station/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
@@ -131,6 +139,7 @@ app.get('/media', (req, res) => {
message: 'Use a concrete media path',
routes: [
'/media/audio/music/:file',
'/media/audio/voice/:file',
'/media/audio/general/:file',
'/media/audio/station/:file',
'/media/temp/:file'
@@ -747,8 +756,10 @@ function resolveManagedPath(inputPath) {
const prefixes = [
{ prefix: 'Audio/music/', baseDir: audioMusicDir, logicalPrefix: 'Audio/music/' },
{ prefix: 'Audio/voice/', baseDir: audioVoiceDir, logicalPrefix: 'Audio/voice/' },
{ prefix: 'Audio/general/', baseDir: audioGeneralDir, logicalPrefix: 'Audio/general/' },
{ prefix: 'uploads/audio/music/', baseDir: audioMusicDir, logicalPrefix: 'Audio/music/' },
{ prefix: 'uploads/audio/voice/', baseDir: audioVoiceDir, logicalPrefix: 'Audio/voice/' },
{ 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/' }
@@ -799,6 +810,24 @@ function getManagedFiles() {
});
}
if (fs.existsSync(audioVoiceDir)) {
fs.readdirSync(audioVoiceDir).forEach(file => {
if (file.endsWith('.dfpwm')) {
const wavName = file.replace(/\.dfpwm$/, '.wav');
if (fs.existsSync(path.join(audioVoiceDir, wavName))) return;
}
const filePath = path.join(audioVoiceDir, file);
const stats = fs.statSync(filePath);
files.push({
name: file,
path: `Audio/voice/${file}`,
type: 'voice',
size: stats.size,
mtime: stats.mtimeMs
});
});
}
if (fs.existsSync(audioGeneralDir)) {
fs.readdirSync(audioGeneralDir).forEach(file => {
if (file.endsWith('.dfpwm')) {
@@ -1898,6 +1927,12 @@ app.post('/api/upload/music-audio', upload.array('musicAudioFiles'), (req, res)
res.json({ success: true, message: `${files.length} music audio files uploaded` });
});
app.post('/api/upload/voice-audio', upload.array('voiceAudioFiles'), (req, res) => {
const files = req.files || [];
log('SUCCESS', `Uploaded ${files.length} voice audio files`);
res.json({ success: true, message: `${files.length} voice audio files uploaded` });
});
app.post('/api/upload/station-audio', upload.array('stationAudioFiles'), (req, res) => {
const files = req.files || [];
log('SUCCESS', `Uploaded ${files.length} station audio files`);