diff --git a/mimic3_http/app.py b/mimic3_http/app.py index ee4798a..789c3ca 100644 --- a/mimic3_http/app.py +++ b/mimic3_http/app.py @@ -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(): diff --git a/mimic3_http/args.py b/mimic3_http/args.py index 7cf5e67..0110bf2 100644 --- a/mimic3_http/args.py +++ b/mimic3_http/args.py @@ -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" ) diff --git a/mimic3_http/templates/index.html b/mimic3_http/templates/index.html index 9355091..c6db391 100644 --- a/mimic3_http/templates/index.html +++ b/mimic3_http/templates/index.html @@ -87,8 +87,13 @@
- - + +
+
+ +
@@ -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', '' + ) + + 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', '' - ) + if (!langs.has(voice.language)) { + voiceLang.insertAdjacentHTML( + 'beforeend', '' + ) + 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) + }) diff --git a/mimic3_tts/utils.py b/mimic3_tts/utils.py index 21d0752..f5a7720 100644 --- a/mimic3_tts/utils.py +++ b/mimic3_tts/utils.py @@ -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"),