Add --default-voice option to mimic3-server and split voice into language/name
This commit is contained in:
parent
d178adf1f7
commit
918c692d53
4 changed files with 114 additions and 21 deletions
|
|
@ -36,6 +36,7 @@ from quart import (
|
|||
from swagger_ui import api_doc
|
||||
|
||||
from mimic3_tts import DEFAULT_VOICE, Mimic3Settings, Mimic3TextToSpeechSystem
|
||||
from mimic3_tts.utils import LANG_NAMES
|
||||
|
||||
from ._resources import _DIR, _PACKAGE
|
||||
from .args import _MISSING
|
||||
|
|
@ -157,6 +158,7 @@ def get_app(args: argparse.Namespace, request_queue: Queue, temp_dir: str):
|
|||
"index.html",
|
||||
show_openapi=show_openapi,
|
||||
max_text_length=args.max_text_length,
|
||||
default_voice=args.default_voice,
|
||||
)
|
||||
|
||||
@app.route("/api/tts", methods=["GET", "POST"])
|
||||
|
|
@ -225,9 +227,26 @@ def get_app(args: argparse.Namespace, request_queue: Queue, temp_dir: str):
|
|||
|
||||
@app.route("/api/voices", methods=["GET"])
|
||||
async def api_voices():
|
||||
voices_dict = {v.key: v for v in _MIMIC3.get_voices()}
|
||||
voices = sorted(voices_dict.values(), key=lambda v: v.key)
|
||||
return jsonify([dataclasses.asdict(v) for v in voices])
|
||||
voices_by_key = {v.key: v for v in _MIMIC3.get_voices()}
|
||||
sorted_voices = sorted(voices_by_key.values(), key=lambda v: v.key)
|
||||
voice_dicts = [dataclasses.asdict(v) for v in sorted_voices]
|
||||
|
||||
# Add more fields to voices
|
||||
for voice_dict in voice_dicts:
|
||||
voice_lang = voice_dict["language"]
|
||||
lang_name = LANG_NAMES.get(voice_lang, voice_lang)
|
||||
|
||||
if isinstance(lang_name, str):
|
||||
# Native and English language name are the same
|
||||
native_lang, english_lang = lang_name, lang_name
|
||||
else:
|
||||
# Native and English language name are different
|
||||
native_lang, english_lang = lang_name
|
||||
|
||||
voice_dict["language_native"] = native_lang
|
||||
voice_dict["language_english"] = english_lang
|
||||
|
||||
return jsonify(voice_dicts)
|
||||
|
||||
@app.route("/process", methods=["GET", "POST"])
|
||||
async def api_process():
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ def get_args(argv=None) -> argparse.Namespace:
|
|||
type=int,
|
||||
help="Maximum length of input text to process (default: no limit)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--default-voice",
|
||||
help="Default voice key to select in web interface",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug", action="store_true", help="Print DEBUG messages to console"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -87,8 +87,13 @@
|
|||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-auto">
|
||||
<label for="voice-list" title="Voice name">Voice:</label>
|
||||
<select id="voice-list" name="voices">
|
||||
<label for="voice-language" title="Voice language">Language:</label>
|
||||
<select id="voice-language" name="voice-language">
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label for="voice-name" title="Voice name">Name:</label>
|
||||
<select id="voice-name" name="voice-name">
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
|
|
@ -246,8 +251,8 @@
|
|||
q('#speak-button').addEventListener('click', do_tts)
|
||||
|
||||
async function synthesize(text) {
|
||||
var voiceList = q('#voice-list')
|
||||
var voice = voiceList.options[voiceList.selectedIndex].value
|
||||
var voiceName = q('#voice-name')
|
||||
var voice = voiceName.options[voiceName.selectedIndex].value
|
||||
|
||||
var noiseScale = q('#noise-scale').value || '0.667'
|
||||
var noiseW = q('#noise-w').value || '0.8'
|
||||
|
|
@ -289,8 +294,52 @@
|
|||
}
|
||||
}
|
||||
|
||||
function voiceChanged() {
|
||||
var voiceList = q('#voice-list')
|
||||
function langChanged(indexToSelect) {
|
||||
// Called when voice language is changed
|
||||
|
||||
// If set, select a specific language by index
|
||||
indexToSelect = indexToSelect === undefined ? -1 : indexToSelect
|
||||
|
||||
var voiceLang = q('#voice-language')
|
||||
|
||||
// Reset names
|
||||
var voiceName = q('#voice-name')
|
||||
for (var i = voiceName.options.length - 1; i >= 0; i--) {
|
||||
voiceName.options[i].remove()
|
||||
}
|
||||
|
||||
if (indexToSelect >= 0) {
|
||||
// Select specific language
|
||||
voiceLang.selectedIndex = indexToSelect
|
||||
}
|
||||
|
||||
var selectedLang = voiceLang.options[voiceLang.selectedIndex].value
|
||||
var nameIndexToSelect = -1
|
||||
|
||||
Object.values(voicesInfo).forEach(function(voice) {
|
||||
if (voice.language == selectedLang) {
|
||||
voiceName.insertAdjacentHTML(
|
||||
'beforeend', '<option value="' + voice.key + '">' + voice.name + '</option>'
|
||||
)
|
||||
|
||||
if ((indexToSelect >= 0) && (voice.key == '{{ default_voice }}')) {
|
||||
// Record voice name index to select
|
||||
nameIndexToSelect = voiceName.options.length - 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Trigger voice name change
|
||||
nameChanged(nameIndexToSelect)
|
||||
}
|
||||
|
||||
function nameChanged(indexToSelect) {
|
||||
// Called when voice name is changed
|
||||
|
||||
// If set, select a specific voice by index
|
||||
indexToSelect = indexToSelect === undefined ? -1 : indexToSelect
|
||||
|
||||
var voiceName = q('#voice-name')
|
||||
|
||||
// Reset audio
|
||||
q('#audio-message').hidden = true
|
||||
|
|
@ -304,7 +353,12 @@
|
|||
speakerList.options[i].remove()
|
||||
}
|
||||
|
||||
var voiceKey = voiceList.options[voiceList.selectedIndex].value
|
||||
if (indexToSelect >= 0) {
|
||||
// Select a specific voice by index
|
||||
voiceName.selectedIndex = indexToSelect
|
||||
}
|
||||
|
||||
var voiceKey = voiceName.options[voiceName.selectedIndex].value
|
||||
var voice = voicesInfo[voiceKey]
|
||||
|
||||
if (voice.speakers && voice.speakers.length > 0) {
|
||||
|
|
@ -329,17 +383,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
q('#voice-list').addEventListener('change', voiceChanged)
|
||||
|
||||
function loadVoices() {
|
||||
voicesInfo = {}
|
||||
|
||||
// Remove previous voices
|
||||
var voiceList = q('#voice-list')
|
||||
for (var i = voiceList.options.length - 1; i >= 0; i--) {
|
||||
voiceList.options[i].remove()
|
||||
var voiceLang = q('#voice-language')
|
||||
for (var i = voiceLang.options.length - 1; i >= 0; i--) {
|
||||
voiceLang.options[i].remove()
|
||||
}
|
||||
|
||||
var voiceName = q('#voice-name')
|
||||
for (var i = voiceName.options.length - 1; i >= 0; i--) {
|
||||
voiceName.options[i].remove()
|
||||
}
|
||||
|
||||
var langs = new Set();
|
||||
|
||||
fetch('api/voices')
|
||||
.then(function(res) {
|
||||
if (!res.ok) throw Error(res.statusText)
|
||||
|
|
@ -352,12 +411,19 @@
|
|||
|
||||
voices.forEach(function(voice) {
|
||||
voicesInfo[voice.key] = voice
|
||||
voiceList.insertAdjacentHTML(
|
||||
'beforeend', '<option value="' + voice.key + '">' + voice.language + '/' + voice.name + '</option>'
|
||||
)
|
||||
if (!langs.has(voice.language)) {
|
||||
voiceLang.insertAdjacentHTML(
|
||||
'beforeend', '<option value="' + voice.language + '" title="' + voice.language_english + '">' + voice.language_native + '</option>'
|
||||
)
|
||||
langs.add(voice.language)
|
||||
}
|
||||
|
||||
if (voice.key == '{{ default_voice }}') {
|
||||
indexToSelect = voiceLang.options.length - 1
|
||||
}
|
||||
})
|
||||
|
||||
voiceChanged()
|
||||
langChanged(indexToSelect)
|
||||
}).catch(function(err) {
|
||||
q('#message').textContent = 'Error: ' + err.message
|
||||
q('#speak-button').disabled = false
|
||||
|
|
@ -366,6 +432,10 @@
|
|||
|
||||
window.addEventListener('load', function() {
|
||||
loadVoices()
|
||||
|
||||
q('#voice-language').addEventListener('change', langChanged)
|
||||
q('#voice-name').addEventListener('change', nameChanged)
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ LANG_NAMES = {
|
|||
"af_ZA": "Afrikaans",
|
||||
"da_DK": ("Dansk", "Danish"),
|
||||
"de_DE": ("Deutsch", "German"),
|
||||
"en_UK": "English",
|
||||
"en_US": "English",
|
||||
"en_UK": "English (UK)",
|
||||
"en_US": "English (US)",
|
||||
"el_GR": ("Ελληνικά", "Greek"),
|
||||
"es_ES": ("Español", "Spanish"),
|
||||
"fa": ("فارسی", "Persian"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue