fix(tts): rename japanese station audio suffix to ja
Switch generated station audio entries from en-* to ja-* and update the generator flow so Chinese plus Japanese audio imports use the new suffix consistently.
This commit is contained in:
@@ -566,8 +566,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>English Station Name (e.g., Beijing)</label>
|
<label>Japanese Station Name (e.g., ちば)</label>
|
||||||
<input type="text" id="en-text" placeholder="Enter English Name">
|
<input type="text" id="en-text" placeholder="输入日文站名">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-primary" id="btn-generate" onclick="generateAudio()">
|
<button class="btn btn-primary" id="btn-generate" onclick="generateAudio()">
|
||||||
@@ -584,7 +584,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>English Preview</label>
|
<label>Japanese Preview</label>
|
||||||
<div class="audio-preview">
|
<div class="audio-preview">
|
||||||
<audio id="en-audio" controls></audio>
|
<audio id="en-audio" controls></audio>
|
||||||
</div>
|
</div>
|
||||||
@@ -605,8 +605,8 @@
|
|||||||
<!-- Batch Generation View -->
|
<!-- Batch Generation View -->
|
||||||
<div id="view-batch" style="display: none; flex-direction: column; gap: 20px;">
|
<div id="view-batch" style="display: none; flex-direction: column; gap: 20px;">
|
||||||
<div class="form-group" id="batch-input-group">
|
<div class="form-group" id="batch-input-group">
|
||||||
<label>Paste Data (Format: ID <Tab> Chinese <Tab> English)</label>
|
<label>Paste Data (Format: ID <Tab> Chinese <Tab> Japanese)</label>
|
||||||
<textarea id="batch-input" rows="8" placeholder="01-01	千叶	Chiba CK-01	沼津	Numazu" style="resize: vertical; font-family: var(--font-mono); white-space: pre;"></textarea>
|
<textarea id="batch-input" rows="8" placeholder="01-01	千叶	ちば CK-01	沼津	ぬまづ" style="resize: vertical; font-family: var(--font-mono); white-space: pre;"></textarea>
|
||||||
<button class="btn btn-primary" id="btn-batch-preview" onclick="previewBatch()" style="margin-top: 10px;">
|
<button class="btn btn-primary" id="btn-batch-preview" onclick="previewBatch()" style="margin-top: 10px;">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
||||||
Preview Tasks
|
Preview Tasks
|
||||||
@@ -721,10 +721,10 @@
|
|||||||
<script>
|
<script>
|
||||||
const TTS_MODEL = "s2.1-pro-free";
|
const TTS_MODEL = "s2.1-pro-free";
|
||||||
const ZH_REFERENCE_ID = "0aee9f0f5683480d987c9e3c5d2de04c";
|
const ZH_REFERENCE_ID = "0aee9f0f5683480d987c9e3c5d2de04c";
|
||||||
const EN_REFERENCE_ID = "38985e545cff41b79c11c32b0196c9a0";
|
const JA_REFERENCE_ID = "38985e545cff41b79c11c32b0196c9a0";
|
||||||
|
|
||||||
let zhTempFile = null;
|
let zhTempFile = null;
|
||||||
let enTempFile = null;
|
let jaTempFile = null;
|
||||||
|
|
||||||
let listData = [];
|
let listData = [];
|
||||||
let availableFiles = [];
|
let availableFiles = [];
|
||||||
@@ -829,11 +829,11 @@
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _importSingleAudio(code, zhTemp, enTemp) {
|
async function _importSingleAudio(code, zhTemp, jaTemp) {
|
||||||
const res = await fetch('/api/tts/import', {
|
const res = await fetch('/api/tts/import', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({ stationCode: code, zhTemp, enTemp })
|
body: JSON.stringify({ stationCode: code, zhTemp, jaTemp })
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!data.success) throw new Error(data.message);
|
if (!data.success) throw new Error(data.message);
|
||||||
@@ -878,9 +878,9 @@
|
|||||||
batchTasks.forEach((task, index) => {
|
batchTasks.forEach((task, index) => {
|
||||||
// Initialize temp vars
|
// Initialize temp vars
|
||||||
task.zhTempName = null;
|
task.zhTempName = null;
|
||||||
task.enTempName = null;
|
task.jaTempName = null;
|
||||||
task.zhUrl = null;
|
task.zhUrl = null;
|
||||||
task.enUrl = null;
|
task.jaUrl = null;
|
||||||
|
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'edit-file-item';
|
item.className = 'edit-file-item';
|
||||||
@@ -892,7 +892,7 @@
|
|||||||
<div style="display:flex; justify-content:space-between; align-items:flex-start;">
|
<div style="display:flex; justify-content:space-between; align-items:flex-start;">
|
||||||
<div style="display:flex; flex-direction:column; gap:2px;">
|
<div style="display:flex; flex-direction:column; gap:2px;">
|
||||||
<span style="font-weight:600; color:var(--accent)">${task.code}</span>
|
<span style="font-weight:600; color:var(--accent)">${task.code}</span>
|
||||||
<span class="edit-file-name" style="color:var(--text-muted)">ZH: ${task.zh} | EN: ${task.en}</span>
|
<span class="edit-file-name" style="color:var(--text-muted)">ZH: ${task.zh} | JA: ${task.en}</span>
|
||||||
</div>
|
</div>
|
||||||
<span id="batch-status-span-${index}" style="font-size:0.8rem; color:var(--text-muted)">Pending</span>
|
<span id="batch-status-span-${index}" style="font-size:0.8rem; color:var(--text-muted)">Pending</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -902,8 +902,8 @@
|
|||||||
<audio id="batch-audio-zh-${index}" controls style="height:24px;"></audio>
|
<audio id="batch-audio-zh-${index}" controls style="height:24px;"></audio>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex; align-items:center; gap:8px;">
|
<div style="display:flex; align-items:center; gap:8px;">
|
||||||
<span style="font-size:0.75rem; color:var(--text-muted); width:20px;">EN</span>
|
<span style="font-size:0.75rem; color:var(--text-muted); width:20px;">JA</span>
|
||||||
<audio id="batch-audio-en-${index}" controls style="height:24px;"></audio>
|
<audio id="batch-audio-ja-${index}" controls style="height:24px;"></audio>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -957,14 +957,14 @@
|
|||||||
while (retryCount < 2 && !success) {
|
while (retryCount < 2 && !success) {
|
||||||
try {
|
try {
|
||||||
// 1. Generate ZH
|
// 1. Generate ZH
|
||||||
const zhData = await _generateSingleTTS(task.zh, ZH_MODEL);
|
const zhData = await _generateSingleTTS(task.zh, ZH_REFERENCE_ID);
|
||||||
// 2. Generate EN
|
// 2. Generate JA
|
||||||
const enData = await _generateSingleTTS(task.en, EN_MODEL);
|
const jaData = await _generateSingleTTS(task.en, JA_REFERENCE_ID);
|
||||||
|
|
||||||
task.zhTempName = zhData.tempName;
|
task.zhTempName = zhData.tempName;
|
||||||
task.enTempName = enData.tempName;
|
task.jaTempName = jaData.jaTempName || jaData.enTempName || jaData.tempName;
|
||||||
task.zhUrl = zhData.url;
|
task.zhUrl = zhData.url;
|
||||||
task.enUrl = enData.url;
|
task.jaUrl = jaData.jaUrl || jaData.enUrl || jaData.url;
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -987,7 +987,7 @@
|
|||||||
const previewDiv = document.getElementById(`batch-audio-preview-${i}`);
|
const previewDiv = document.getElementById(`batch-audio-preview-${i}`);
|
||||||
previewDiv.style.display = 'flex';
|
previewDiv.style.display = 'flex';
|
||||||
document.getElementById(`batch-audio-zh-${i}`).src = task.zhUrl;
|
document.getElementById(`batch-audio-zh-${i}`).src = task.zhUrl;
|
||||||
document.getElementById(`batch-audio-en-${i}`).src = task.enUrl;
|
document.getElementById(`batch-audio-ja-${i}`).src = task.jaUrl;
|
||||||
|
|
||||||
successCount++;
|
successCount++;
|
||||||
} else {
|
} else {
|
||||||
@@ -1031,7 +1031,7 @@
|
|||||||
|
|
||||||
for (let i = 0; i < batchTasks.length; i++) {
|
for (let i = 0; i < batchTasks.length; i++) {
|
||||||
const task = batchTasks[i];
|
const task = batchTasks[i];
|
||||||
if (!task.zhTempName || !task.enTempName) continue; // Skip failed ones
|
if (!task.zhTempName || !task.jaTempName) continue; // Skip failed ones
|
||||||
|
|
||||||
// Replace slashes with dashes to ensure filesystem compatibility
|
// Replace slashes with dashes to ensure filesystem compatibility
|
||||||
if (task.code.includes('/')) {
|
if (task.code.includes('/')) {
|
||||||
@@ -1044,10 +1044,10 @@
|
|||||||
statusSpan.style.color = 'var(--warning)';
|
statusSpan.style.color = 'var(--warning)';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _importSingleAudio(task.code, task.zhTempName, task.enTempName);
|
await _importSingleAudio(task.code, task.zhTempName, task.jaTempName);
|
||||||
|
|
||||||
// Update listData
|
// Update listData
|
||||||
const filesStr = `ch-${task.code}.wav,en-${task.code}.wav`;
|
const filesStr = `ch-${task.code}.wav,ja-${task.code}.wav`;
|
||||||
const existingIndex = listData.findIndex(item => item.code === task.code);
|
const existingIndex = listData.findIndex(item => item.code === task.code);
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
listData[existingIndex].files = filesStr.split(',');
|
listData[existingIndex].files = filesStr.split(',');
|
||||||
@@ -1088,10 +1088,10 @@
|
|||||||
|
|
||||||
async function generateAudio() {
|
async function generateAudio() {
|
||||||
const zhText = document.getElementById('zh-text').value.trim();
|
const zhText = document.getElementById('zh-text').value.trim();
|
||||||
const enText = document.getElementById('en-text').value.trim();
|
const jaText = document.getElementById('en-text').value.trim();
|
||||||
|
|
||||||
if (!zhText || !enText) {
|
if (!zhText || !jaText) {
|
||||||
showToast("Error", "Please enter both Chinese and English names");
|
showToast("Error", "Please enter both Chinese and Japanese names");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1119,30 +1119,30 @@
|
|||||||
const dataZh = await resZh.json();
|
const dataZh = await resZh.json();
|
||||||
if (!dataZh.success) throw new Error("ZH TTS failed: " + dataZh.message);
|
if (!dataZh.success) throw new Error("ZH TTS failed: " + dataZh.message);
|
||||||
|
|
||||||
status.textContent = "Generating English audio...";
|
status.textContent = "Generating Japanese audio...";
|
||||||
|
|
||||||
// Generate English via Server-Side Proxy
|
// Generate Japanese via Server-Side Proxy
|
||||||
const resEn = await fetch('/api/tts/generate', {
|
const resJa = await fetch('/api/tts/generate', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
text: enText,
|
text: jaText,
|
||||||
reference_id: EN_REFERENCE_ID,
|
reference_id: JA_REFERENCE_ID,
|
||||||
model: TTS_MODEL
|
model: TTS_MODEL
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const dataEn = await resEn.json();
|
const dataJa = await resJa.json();
|
||||||
if (!dataEn.success) throw new Error("EN TTS failed: " + dataEn.message);
|
if (!dataJa.success) throw new Error("JA TTS failed: " + dataJa.message);
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
zhTempFile = dataZh.tempName;
|
zhTempFile = dataZh.tempName;
|
||||||
enTempFile = dataEn.tempName;
|
jaTempFile = dataJa.jaTempName || dataJa.enTempName || dataJa.tempName;
|
||||||
|
|
||||||
document.getElementById('zh-audio').src = dataZh.url;
|
document.getElementById('zh-audio').src = dataZh.url;
|
||||||
document.getElementById('en-audio').src = dataEn.url;
|
document.getElementById('en-audio').src = dataJa.jaUrl || dataJa.enUrl || dataJa.url;
|
||||||
|
|
||||||
document.getElementById('preview-section').style.display = 'block';
|
document.getElementById('preview-section').style.display = 'block';
|
||||||
status.textContent = "Generation complete!";
|
status.textContent = "Generation complete!";
|
||||||
@@ -1170,7 +1170,7 @@
|
|||||||
code = code.replace(/\//g, '-');
|
code = code.replace(/\//g, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!zhTempFile || !enTempFile) {
|
if (!zhTempFile || !jaTempFile) {
|
||||||
showToast("Error", "No audio generated to import");
|
showToast("Error", "No audio generated to import");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1186,7 +1186,7 @@
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
stationCode: code,
|
stationCode: code,
|
||||||
zhTemp: zhTempFile,
|
zhTemp: zhTempFile,
|
||||||
enTemp: enTempFile
|
jaTemp: jaTempFile
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1196,7 +1196,7 @@
|
|||||||
showToast("Success", "Files imported to Station folder!");
|
showToast("Success", "Files imported to Station folder!");
|
||||||
|
|
||||||
// Automatically add/update to listData if generated via this panel
|
// Automatically add/update to listData if generated via this panel
|
||||||
const filesStr = `ch-${code}.wav,en-${code}.wav`;
|
const filesStr = `ch-${code}.wav,ja-${code}.wav`;
|
||||||
const existingIndex = listData.findIndex(item => item.code === code);
|
const existingIndex = listData.findIndex(item => item.code === code);
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
listData[existingIndex].files = filesStr.split(',');
|
listData[existingIndex].files = filesStr.split(',');
|
||||||
@@ -1209,7 +1209,7 @@
|
|||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
zhTempFile = null;
|
zhTempFile = null;
|
||||||
enTempFile = null;
|
jaTempFile = null;
|
||||||
document.getElementById('preview-section').style.display = 'none';
|
document.getElementById('preview-section').style.display = 'none';
|
||||||
document.getElementById('gen-status').textContent = "";
|
document.getElementById('gen-status').textContent = "";
|
||||||
document.getElementById('zh-text').value = "";
|
document.getElementById('zh-text').value = "";
|
||||||
|
|||||||
+18
-13
@@ -18,7 +18,7 @@ const wss = new WebSocket.Server({ server });
|
|||||||
const PORT = Number(process.env.PORT || '8999');
|
const PORT = Number(process.env.PORT || '8999');
|
||||||
const DEFAULT_TTS_MODEL = process.env.TTS_MODEL || 's2.1-pro-free';
|
const DEFAULT_TTS_MODEL = process.env.TTS_MODEL || 's2.1-pro-free';
|
||||||
const DEFAULT_ZH_REFERENCE_ID = process.env.ZH_REFERENCE_ID || '0aee9f0f5683480d987c9e3c5d2de04c';
|
const DEFAULT_ZH_REFERENCE_ID = process.env.ZH_REFERENCE_ID || '0aee9f0f5683480d987c9e3c5d2de04c';
|
||||||
const DEFAULT_EN_REFERENCE_ID = process.env.EN_REFERENCE_ID || '38985e545cff41b79c11c32b0196c9a0';
|
const DEFAULT_JA_REFERENCE_ID = process.env.JA_REFERENCE_ID || process.env.EN_REFERENCE_ID || '38985e545cff41b79c11c32b0196c9a0';
|
||||||
|
|
||||||
const HLS_ENABLE = process.env.HLS_ENABLE !== '0';
|
const HLS_ENABLE = process.env.HLS_ENABLE !== '0';
|
||||||
const HLS_STREAM_MODE = (process.env.HLS_STREAM_MODE || 'event').toLowerCase();
|
const HLS_STREAM_MODE = (process.env.HLS_STREAM_MODE || 'event').toLowerCase();
|
||||||
@@ -2375,20 +2375,23 @@ app.post('/api/tts/upload_temp', upload.fields([{ name: 'zhAudio', maxCount: 1 }
|
|||||||
|
|
||||||
// Rename to secure temp names
|
// Rename to secure temp names
|
||||||
const zhTempName = `temp_zh_${Date.now()}_${Math.floor(Math.random() * 1000)}.wav`;
|
const zhTempName = `temp_zh_${Date.now()}_${Math.floor(Math.random() * 1000)}.wav`;
|
||||||
const enTempName = `temp_en_${Date.now()}_${Math.floor(Math.random() * 1000)}.wav`;
|
const jaTempName = `temp_ja_${Date.now()}_${Math.floor(Math.random() * 1000)}.wav`;
|
||||||
|
|
||||||
const zhTempPath = path.join(uploadDir, zhTempName);
|
const zhTempPath = path.join(uploadDir, zhTempName);
|
||||||
const enTempPath = path.join(uploadDir, enTempName);
|
const jaTempPath = path.join(uploadDir, jaTempName);
|
||||||
|
|
||||||
fs.renameSync(zhFile.path, zhTempPath);
|
fs.renameSync(zhFile.path, zhTempPath);
|
||||||
fs.renameSync(enFile.path, enTempPath);
|
fs.renameSync(enFile.path, jaTempPath);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
zhTempName,
|
zhTempName,
|
||||||
enTempName,
|
jaTempName,
|
||||||
zhUrl: `/uploads/${zhTempName}`,
|
zhUrl: `/uploads/${zhTempName}`,
|
||||||
enUrl: `/uploads/${enTempName}`
|
jaUrl: `/uploads/${jaTempName}`,
|
||||||
|
// Keep old keys temporarily so cached frontends do not break.
|
||||||
|
enTempName: jaTempName,
|
||||||
|
enUrl: `/uploads/${jaTempName}`
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(500).json({ success: false, message: e.message });
|
res.status(500).json({ success: false, message: e.message });
|
||||||
@@ -2397,23 +2400,25 @@ app.post('/api/tts/upload_temp', upload.fields([{ name: 'zhAudio', maxCount: 1 }
|
|||||||
|
|
||||||
// Import TTS temp files to station folder
|
// Import TTS temp files to station folder
|
||||||
app.post('/api/tts/import', express.json(), (req, res) => {
|
app.post('/api/tts/import', express.json(), (req, res) => {
|
||||||
const { stationCode, zhTemp, enTemp } = req.body;
|
const stationCode = String(req.body?.stationCode || '').trim();
|
||||||
if (!stationCode || !zhTemp || !enTemp) return res.status(400).json({ success: false, message: 'Missing parameters' });
|
const zhTemp = String(req.body?.zhTemp || '').trim();
|
||||||
|
const jaTemp = String(req.body?.jaTemp || req.body?.enTemp || '').trim();
|
||||||
|
if (!stationCode || !zhTemp || !jaTemp) return res.status(400).json({ success: false, message: 'Missing parameters' });
|
||||||
|
|
||||||
const zhSource = path.join(uploadDir, zhTemp);
|
const zhSource = path.join(uploadDir, zhTemp);
|
||||||
const enSource = path.join(uploadDir, enTemp);
|
const jaSource = path.join(uploadDir, jaTemp);
|
||||||
const safeStationCode = String(stationCode).replace(/\//g, '-');
|
const safeStationCode = String(stationCode).replace(/\//g, '-');
|
||||||
const zhDest = path.join(audioStationDir, `ch-${safeStationCode}.wav`);
|
const zhDest = path.join(audioStationDir, `ch-${safeStationCode}.wav`);
|
||||||
const enDest = path.join(audioStationDir, `en-${safeStationCode}.wav`);
|
const jaDest = path.join(audioStationDir, `ja-${safeStationCode}.wav`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(zhSource)) {
|
if (fs.existsSync(zhSource)) {
|
||||||
fs.copyFileSync(zhSource, zhDest);
|
fs.copyFileSync(zhSource, zhDest);
|
||||||
fs.unlinkSync(zhSource);
|
fs.unlinkSync(zhSource);
|
||||||
}
|
}
|
||||||
if (fs.existsSync(enSource)) {
|
if (fs.existsSync(jaSource)) {
|
||||||
fs.copyFileSync(enSource, enDest);
|
fs.copyFileSync(jaSource, jaDest);
|
||||||
fs.unlinkSync(enSource);
|
fs.unlinkSync(jaSource);
|
||||||
}
|
}
|
||||||
log('SUCCESS', `Imported generated audio for station ${stationCode}`);
|
log('SUCCESS', `Imported generated audio for station ${stationCode}`);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user