HFHash789's picture
Upload folder using huggingface_hub
dc6a411 verified
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chatterbox TTS 服务</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f4f7f9;
color: #333;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
box-sizing: border-box;
}
.container {
width: 100%;
max-width: 1200px;
background: #fff;
padding: 30px;
border-radius: 12px;
box-shadow: 0 8px 30px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #1a237e;
margin-bottom: 25px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
font-weight: 600;
color: #555;
}
textarea {
width: 100%;
padding: 12px;
border-radius: 8px;
border: 1px solid #ccc;
font-size: 16px;
min-height: 120px;
resize: vertical;
box-sizing: border-box;
transition: border-color 0.3s;
}
textarea:focus {
border-color: #3f51b5;
outline: none;
}
.file-input-wrapper {
position: relative;
overflow: hidden;
display: inline-block;
cursor: pointer;
padding: 10px 15px;
border: 1px dashed #ccc;
border-radius: 8px;
background-color: #f9f9f9;
}
.file-input-wrapper:hover {
border-color: #3f51b5;
}
input[type="file"] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
input[type="number"]{
line-height:25px;
margin-right:10px;
margin-left:5px;
}
.d-flex{
display:flex;
align-items:center
}
#file-name {
margin-left: 10px;
font-style: italic;
color: #777;
}
.submit-btn {
width: 100%;
padding: 15px;
font-size: 18px;
font-weight: bold;
color: #fff;
background-color: #3f51b5;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.3s, transform 0.1s;
}
.submit-btn:hover:not(:disabled) {
background-color: #303f9f;
}
.submit-btn:active:not(:disabled) {
transform: scale(0.98);
}
.submit-btn:disabled {
background-color: #9fa8da;
cursor: not-allowed;
}
.result-container {
margin-top: 30px;
display: none; /* Initially hidden */
}
audio {
width: 100%;
margin-bottom: 15px;
}
.download-link {
display: block;
text-align: center;
padding: 10px;
background: #e8eaf6;
color: #3f51b5;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
}
.download-link:hover {
background: #c5cae9;
}
.status {
text-align: center;
margin-top: 20px;
font-weight: 500;
height: 24px; /* Reserve space to prevent layout shift */
}
.status.loading::after {
content: '...';
display: inline-block;
animation: BouncingDots 1.4s infinite ease-in-out both;
}
@keyframes BouncingDots {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1.0); }
}
#tips{
display:block;
width:30%;
max-width:600px;
min-width:200px;
text-align:center;
color:#777;
margin:15px auto;
}
#params-tip{
font-size:12px;color:#999;
margin:5px 10px 0;
}
#language{padding:5px;}
</style>
</head>
<body>
<div class="container">
<h1>Chatterbox TTS 服务</h1>
<form id="tts-form">
<div class="form-group">
<label for="text-input">输入文本</label>
<textarea id="text-input" placeholder="在此输入您想转换的文字..." required>你好啊,亲爱的朋友,祝你早日发财.</textarea>
</div>
<div class="d-flex">
<label for="language">语言</label>
<select id="language" class="">
<option value="zh">中文</option>
<option value="en">英语</option>
<option value="ar">阿拉伯语</option>
<option value="da">丹麦语</option>
<option value="de">德语</option>
<option value="el">希腊语</option>
<option value="es">西班牙语</option>
<option value="fi">芬兰语</option>
<option value="fr">法语</option>
<option value="he">希伯来语</option>
<option value="hi">印地语</option>
<option value="it">意大利语</option>
<option value="ja">日语</option>
<option value="ko">韩语</option>
<option value="ms">马来语</option>
<option value="nl">荷兰语</option>
<option value="no">挪威语</option>
<option value="pl">波兰语</option>
<option value="pt">葡萄牙语</option>
<option value="ru">俄语</option>
<option value="sv">瑞典语</option>
<option value="sw">斯瓦西里语</option>
<option value="tr">土耳其语</option>
</select>
<label for="cfg_weight">cfg_weight</label>
<input type="number" id="cfg_weight" value="0.5" min="0.0" max="1.0" step='0.05'>
<label for="exaggeration">exaggeration</label>
<input type="number" id="exaggeration" value="0.5" min="0.25" max="2.0" step='0.05'>
</div>
<div id="params-tip">
<strong>cfg_weight: (范围 0.0 - 1.0)</strong> 控制语音的节奏。值越低,语速越慢、越从容。
<strong>exaggeration: (范围 0.25 - 2.0)</strong> 控制语音的情感和语调夸张程度。值越高,情感越丰富。
</div>
<div class="form-group d-flex">
<label for="audio-prompt">参考音频 (可选, 用于声音克隆) </label>
<div class="file-input-wrapper">
<span>选择文件</span>
<input type="file" id="audio-prompt" accept="audio/*">
</div>
<span id="file-name"></span>
</div>
<button type="submit" id="generate-btn" class="submit-btn">生成语音</button>
</form>
<div id="status" class="status"></div>
<div id="result-container" class="result-container">
<audio id="audio-player" controls></audio>
<a id="download-link" href="#" download="synthesis.mp3" class="download-link">下载 MP3</a>
</div>
<a href="https://github.com/jianchang512/chatterbox-api" target="_blank" id="tips">GitHub:jianchang512/chatterbox-api</a>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const ttsForm = document.getElementById('tts-form');
const textInput = document.getElementById('text-input');
const audioPromptInput = document.getElementById('audio-prompt');
const fileNameSpan = document.getElementById('file-name');
const generateBtn = document.getElementById('generate-btn');
const statusDiv = document.getElementById('status');
const resultContainer = document.getElementById('result-container');
const audioPlayer = document.getElementById('audio-player');
const downloadLink = document.getElementById('download-link');
// 更新显示的文件名
audioPromptInput.addEventListener('change', () => {
if (audioPromptInput.files.length > 0) {
fileNameSpan.textContent = audioPromptInput.files[0].name;
} else {
fileNameSpan.textContent = '未选择文件';
}
});
ttsForm.addEventListener('submit', async (event) => {
event.preventDefault(); // 阻止表单默认提交
const text = textInput.value.trim();
if (!text) {
alert('请输入要转换的文本!');
return;
}
// 禁用按钮并显示加载状态
generateBtn.disabled = true;
generateBtn.textContent = '生成中...';
statusDiv.textContent = '正在请求服务器,请稍候';
statusDiv.classList.add('loading');
resultContainer.style.display = 'none';
let cfg_weight=document.getElementById('cfg_weight').value
let language=document.getElementById('language').value
let exaggeration= String(document.getElementById('exaggeration').value)
const audioFile = audioPromptInput.files[0];
try {
let response;
if (audioFile) {
// 使用接口2:带参考音频
const formData = new FormData();
formData.append('input', text);
formData.append('response_format', 'mp3');
formData.append('exaggeration', exaggeration);
formData.append('cfg_weight', cfg_weight);
formData.append('audio_prompt', audioFile);
response = await fetch('/v2/audio/speech_with_prompt', {
method: 'POST',
body: formData,
});
} else {
// 使用接口1:兼容OpenAI
const payload = {
input: text,
speed:cfg_weight,
voice:language,
instructions:exaggeration,
model: 'chatterbox-tts', // 兼容参数
response_format: 'mp3' // 请求mp3格式
};
response = await fetch('/v1/audio/speech', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({error: '无法解析的服务器错误'}));
throw new Error(`服务器错误: ${response.status} - ${errorData.error || '未知错误'}`);
}
// 处理成功的音频流
const blob = await response.blob();
const audioUrl = URL.createObjectURL(blob);
audioPlayer.src = audioUrl;
downloadLink.href = audioUrl;
resultContainer.style.display = 'block';
statusDiv.textContent = '🎉 生成成功!';
statusDiv.style.color = 'green';
} catch (error) {
console.error('TTS Generation Error:', error);
statusDiv.textContent = `❌ 生成失败: ${error.message}`;
statusDiv.style.color = 'red';
} finally {
// 恢复按钮状态
generateBtn.disabled = false;
generateBtn.textContent = '生成语音';
statusDiv.classList.remove('loading');
}
});
});
</script>
</body>
</html>