fix(console): map Beep files to Audio/beep

Store and present Beep assets under Audio/beep while keeping legacy Audio/general reads working so existing files remain accessible during the directory transition.
This commit is contained in:
2026-08-01 11:42:38 +08:00
parent 545c19335f
commit c0f100a833
3 changed files with 60 additions and 21 deletions
+57 -20
View File
@@ -43,7 +43,8 @@ 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 audioBeepDir = path.join(audioRootDir, 'beep');
const audioGeneralLegacyDir = path.join(audioRootDir, 'general');
const audioStationDir = path.join(audioRootDir, 'station');
const listFilePath = path.join(appRootDir, 'list.txt');
const tempBroadcastDir = path.join(appRootDir, 'temp-broadcast');
@@ -54,10 +55,27 @@ 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(audioBeepDir)) fs.mkdirSync(audioBeepDir, { recursive: true });
if (!fs.existsSync(audioStationDir)) fs.mkdirSync(audioStationDir, { recursive: true });
if (!fs.existsSync(tempBroadcastDir)) fs.mkdirSync(tempBroadcastDir, { recursive: true });
function getBeepAudioDirs() {
const dirs = [audioBeepDir];
if (audioGeneralLegacyDir !== audioBeepDir && fs.existsSync(audioGeneralLegacyDir)) {
dirs.push(audioGeneralLegacyDir);
}
return dirs;
}
function sendBinaryFileFromCandidates(res, candidates) {
for (const filePath of candidates) {
if (fs.existsSync(filePath)) {
return res.sendFile(filePath);
}
}
return res.status(404).send('Not found');
}
// Multer storage configuration
const storage = multer.diskStorage({
destination: function (req, file, cb) {
@@ -68,7 +86,7 @@ const storage = multer.diskStorage({
} else if (file.fieldname === 'voiceAudioFiles') {
cb(null, audioVoiceDir);
} else if (file.fieldname === 'generalAudioFiles') {
cb(null, audioGeneralDir);
cb(null, audioBeepDir);
} else if (file.fieldname === 'stationAudioFiles') {
cb(null, audioStationDir);
} else {
@@ -114,7 +132,11 @@ function sendBinaryFile(res, filePath) {
app.get('/media/audio/general/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
sendBinaryFile(res, path.join(audioGeneralDir, name));
sendBinaryFileFromCandidates(res, getBeepAudioDirs().map(dir => path.join(dir, name)));
});
app.get('/media/audio/beep/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
sendBinaryFileFromCandidates(res, getBeepAudioDirs().map(dir => path.join(dir, name)));
});
app.get('/media/audio/music/:file', (req, res) => {
const name = path.basename(String(req.params.file || ''));
@@ -142,6 +164,7 @@ app.get('/media', (req, res) => {
routes: [
'/media/audio/music/:file',
'/media/audio/voice/:file',
'/media/audio/beep/:file',
'/media/audio/general/:file',
'/media/audio/station/:file',
'/media/temp/:file'
@@ -585,7 +608,7 @@ function listMusicTracks() {
const out = new Set();
const scanDirs = [audioMusicDir];
if (fs.existsSync(audioMusicDir) && fs.readdirSync(audioMusicDir).length === 0) {
scanDirs.push(audioGeneralDir);
scanDirs.push(...getBeepAudioDirs());
}
for (const dir of scanDirs) {
@@ -642,7 +665,7 @@ function getPlaybackAudioByBaseName(baseName) {
for (const ext of STREAMABLE_AUDIO_EXTENSIONS) {
const candidates = [
path.join(audioMusicDir, `${base}${ext}`),
path.join(audioGeneralDir, `${base}${ext}`),
...getBeepAudioDirs().map(dir => path.join(dir, `${base}${ext}`)),
path.join(audioStationDir, `${base}${ext}`)
];
for (const p of candidates) {
@@ -657,7 +680,7 @@ function getPlaybackAudioByBaseName(baseName) {
audioFormat: 'stream',
streamUrl: getManagedMediaUrl(
p.startsWith(audioStationDir) ? 'audio/station' :
(p.startsWith(audioMusicDir) ? 'audio/music' : 'audio/general'),
(p.startsWith(audioMusicDir) ? 'audio/music' : 'audio/beep'),
path.basename(p)
),
durationSec
@@ -667,7 +690,7 @@ function getPlaybackAudioByBaseName(baseName) {
for (const p of [
path.join(audioMusicDir, `${base}.dfpwm`),
path.join(audioGeneralDir, `${base}.dfpwm`),
...getBeepAudioDirs().map(dir => path.join(dir, `${base}.dfpwm`)),
path.join(audioStationDir, `${base}.dfpwm`)
]) {
if (!fs.existsSync(p)) continue;
@@ -838,17 +861,27 @@ function resolveManagedPath(inputPath) {
return { logicalPath: 'list.txt', fullPath: listFilePath };
}
if (normalized === 'Audio/general/line.txt' || normalized === 'uploads/audio/general/line.txt') {
return { logicalPath: 'Audio/general/line.txt', fullPath: path.join(audioGeneralDir, 'line.txt') };
if (
normalized === 'Audio/beep/line.txt' ||
normalized === 'uploads/audio/beep/line.txt' ||
normalized === 'Audio/general/line.txt' ||
normalized === 'uploads/audio/general/line.txt'
) {
const fullPath = fs.existsSync(path.join(audioBeepDir, 'line.txt'))
? path.join(audioBeepDir, 'line.txt')
: path.join(audioGeneralLegacyDir, 'line.txt');
return { logicalPath: 'Audio/beep/line.txt', fullPath };
}
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: 'Audio/beep/', baseDir: audioBeepDir, logicalPrefix: 'Audio/beep/' },
{ prefix: 'Audio/general/', baseDir: audioGeneralLegacyDir, logicalPrefix: 'Audio/beep/' },
{ 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: 'uploads/audio/beep/', baseDir: audioBeepDir, logicalPrefix: 'Audio/beep/' },
{ prefix: 'uploads/audio/general/', baseDir: audioGeneralLegacyDir, logicalPrefix: 'Audio/beep/' },
{ prefix: 'Audio/station/', baseDir: audioStationDir, logicalPrefix: 'Audio/station/' },
{ prefix: 'uploads/audio/station/', baseDir: audioStationDir, logicalPrefix: 'Audio/station/' }
];
@@ -916,17 +949,21 @@ function getManagedFiles() {
});
}
if (fs.existsSync(audioGeneralDir)) {
fs.readdirSync(audioGeneralDir).forEach(file => {
const beepSeen = new Set();
for (const dir of getBeepAudioDirs()) {
if (!fs.existsSync(dir)) continue;
fs.readdirSync(dir).forEach(file => {
if (beepSeen.has(file)) return;
beepSeen.add(file);
if (file.endsWith('.dfpwm')) {
const wavName = file.replace(/\.dfpwm$/, '.wav');
if (fs.existsSync(path.join(audioGeneralDir, wavName))) return;
if (beepSeen.has(wavName) || fs.existsSync(path.join(dir, wavName))) return;
}
const filePath = path.join(audioGeneralDir, file);
const filePath = path.join(dir, file);
const stats = fs.statSync(filePath);
files.push({
name: file,
path: `Audio/general/${file}`,
path: `Audio/beep/${file}`,
type: file === 'line.txt' ? 'config' : 'beep',
size: stats.size,
mtime: stats.mtimeMs
@@ -1183,7 +1220,7 @@ function buildConcatListFile(seq) {
const candidates = [
path.join(audioStationDir, `${item}.wav`),
path.join(audioGeneralDir, `${item}.wav`)
...getBeepAudioDirs().map(dir => path.join(dir, `${item}.wav`))
];
const found = candidates.find(p => fs.existsSync(p));
@@ -2527,7 +2564,7 @@ app.post('/api/tts/import', express.json(), (req, res) => {
const jaSource = path.join(uploadDir, jaTemp);
const safeStationCode = String(stationCode).replace(/\//g, '-');
const targetDir = type === 'line' ? audioGeneralDir : audioStationDir;
const targetDir = type === 'line' ? audioBeepDir : audioStationDir;
const zhDest = path.join(targetDir, `ch-${safeStationCode}.wav`);
const jaDest = path.join(targetDir, `ja-${safeStationCode}.wav`);
@@ -2558,7 +2595,7 @@ function findWavByBaseName(baseName) {
const safeBase = String(baseName || '').trim().replace(/\.wav$/i, '');
if (!safeBase) return null;
const candidates = [
path.join(audioGeneralDir, `${safeBase}.wav`),
...getBeepAudioDirs().map(dir => path.join(dir, `${safeBase}.wav`)),
path.join(audioStationDir, `${safeBase}.wav`),
path.join(uploadDir, `${safeBase}.wav`)
];