diff --git a/CloudServer/public/index.html b/CloudServer/public/index.html index b271f12..38d1875 100644 --- a/CloudServer/public/index.html +++ b/CloudServer/public/index.html @@ -813,6 +813,8 @@ + 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/')) { diff --git a/CloudServer/server.js b/CloudServer/server.js index 3b7aaac..3044a3c 100644 --- a/CloudServer/server.js +++ b/CloudServer/server.js @@ -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`);