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 swagger_ui import api_doc
|
||||||
|
|
||||||
from mimic3_tts import DEFAULT_VOICE, Mimic3Settings, Mimic3TextToSpeechSystem
|
from mimic3_tts import DEFAULT_VOICE, Mimic3Settings, Mimic3TextToSpeechSystem
|
||||||
|
from mimic3_tts.utils import LANG_NAMES
|
||||||
|
|
||||||
from ._resources import _DIR, _PACKAGE
|
from ._resources import _DIR, _PACKAGE
|
||||||
from .args import _MISSING
|
from .args import _MISSING
|
||||||
|
|
@ -157,6 +158,7 @@ def get_app(args: argparse.Namespace, request_queue: Queue, temp_dir: str):
|
||||||
"index.html",
|
"index.html",
|
||||||
show_openapi=show_openapi,
|
show_openapi=show_openapi,
|
||||||
max_text_length=args.max_text_length,
|
max_text_length=args.max_text_length,
|
||||||
|
default_voice=args.default_voice,
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.route("/api/tts", methods=["GET", "POST"])
|
@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"])
|
@app.route("/api/voices", methods=["GET"])
|
||||||
async def api_voices():
|
async def api_voices():
|
||||||
voices_dict = {v.key: v for v in _MIMIC3.get_voices()}
|
voices_by_key = {v.key: v for v in _MIMIC3.get_voices()}
|
||||||
voices = sorted(voices_dict.values(), key=lambda v: v.key)
|
sorted_voices = sorted(voices_by_key.values(), key=lambda v: v.key)
|
||||||
return jsonify([dataclasses.asdict(v) for v in voices])
|
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"])
|
@app.route("/process", methods=["GET", "POST"])
|
||||||
async def api_process():
|
async def api_process():
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,10 @@ def get_args(argv=None) -> argparse.Namespace:
|
||||||
type=int,
|
type=int,
|
||||||
help="Maximum length of input text to process (default: no limit)",
|
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(
|
parser.add_argument(
|
||||||
"--debug", action="store_true", help="Print DEBUG messages to console"
|
"--debug", action="store_true", help="Print DEBUG messages to console"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-3">
|
<div class="row mt-3">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<label for="voice-list" title="Voice name">Voice:</label>
|
<label for="voice-language" title="Voice language">Language:</label>
|
||||||
<select id="voice-list" name="voices">
|
<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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
|
|
@ -246,8 +251,8 @@
|
||||||
q('#speak-button').addEventListener('click', do_tts)
|
q('#speak-button').addEventListener('click', do_tts)
|
||||||
|
|
||||||
async function synthesize(text) {
|
async function synthesize(text) {
|
||||||
var voiceList = q('#voice-list')
|
var voiceName = q('#voice-name')
|
||||||
var voice = voiceList.options[voiceList.selectedIndex].value
|
var voice = voiceName.options[voiceName.selectedIndex].value
|
||||||
|
|
||||||
var noiseScale = q('#noise-scale').value || '0.667'
|
var noiseScale = q('#noise-scale').value || '0.667'
|
||||||
var noiseW = q('#noise-w').value || '0.8'
|
var noiseW = q('#noise-w').value || '0.8'
|
||||||
|
|
@ -289,8 +294,52 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function voiceChanged() {
|
function langChanged(indexToSelect) {
|
||||||
var voiceList = q('#voice-list')
|
// 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
|
// Reset audio
|
||||||
q('#audio-message').hidden = true
|
q('#audio-message').hidden = true
|
||||||
|
|
@ -304,7 +353,12 @@
|
||||||
speakerList.options[i].remove()
|
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]
|
var voice = voicesInfo[voiceKey]
|
||||||
|
|
||||||
if (voice.speakers && voice.speakers.length > 0) {
|
if (voice.speakers && voice.speakers.length > 0) {
|
||||||
|
|
@ -329,17 +383,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
q('#voice-list').addEventListener('change', voiceChanged)
|
|
||||||
|
|
||||||
function loadVoices() {
|
function loadVoices() {
|
||||||
voicesInfo = {}
|
voicesInfo = {}
|
||||||
|
|
||||||
// Remove previous voices
|
// Remove previous voices
|
||||||
var voiceList = q('#voice-list')
|
var voiceLang = q('#voice-language')
|
||||||
for (var i = voiceList.options.length - 1; i >= 0; i--) {
|
for (var i = voiceLang.options.length - 1; i >= 0; i--) {
|
||||||
voiceList.options[i].remove()
|
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')
|
fetch('api/voices')
|
||||||
.then(function(res) {
|
.then(function(res) {
|
||||||
if (!res.ok) throw Error(res.statusText)
|
if (!res.ok) throw Error(res.statusText)
|
||||||
|
|
@ -352,12 +411,19 @@
|
||||||
|
|
||||||
voices.forEach(function(voice) {
|
voices.forEach(function(voice) {
|
||||||
voicesInfo[voice.key] = voice
|
voicesInfo[voice.key] = voice
|
||||||
voiceList.insertAdjacentHTML(
|
if (!langs.has(voice.language)) {
|
||||||
'beforeend', '<option value="' + voice.key + '">' + voice.language + '/' + voice.name + '</option>'
|
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) {
|
}).catch(function(err) {
|
||||||
q('#message').textContent = 'Error: ' + err.message
|
q('#message').textContent = 'Error: ' + err.message
|
||||||
q('#speak-button').disabled = false
|
q('#speak-button').disabled = false
|
||||||
|
|
@ -366,6 +432,10 @@
|
||||||
|
|
||||||
window.addEventListener('load', function() {
|
window.addEventListener('load', function() {
|
||||||
loadVoices()
|
loadVoices()
|
||||||
|
|
||||||
|
q('#voice-language').addEventListener('change', langChanged)
|
||||||
|
q('#voice-name').addEventListener('change', nameChanged)
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ LANG_NAMES = {
|
||||||
"af_ZA": "Afrikaans",
|
"af_ZA": "Afrikaans",
|
||||||
"da_DK": ("Dansk", "Danish"),
|
"da_DK": ("Dansk", "Danish"),
|
||||||
"de_DE": ("Deutsch", "German"),
|
"de_DE": ("Deutsch", "German"),
|
||||||
"en_UK": "English",
|
"en_UK": "English (UK)",
|
||||||
"en_US": "English",
|
"en_US": "English (US)",
|
||||||
"el_GR": ("Ελληνικά", "Greek"),
|
"el_GR": ("Ελληνικά", "Greek"),
|
||||||
"es_ES": ("Español", "Spanish"),
|
"es_ES": ("Español", "Spanish"),
|
||||||
"fa": ("فارسی", "Persian"),
|
"fa": ("فارسی", "Persian"),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue