t2 / templates /index.html
99i's picture
Create templates/index.html
c4d8226 verified
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TTS 语音合成服务</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
text-align: center;
}
.filter-section {
margin-bottom: 20px;
padding: 15px;
background-color: #f0f8ff;
border-radius: 5px;
}
.voice-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.voice-card {
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
background-color: white;
}
.voice-card h3 {
margin-top: 0;
color: #2c3e50;
}
.voice-card p {
margin: 5px 0;
}
.style-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-top: 10px;
}
.style-tag {
background-color: #e0f7fa;
padding: 3px 8px;
border-radius: 10px;
font-size: 12px;
}
select,
button {
padding: 8px 12px;
margin-right: 10px;
border-radius: 4px;
border: 1px solid #ddd;
}
button {
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="container">
<h1>TTS 语音合成服务</h1>
<div class="container" style="margin-top: 30px;">
<h2>语音合成</h2>
<div class="filter-section">
<div style="margin-bottom: 15px;">
<label for="synthesis-text">输入文本:</label>
<textarea id="synthesis-text" rows="4"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd;"></textarea>
</div>
<div style="margin-bottom: 15px;">
<label for="synthesis-voice">选择音色:</label>
<select id="synthesis-voice" style="width: 200px;">
<!-- 音色选项将通过JavaScript动态加载 -->
</select>
</div>
<div style="margin-bottom: 15px;">
<label for="rate-slider">语速: <span id="rate-value">1.0</span></label>
<input type="range" id="rate-slider" min="0.5" max="2.0" step="0.1" value="1.0"
style="width: 200px;" oninput="document.getElementById('rate-value').textContent = this.value">
</div>
<div style="margin-bottom: 15px;">
<label for="pitch-slider">音调: <span id="pitch-value">0</span></label>
<input type="range" id="pitch-slider" min="-12" max="12" step="1" value="0" style="width: 200px;"
oninput="document.getElementById('pitch-value').textContent = this.value">
</div>
<button onclick="synthesizeSpeech()">合成语音</button>
<audio id="audio-player" controls style="display: none; margin-top: 15px; width: 100%;"></audio>
</div>
<div class="filter-section">
<label for="gender-filter">按性别筛选:</label>
<select id="gender-filter">
<option value="all">全部</option>
<option value="Female">女声</option>
<option value="Male">男声</option>
</select>
<button id="apply-filter">应用筛选</button>
</div>
<div class="voice-list" id="voice-list">
<!-- 语音列表将通过JavaScript动态加载 -->
</div>
</div>
</div>
<script>
async function loadVoices(gender = 'all') {
try {
const response = await fetch('/voices?l=zh&d=true');
let voices = await response.json();
voices = voices['voices']
const filteredVoices = gender === 'all'
? voices
: voices.filter(voice => voice.Gender === gender);
const voiceList = document.getElementById('voice-list');
voiceList.innerHTML = '';
filteredVoices.forEach(voice => {
const voiceCard = document.createElement('div');
voiceCard.className = 'voice-card';
voiceCard.innerHTML = `
<h3>${voice.LocalName} (${voice.DisplayName})</h3>
<p><strong>性别:</strong> ${voice.Gender === 'Female' ? '女' : '男'}</p>
<p><strong>语言:</strong> ${voice.LocaleName}</p>
<p><strong>短名称:</strong> ${voice.ShortName}</p>
<div class="style-list">
${voice.StyleList ? voice.StyleList.map(style =>
`<span class="style-tag">${style}</span>`).join('') : ''}
</div>
`;
voiceList.appendChild(voiceCard);
});
} catch (error) {
console.error('加载音色列表失败:', error);
document.getElementById('voice-list').innerHTML =
'<p style="color:red;">加载音色列表失败,请刷新页面重试</p>';
}
}
async function synthesizeSpeech() {
const text = document.getElementById('synthesis-text').value;
const voice = document.getElementById('synthesis-voice').value;
const rate = document.getElementById('rate-slider').value;
const pitch = document.getElementById('pitch-slider').value;
try {
const response = await fetch('/tts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
t: text,
v: voice,
r: rate,
p: pitch
})
});
if (response.ok) {
let audioUrl = await response.json();
audioUrl = audioUrl['audio_url']
const audioPlayer = document.getElementById('audio-player');
audioPlayer.src = audioUrl;
audioPlayer.style.display = 'block';
} else {
alert('语音合成失败: ' + await response.text());
}
} catch (error) {
console.error('语音合成错误:', error);
alert('语音合成过程中发生错误');
}
}
document.addEventListener('DOMContentLoaded', () => {
loadVoices();
// 加载音色选项
fetch('/voices?l=zh&d=true')
.then(response => response.json())
.then(data => {
const voiceSelect = document.getElementById('synthesis-voice');
data.voices.forEach(voice => {
const option = document.createElement('option');
option.value = voice.ShortName;
option.textContent = `${voice.LocalName} (${voice.Gender === 'Female' ? '女' : '男'})`;
voiceSelect.appendChild(option);
});
});
document.getElementById('apply-filter').addEventListener('click', () => {
const gender = document.getElementById('gender-filter').value;
loadVoices(gender);
});
});
</script>
</body>
</html>