diff --git a/config/webvoice.json b/config/webvoice.json new file mode 100644 index 0000000..0f7cbbf --- /dev/null +++ b/config/webvoice.json @@ -0,0 +1,2128 @@ +{ + "voices": [ + { + "name": "Xiaochen", + "alias": "小陈", + "code": "zh-CN-Xiaochen:DragonHDLatestNeural", + "locale": "zh-CN", + "gender": "Female", + "usedname": "小陈", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-Xiaochen:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "YunFan", + "alias": "云帆", + "code": "zh-CN-Yunfan:DragonHDLatestNeural", + "locale": "zh-CN", + "gender": "Male", + "usedname": "云帆", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-Yunfan:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "XiaochenFlash", + "alias": "小陈-flash", + "code": "zh-CN-Xiaochen:DragonHDFlashLatestNeural", + "locale": "zh-CN", + "gender": "Female", + "usedname": "小陈", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-Xiaochen:DragonHDFlashLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "XiaoxiaoFlash", + "alias": "小晓-flash", + "code": "zh-CN-Xiaoxiao:DragonHDFlashLatestNeural", + "locale": "zh-CN", + "gender": "Female", + "usedname": "小晓", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-Xiaoxiao:DragonHDFlashLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "XiaoxiaoFlash2", + "alias": "小笑-flash", + "code": "zh-CN-Xiaoxiao:DragonHDFlashLatestNeural2", + "locale": "zh-CN", + "gender": "Female", + "usedname": "小笑", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-Xiaoxiao:DragonHDFlashLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "YunxiaoFlash", + "alias": "云晓-flash", + "code": "zh-CN-Yunxiao:DragonHDFlashLatestNeural", + "locale": "zh-CN", + "gender": "Male", + "usedname": "云晓", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-Yunxiao:DragonHDFlashLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "YunyeFlash", + "alias": "云叶-flash", + "code": "zh-CN-Yunye:DragonHDFlashLatestNeural", + "locale": "zh-CN", + "gender": "Male", + "usedname": "云叶", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-Yunye:DragonHDFlashLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "YunyiFlash", + "alias": "云翼-flash", + "code": "zh-CN-Yunyi:DragonHDFlashLatestNeural", + "locale": "zh-CN", + "gender": "Male", + "usedname": "云翼", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-Yunyi:DragonHDFlashLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Xiaochen Multilingual", + "alias": "晓辰 多语言", + "code": "zh-CN-XiaochenMultilingualNeural", + "locale": "zh-CN", + "gender": "Female", + "usedname": "晓辰", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-XiaochenMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Xiaoxiao Multilingual", + "alias": "晓晓 多语言", + "code": "zh-CN-XiaoxiaoMultilingualNeural", + "locale": "zh-CN", + "gender": "Female", + "usedname": "晓晓", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-XiaoxiaoMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Xiaoyu Multilingual", + "alias": "晓宇 多语言", + "code": "zh-CN-XiaoyuMultilingualNeural", + "locale": "zh-CN", + "gender": "Female", + "usedname": "晓宇", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-XiaoyuMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yunfan Multilingual", + "alias": "云帆 多语言", + "code": "zh-CN-YunfanMultilingualNeural", + "locale": "zh-CN", + "gender": "Male", + "usedname": "云帆", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-YunfanMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yunxiao Multilingual", + "alias": "云霄 多语言", + "code": "zh-CN-YunxiaoMultilingualNeural", + "locale": "zh-CN", + "gender": "Male", + "usedname": "云霄", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-YunxiaoMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yunyi Multilingual", + "alias": "云逸 多语言", + "code": "zh-CN-YunyiMultilingualNeural", + "locale": "zh-CN", + "gender": "Male", + "usedname": "云逸", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-YunyiMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "HiuGaai", + "alias": "曉佳 香港", + "code": "zh-HK-HiuGaaiNeural", + "locale": "zh-HK", + "gender": "Female", + "usedname": "曉佳", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-HK-HiuGaaiNeural.wav", + "owner": "edge-tts" + }, + { + "name": "HiuMaan", + "alias": "曉曼 香港", + "code": "zh-HK-HiuMaanNeural", + "locale": "zh-HK", + "gender": "Female", + "usedname": "曉曼", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-HK-HiuMaanNeural.wav", + "owner": "edge-tts" + }, + { + "name": "WanLung", + "alias": "雲龍 香港", + "code": "zh-HK-WanLungNeural", + "locale": "zh-HK", + "gender": "Male", + "usedname": "雲龍", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-HK-WanLungNeural.wav", + "owner": "edge-tts" + }, + { + "name": "YunSong", + "alias": "云松 粤语", + "code": "yue-CN-YunSongNeural", + "locale": "zh-yue-CN", + "gender": "Male", + "usedname": "云松", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/yue-CN-YunSongNeural.wav", + "owner": "edge-tts" + }, + { + "name": "HsiaoChen", + "alias": "曉臻 台湾", + "code": "zh-TW-HsiaoChenNeural", + "locale": "zh-TW", + "gender": "Female", + "usedname": "曉臻", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-TW-HsiaoChenNeural.wav", + "owner": "edge-tts" + }, + { + "name": "HsiaoYu", + "alias": "曉雨 台湾", + "code": "zh-TW-HsiaoYuNeural", + "locale": "zh-TW", + "gender": "Female", + "usedname": "曉雨", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-TW-HsiaoYuNeural.wav", + "owner": "edge-tts" + }, + { + "name": "YunJhe", + "alias": "雲哲 台湾", + "code": "zh-TW-YunJheNeural", + "locale": "zh-TW", + "gender": "Male", + "usedname": "雲哲", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-TW-YunJheNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Xiaobei", + "alias": "晓北 辽宁", + "code": "zh-CN-liaoning-XiaobeiNeural", + "locale": "zh-CN-liaoning", + "gender": "Female", + "usedname": "晓北", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-liaoning-XiaobeiNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yunbiao", + "alias": "云彪 辽宁", + "code": "zh-CN-liaoning-YunbiaoNeural", + "locale": "zh-CN-liaoning", + "gender": "Male", + "usedname": "云彪", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-liaoning-YunbiaoNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Xiaotong", + "alias": "晓彤 上海", + "code": "wuu-CN-XiaotongNeural", + "locale": "zh-wuu-CN", + "gender": "Female", + "usedname": "晓彤", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/wuu-CN-XiaotongNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yunzhe", + "alias": "云哲 上海", + "code": "wuu-CN-YunzheNeural", + "locale": "zh-wuu-CN", + "gender": "Male", + "usedname": "云哲", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/wuu-CN-YunzheNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Xiaoni", + "alias": "晓妮 陕西", + "code": "zh-CN-shaanxi-XiaoniNeural", + "locale": "zh-CN-shaanxi", + "gender": "Female", + "usedname": "晓妮", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-shaanxi-XiaoniNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Xiaoxiao Dialects", + "alias": "晓晓 陕西", + "code": "zh-CN-XiaoxiaoDialectsNeural", + "locale": "zh-CN-shaanxi", + "gender": "Female", + "usedname": "晓晓", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-XiaoxiaoDialectsNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yundeng", + "alias": "云登 河南", + "code": "zh-CN-henan-YundengNeural", + "locale": "zh-CN-henan", + "gender": "Male", + "usedname": "云登", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-henan-YundengNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yunxi", + "alias": "云希 四川", + "code": "zh-CN-sichuan-YunxiNeural", + "locale": "zh-CN-sichuan", + "gender": "Male", + "usedname": "云希", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-sichuan-YunxiNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yunqi", + "alias": "云奇 广西", + "code": "zh-CN-guangxi-YunqiNeural", + "locale": "zh-CN-guangxi", + "gender": "Male", + "usedname": "云奇", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-guangxi-YunqiNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yunxiang", + "alias": "云翔 山东", + "code": "zh-CN-shandong-YunxiangNeural", + "locale": "zh-CN-shandong", + "gender": "Male", + "usedname": "云翔", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/zh-CN-shandong-YunxiangNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Adam Dragon HD Latest", + "alias": "Adam Dragon HD Latest", + "code": "en-US-Adam:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Adam", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Adam:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Alloy Dragon HD Latest", + "alias": "Alloy Dragon HD Latest", + "code": "en-US-Alloy:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Alloy", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Alloy:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Andrew Dragon HD Latest", + "alias": "Andrew Dragon HD Latest", + "code": "en-US-Andrew:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Andrew", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Andrew:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Andrew2 Dragon HD Latest", + "alias": "Andrew2 Dragon HD Latest", + "code": "en-US-Andrew2:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Andrew2", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Andrew2:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Andrew3 Dragon HD Latest", + "alias": "Andrew3 Dragon HD Latest", + "code": "en-US-Andrew3:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Andrew3", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Andrew3:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Aria Dragon HD Latest", + "alias": "Aria Dragon HD Latest", + "code": "en-US-Aria:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Aria", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Aria:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ava & Andrew MultiTalker Dragon HD Latest", + "alias": "Ava & Andrew MultiTalker Dragon HD Latest", + "code": "en-US-MultiTalker-Ava-Andrew:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Neutral", + "usedname": "Ava", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-MultiTalker-Ava-Andrew:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ava & Steffan MultiTalker Dragon HD Latest", + "alias": "Ava & Steffan MultiTalker Dragon HD Latest", + "code": "en-US-MultiTalker-Ava-Steffan:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Neutral", + "usedname": "Ava", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-MultiTalker-Ava-Steffan:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ava Dragon HD Latest", + "alias": "Ava Dragon HD Latest", + "code": "en-US-Ava:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Ava", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Ava:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ava3 Dragon HD Latest", + "alias": "Ava3 Dragon HD Latest", + "code": "en-US-Ava3:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Ava3", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Ava3:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Bree Dragon HD Latest", + "alias": "Bree Dragon HD Latest", + "code": "en-US-Bree:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Bree", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Bree:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Brian Dragon HD Latest", + "alias": "Brian Dragon HD Latest", + "code": "en-US-Brian:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Brian", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Brian:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Davis Dragon HD Latest", + "alias": "Davis Dragon HD Latest", + "code": "en-US-Davis:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Davis", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Davis:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Emma Dragon HD Latest", + "alias": "Emma Dragon HD Latest", + "code": "en-US-Emma:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Emma", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Emma:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Emma2 Dragon HD Latest", + "alias": "Emma2 Dragon HD Latest", + "code": "en-US-Emma2:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Emma2", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Emma2:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Jane Dragon HD Latest", + "alias": "Jane Dragon HD Latest", + "code": "en-US-Jane:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Jane", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Jane:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Jenny Dragon HD Latest", + "alias": "Jenny Dragon HD Latest", + "code": "en-US-Jenny:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Jenny", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Jenny:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Nova Dragon HD Latest", + "alias": "Nova Dragon HD Latest", + "code": "en-US-Nova:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Nova", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Nova:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Phoebe Dragon HD Latest", + "alias": "Phoebe Dragon HD Latest", + "code": "en-US-Phoebe:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Phoebe", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Phoebe:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Serena Dragon HD Latest", + "alias": "Serena Dragon HD Latest", + "code": "en-US-Serena:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Serena", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Serena:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Steffan Dragon HD Latest", + "alias": "Steffan Dragon HD Latest", + "code": "en-US-Steffan:DragonHDLatestNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Steffan", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-Steffan:DragonHDLatestNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Aarav", + "alias": "Aarav", + "code": "en-IN-AaravNeural", + "locale": "en-IN", + "gender": "Male", + "usedname": "Aarav", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-AaravNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Aarti", + "alias": "Aarti", + "code": "en-IN-AartiNeural", + "locale": "en-IN", + "gender": "Female", + "usedname": "Aarti", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-AartiNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Aashi", + "alias": "Aashi", + "code": "en-IN-AashiNeural", + "locale": "en-IN", + "gender": "Female", + "usedname": "Aashi", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-AashiNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Abbi", + "alias": "Abbi", + "code": "en-GB-AbbiNeural", + "locale": "en-GB", + "gender": "Female", + "usedname": "Abbi", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-AbbiNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Abeo", + "alias": "Abeo", + "code": "en-NG-AbeoNeural", + "locale": "en-NG", + "gender": "Male", + "usedname": "Abeo", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-NG-AbeoNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ada Multilingual", + "alias": "Ada Multilingual", + "code": "en-GB-AdaMultilingualNeural", + "locale": "en-GB", + "gender": "Female", + "usedname": "Ada", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-AdaMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Adam Multilingual", + "alias": "Adam Multilingual", + "code": "en-US-AdamMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Adam", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AdamMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "AIGenerate1", + "alias": "AIGenerate1", + "code": "en-US-AIGenerate1Neural", + "locale": "en-US", + "gender": "Male", + "usedname": "AIGenerate1", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AIGenerate1Neural.wav", + "owner": "edge-tts" + }, + { + "name": "AIGenerate2", + "alias": "AIGenerate2", + "code": "en-US-AIGenerate2Neural", + "locale": "en-US", + "gender": "Female", + "usedname": "AIGenerate2", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AIGenerate2Neural.wav", + "owner": "edge-tts" + }, + { + "name": "Alfie", + "alias": "Alfie", + "code": "en-GB-AlfieNeural", + "locale": "en-GB", + "gender": "Male", + "usedname": "Alfie", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-AlfieNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Alloy Turbo Multilingual", + "alias": "Alloy Turbo Multilingual", + "code": "en-US-AlloyTurboMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Alloy", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AlloyTurboMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Amanda Multilingual", + "alias": "Amanda Multilingual", + "code": "en-US-AmandaMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Amanda", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AmandaMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Amber", + "alias": "Amber", + "code": "en-US-AmberNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Amber", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AmberNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ana", + "alias": "Ana", + "code": "en-US-AnaNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Ana", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AnaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ananya", + "alias": "Ananya", + "code": "en-IN-AnanyaNeural", + "locale": "en-IN", + "gender": "Female", + "usedname": "Ananya", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-AnanyaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Andrew", + "alias": "Andrew", + "code": "en-US-AndrewNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Andrew", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AndrewNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Andrew Multilingual", + "alias": "Andrew Multilingual", + "code": "en-US-AndrewMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Andrew", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AndrewMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Annette", + "alias": "Annette", + "code": "en-AU-AnnetteNeural", + "locale": "en-AU", + "gender": "Female", + "usedname": "Annette", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-AnnetteNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Aria", + "alias": "Aria", + "code": "en-US-AriaNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Aria", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AriaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Arjun", + "alias": "Arjun", + "code": "en-IN-ArjunNeural", + "locale": "en-IN", + "gender": "Male", + "usedname": "Arjun", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-ArjunNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Arjun Indic", + "alias": "Arjun Indic", + "code": "en-IN-ArjunIndicNeural", + "locale": "en-IN", + "gender": "Male", + "usedname": "Arjun", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-ArjunIndicNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ash Turbo Multilingual", + "alias": "Ash Turbo Multilingual", + "code": "en-US-AshTurboMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Ash", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AshTurboMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ashley", + "alias": "Ashley", + "code": "en-US-AshleyNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Ashley", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AshleyNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Asilia", + "alias": "Asilia", + "code": "en-KE-AsiliaNeural", + "locale": "en-KE", + "gender": "Female", + "usedname": "Asilia", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-KE-AsiliaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ava", + "alias": "Ava", + "code": "en-US-AvaNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Ava", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AvaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ava Multilingual", + "alias": "Ava Multilingual", + "code": "en-US-AvaMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Ava", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-AvaMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Bella", + "alias": "Bella", + "code": "en-GB-BellaNeural", + "locale": "en-GB", + "gender": "Female", + "usedname": "Bella", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-BellaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Blue", + "alias": "Blue", + "code": "en-US-BlueNeural", + "locale": "en-US", + "gender": "Neutral", + "usedname": "Blue", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-BlueNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Brandon", + "alias": "Brandon", + "code": "en-US-BrandonNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Brandon", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-BrandonNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Brandon Multilingual", + "alias": "Brandon Multilingual", + "code": "en-US-BrandonMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Brandon", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-BrandonMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Brian", + "alias": "Brian", + "code": "en-US-BrianNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Brian", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-BrianNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Brian Multilingual", + "alias": "Brian Multilingual", + "code": "en-US-BrianMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Brian", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-BrianMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Carly", + "alias": "Carly", + "code": "en-AU-CarlyNeural", + "locale": "en-AU", + "gender": "Female", + "usedname": "Carly", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-CarlyNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Chilemba", + "alias": "Chilemba", + "code": "en-KE-ChilembaNeural", + "locale": "en-KE", + "gender": "Male", + "usedname": "Chilemba", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-KE-ChilembaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Christopher", + "alias": "Christopher", + "code": "en-US-ChristopherNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Christopher", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-ChristopherNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Christopher Multilingual", + "alias": "Christopher Multilingual", + "code": "en-US-ChristopherMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Christopher", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-ChristopherMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Clara", + "alias": "Clara", + "code": "en-CA-ClaraNeural", + "locale": "en-CA", + "gender": "Female", + "usedname": "Clara", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-CA-ClaraNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Connor", + "alias": "Connor", + "code": "en-IE-ConnorNeural", + "locale": "en-IE", + "gender": "Male", + "usedname": "Connor", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IE-ConnorNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Cora", + "alias": "Cora", + "code": "en-US-CoraNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Cora", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-CoraNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Cora Multilingual", + "alias": "Cora Multilingual", + "code": "en-US-CoraMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Cora", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-CoraMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Darren", + "alias": "Darren", + "code": "en-AU-DarrenNeural", + "locale": "en-AU", + "gender": "Male", + "usedname": "Darren", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-DarrenNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Davis", + "alias": "Davis", + "code": "en-US-DavisNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Davis", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-DavisNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Davis Multilingual", + "alias": "Davis Multilingual", + "code": "en-US-DavisMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Davis", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-DavisMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Derek Multilingual", + "alias": "Derek Multilingual", + "code": "en-US-DerekMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Derek", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-DerekMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Duncan", + "alias": "Duncan", + "code": "en-AU-DuncanNeural", + "locale": "en-AU", + "gender": "Male", + "usedname": "Duncan", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-DuncanNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Dustin Multilingual", + "alias": "Dustin Multilingual", + "code": "en-US-DustinMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Dustin", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-DustinMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Echo Turbo Multilingual", + "alias": "Echo Turbo Multilingual", + "code": "en-US-EchoTurboMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Echo", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-EchoTurboMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Elimu", + "alias": "Elimu", + "code": "en-TZ-ElimuNeural", + "locale": "en-TZ", + "gender": "Male", + "usedname": "Elimu", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-TZ-ElimuNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Elizabeth", + "alias": "Elizabeth", + "code": "en-US-ElizabethNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Elizabeth", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-ElizabethNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Elliot", + "alias": "Elliot", + "code": "en-GB-ElliotNeural", + "locale": "en-GB", + "gender": "Male", + "usedname": "Elliot", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-ElliotNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Elsie", + "alias": "Elsie", + "code": "en-AU-ElsieNeural", + "locale": "en-AU", + "gender": "Female", + "usedname": "Elsie", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-ElsieNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Emily", + "alias": "Emily", + "code": "en-IE-EmilyNeural", + "locale": "en-IE", + "gender": "Female", + "usedname": "Emily", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IE-EmilyNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Emma", + "alias": "Emma", + "code": "en-US-EmmaNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Emma", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-EmmaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Emma Multilingual", + "alias": "Emma Multilingual", + "code": "en-US-EmmaMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Emma", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-EmmaMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Eric", + "alias": "Eric", + "code": "en-US-EricNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Eric", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-EricNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ethan", + "alias": "Ethan", + "code": "en-GB-EthanNeural", + "locale": "en-GB", + "gender": "Male", + "usedname": "Ethan", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-EthanNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Evelyn Multilingual", + "alias": "Evelyn Multilingual", + "code": "en-US-EvelynMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Evelyn", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-EvelynMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ezinne", + "alias": "Ezinne", + "code": "en-NG-EzinneNeural", + "locale": "en-NG", + "gender": "Female", + "usedname": "Ezinne", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-NG-EzinneNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Fable Turbo Multilingual", + "alias": "Fable Turbo Multilingual", + "code": "en-US-FableTurboMultilingualNeural", + "locale": "en-US", + "gender": "Neutral", + "usedname": "Fable", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-FableTurboMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Freya", + "alias": "Freya", + "code": "en-AU-FreyaNeural", + "locale": "en-AU", + "gender": "Female", + "usedname": "Freya", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-FreyaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Guy", + "alias": "Guy", + "code": "en-US-GuyNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Guy", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-GuyNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Hollie", + "alias": "Hollie", + "code": "en-GB-HollieNeural", + "locale": "en-GB", + "gender": "Female", + "usedname": "Hollie", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-HollieNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Imani", + "alias": "Imani", + "code": "en-TZ-ImaniNeural", + "locale": "en-TZ", + "gender": "Female", + "usedname": "Imani", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-TZ-ImaniNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Jacob", + "alias": "Jacob", + "code": "en-US-JacobNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Jacob", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-JacobNeural.wav", + "owner": "edge-tts" + }, + { + "name": "James", + "alias": "James", + "code": "en-PH-JamesNeural", + "locale": "en-PH", + "gender": "Male", + "usedname": "James", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-PH-JamesNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Jane", + "alias": "Jane", + "code": "en-US-JaneNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Jane", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-JaneNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Jason", + "alias": "Jason", + "code": "en-US-JasonNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Jason", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-JasonNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Jenny", + "alias": "Jenny", + "code": "en-US-JennyNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Jenny", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-JennyNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Jenny Multilingual", + "alias": "Jenny Multilingual", + "code": "en-US-JennyMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Jenny", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-JennyMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Joanne", + "alias": "Joanne", + "code": "en-AU-JoanneNeural", + "locale": "en-AU", + "gender": "Female", + "usedname": "Joanne", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-JoanneNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Kai", + "alias": "Kai", + "code": "en-US-KaiNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Kai", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-KaiNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Kavya", + "alias": "Kavya", + "code": "en-IN-KavyaNeural", + "locale": "en-IN", + "gender": "Female", + "usedname": "Kavya", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-KavyaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ken", + "alias": "Ken", + "code": "en-AU-KenNeural", + "locale": "en-AU", + "gender": "Male", + "usedname": "Ken", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-KenNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Kim", + "alias": "Kim", + "code": "en-AU-KimNeural", + "locale": "en-AU", + "gender": "Female", + "usedname": "Kim", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-KimNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Kunal", + "alias": "Kunal", + "code": "en-IN-KunalNeural", + "locale": "en-IN", + "gender": "Male", + "usedname": "Kunal", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-KunalNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Leah", + "alias": "Leah", + "code": "en-ZA-LeahNeural", + "locale": "en-ZA", + "gender": "Female", + "usedname": "Leah", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-ZA-LeahNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Lewis Multilingual", + "alias": "Lewis Multilingual", + "code": "en-US-LewisMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Lewis", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-LewisMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Liam", + "alias": "Liam", + "code": "en-CA-LiamNeural", + "locale": "en-CA", + "gender": "Male", + "usedname": "Liam", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-CA-LiamNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Libby", + "alias": "Libby", + "code": "en-GB-LibbyNeural", + "locale": "en-GB", + "gender": "Female", + "usedname": "Libby", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-LibbyNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Lola Multilingual", + "alias": "Lola Multilingual", + "code": "en-US-LolaMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Lola", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-LolaMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Luke", + "alias": "Luke", + "code": "en-ZA-LukeNeural", + "locale": "en-ZA", + "gender": "Male", + "usedname": "Luke", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-ZA-LukeNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Luna", + "alias": "Luna", + "code": "en-SG-LunaNeural", + "locale": "en-SG", + "gender": "Female", + "usedname": "Luna", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-SG-LunaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Luna", + "alias": "Luna", + "code": "en-US-LunaNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Luna", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-LunaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Maisie", + "alias": "Maisie", + "code": "en-GB-MaisieNeural", + "locale": "en-GB", + "gender": "Female", + "usedname": "Maisie", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-MaisieNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Mia", + "alias": "Mia", + "code": "en-GB-MiaNeural", + "locale": "en-GB", + "gender": "Female", + "usedname": "Mia", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-MiaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Michelle", + "alias": "Michelle", + "code": "en-US-MichelleNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Michelle", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-MichelleNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Mitchell", + "alias": "Mitchell", + "code": "en-NZ-MitchellNeural", + "locale": "en-NZ", + "gender": "Male", + "usedname": "Mitchell", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-NZ-MitchellNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Molly", + "alias": "Molly", + "code": "en-NZ-MollyNeural", + "locale": "en-NZ", + "gender": "Female", + "usedname": "Molly", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-NZ-MollyNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Monica", + "alias": "Monica", + "code": "en-US-MonicaNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Monica", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-MonicaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Nancy", + "alias": "Nancy", + "code": "en-US-NancyNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Nancy", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-NancyNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Nancy Multilingual", + "alias": "Nancy Multilingual", + "code": "en-US-NancyMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Nancy", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-NancyMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Natasha", + "alias": "Natasha", + "code": "en-AU-NatashaNeural", + "locale": "en-AU", + "gender": "Female", + "usedname": "Natasha", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-NatashaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Neerja", + "alias": "Neerja", + "code": "en-IN-NeerjaNeural", + "locale": "en-IN", + "gender": "Female", + "usedname": "Neerja", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-NeerjaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Neerja Indic", + "alias": "Neerja Indic", + "code": "en-IN-NeerjaIndicNeural", + "locale": "en-IN", + "gender": "Female", + "usedname": "Neerja", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-NeerjaIndicNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Neil", + "alias": "Neil", + "code": "en-AU-NeilNeural", + "locale": "en-AU", + "gender": "Male", + "usedname": "Neil", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-NeilNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Noah", + "alias": "Noah", + "code": "en-GB-NoahNeural", + "locale": "en-GB", + "gender": "Male", + "usedname": "Noah", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-NoahNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Nova Turbo Multilingual", + "alias": "Nova Turbo Multilingual", + "code": "en-US-NovaTurboMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Nova", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-NovaTurboMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ollie Multilingual", + "alias": "Ollie Multilingual", + "code": "en-GB-OllieMultilingualNeural", + "locale": "en-GB", + "gender": "Male", + "usedname": "Ollie", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-OllieMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Oliver", + "alias": "Oliver", + "code": "en-GB-OliverNeural", + "locale": "en-GB", + "gender": "Male", + "usedname": "Oliver", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-OliverNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Olivia", + "alias": "Olivia", + "code": "en-GB-OliviaNeural", + "locale": "en-GB", + "gender": "Female", + "usedname": "Olivia", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-OliviaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Onyx Turbo Multilingual", + "alias": "Onyx Turbo Multilingual", + "code": "en-US-OnyxTurboMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Onyx", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-OnyxTurboMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Phoebe Multilingual", + "alias": "Phoebe Multilingual", + "code": "en-US-PhoebeMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Phoebe", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-PhoebeMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Prabhat", + "alias": "Prabhat", + "code": "en-IN-PrabhatNeural", + "locale": "en-IN", + "gender": "Male", + "usedname": "Prabhat", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-PrabhatNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Prabhat Indic", + "alias": "Prabhat Indic", + "code": "en-IN-PrabhatIndicNeural", + "locale": "en-IN", + "gender": "Male", + "usedname": "Prabhat", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-PrabhatIndicNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Rehaan", + "alias": "Rehaan", + "code": "en-IN-RehaanNeural", + "locale": "en-IN", + "gender": "Male", + "usedname": "Rehaan", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-IN-RehaanNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Roger", + "alias": "Roger", + "code": "en-US-RogerNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Roger", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-RogerNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Rosa", + "alias": "Rosa", + "code": "en-PH-RosaNeural", + "locale": "en-PH", + "gender": "Female", + "usedname": "Rosa", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-PH-RosaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ryan", + "alias": "Ryan", + "code": "en-GB-RyanNeural", + "locale": "en-GB", + "gender": "Male", + "usedname": "Ryan", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-RyanNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Ryan Multilingual", + "alias": "Ryan Multilingual", + "code": "en-US-RyanMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Ryan", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-RyanMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Sam", + "alias": "Sam", + "code": "en-HK-SamNeural", + "locale": "en-HK", + "gender": "Male", + "usedname": "Sam", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-HK-SamNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Samuel Multilingual", + "alias": "Samuel Multilingual", + "code": "en-US-SamuelMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Samuel", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-SamuelMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Sara", + "alias": "Sara", + "code": "en-US-SaraNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Sara", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-SaraNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Serena Multilingual", + "alias": "Serena Multilingual", + "code": "en-US-SerenaMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Serena", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-SerenaMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Shimmer Turbo Multilingual", + "alias": "Shimmer Turbo Multilingual", + "code": "en-US-ShimmerTurboMultilingualNeural", + "locale": "en-US", + "gender": "Female", + "usedname": "Shimmer", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-ShimmerTurboMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Sonia", + "alias": "Sonia", + "code": "en-GB-SoniaNeural", + "locale": "en-GB", + "gender": "Female", + "usedname": "Sonia", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-SoniaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Steffan", + "alias": "Steffan", + "code": "en-US-SteffanNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Steffan", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-SteffanNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Steffan Multilingual", + "alias": "Steffan Multilingual", + "code": "en-US-SteffanMultilingualNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Steffan", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-SteffanMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Thomas", + "alias": "Thomas", + "code": "en-GB-ThomasNeural", + "locale": "en-GB", + "gender": "Male", + "usedname": "Thomas", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-GB-ThomasNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Tim", + "alias": "Tim", + "code": "en-AU-TimNeural", + "locale": "en-AU", + "gender": "Male", + "usedname": "Tim", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-TimNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Tina", + "alias": "Tina", + "code": "en-AU-TinaNeural", + "locale": "en-AU", + "gender": "Female", + "usedname": "Tina", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-TinaNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Tony", + "alias": "Tony", + "code": "en-US-TonyNeural", + "locale": "en-US", + "gender": "Male", + "usedname": "Tony", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-US-TonyNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Wayne", + "alias": "Wayne", + "code": "en-SG-WayneNeural", + "locale": "en-SG", + "gender": "Male", + "usedname": "Wayne", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-SG-WayneNeural.wav", + "owner": "edge-tts" + }, + { + "name": "William", + "alias": "William", + "code": "en-AU-WilliamNeural", + "locale": "en-AU", + "gender": "Male", + "usedname": "William", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-WilliamNeural.wav", + "owner": "edge-tts" + }, + { + "name": "William Multilingual", + "alias": "William Multilingual", + "code": "en-AU-WilliamMultilingualNeural", + "locale": "en-AU", + "gender": "Male", + "usedname": "William", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-AU-WilliamMultilingualNeural.wav", + "owner": "edge-tts" + }, + { + "name": "Yan", + "alias": "Yan", + "code": "en-HK-YanNeural", + "locale": "en-HK", + "gender": "Female", + "usedname": "Yan", + "volume_adjustment": 0, + "speed_adjustment": 0, + "audio": "https://podcasts.hubtoday.app/podcast/example/edgetts/en-HK-YanNeural.wav", + "owner": "edge-tts" + } + ] +} \ No newline at end of file diff --git a/server/main.py b/server/main.py index f464157..c8e625f 100644 --- a/server/main.py +++ b/server/main.py @@ -86,6 +86,7 @@ audio_file_mapping: Dict[str, Dict] = {} SECRET_KEY = os.getenv("PODCAST_API_SECRET_KEY", "your-super-secret-key") # 在生产环境中请务必修改! # 定义从 tts_provider 名称到其配置文件路径的映射 tts_provider_map = { + "webvoice": "../config/webvoice.json", "index-tts": "../config/index-tts.json", "doubao-tts": "../config/doubao-tts.json", "edge-tts": "../config/edge-tts.json", diff --git a/server/podcast_generator.py b/server/podcast_generator.py index 747842b..148d3e5 100644 --- a/server/podcast_generator.py +++ b/server/podcast_generator.py @@ -21,6 +21,19 @@ output_dir = "output" # file_list_path is now generated uniquely for each merge operation tts_providers_config_path = '../config/tts_providers.json' +# Global cache for TTS provider configurations +tts_provider_configs_cache = {} + +# Define the TTS provider map +tts_provider_map = { + "index-tts": "../config/index-tts.json", + "doubao-tts": "../config/doubao-tts.json", + "edge-tts": "../config/edge-tts.json", + "fish-audio": "../config/fish-audio.json", + "gemini-tts": "../config/gemini-tts.json", + "minimax": "../config/minimax.json", +} + def read_file_content(filepath): """Reads content from a given file path.""" try: @@ -360,18 +373,36 @@ def _load_configuration(): print("\nLoaded Configuration: " + tts_provider) return config_data -def _load_configuration_path(config_path: str) -> dict: - """Loads JSON configuration from a specified path and infers tts_provider from the file name.""" +def _load_configuration_path(config_path: str, pod_users: Optional[list] = None) -> dict: + """Loads JSON configuration from a specified path and infers tts_provider from the file name or podUsers owner.""" config_data = _load_json_config(config_path) - # 从文件名中提取 tts_provider + # 先从文件名中提取 tts_provider file_name = os.path.basename(config_path) - tts_provider = os.path.splitext(file_name)[0] # 移除 .json 扩展名 + default_tts_provider = os.path.splitext(file_name)[0] # 移除 .json 扩展名 + + # 如果提供了 pod_users 参数,则使用它;否则从配置中获取 + if pod_users is None: + pod_users = config_data.get("podUsers", []) + + # 从 podUsers 中获取所有不同的 owner 值,用逗号分隔 + owners = [] + if pod_users: # 添加空值检查 + owners = list(set(user.get("owner") for user in pod_users if user.get("owner"))) + + # 如果找到了 owners,则使用逗号分隔的 owners 作为 tts_provider + if owners: + tts_provider = ",".join(owners) + print(f"Found multiple owners in podUsers: {owners}. Using comma-separated tts_provider: {tts_provider}") + else: + # 否则使用默认的从文件名提取的 tts_provider + tts_provider = "edge-tts" + print(f"No owners found in podUsers. Using default tts_provider from file name: {tts_provider}") config_data["tts_provider"] = tts_provider # 将 tts_provider 添加到配置数据中 print(f"\nLoaded Configuration: {tts_provider} from {config_path}") - return config_data + return config_data def _prepare_openai_settings(args, config_data): """Determines final OpenAI API key, base URL, and model based on priority.""" @@ -428,86 +459,180 @@ def _prepare_podcast_prompts(config_data, original_podscript_prompt, custom_cont podscript_prompt = speaker_id_info + "\n\n" + custom_content + "\n\n" + original_podscript_prompt return podscript_prompt, pod_users, voices, turn_pattern # Return voices for potential future use or consistency +def _is_content_quality_acceptable(content: str, title: str, tags: str, content_type: str = "overview") -> bool: + """Checks if the generated content meets quality standards.""" + if content_type == "overview": + # Check if overview content is not empty and has reasonable length + if not content or len(content.strip()) < 20: + return False + if not title or len(title.strip()) < 2: + return False + if not tags or len(tags.strip()) < 1: + return False + return True + elif content_type == "script": + try: + # Check if the content contains valid podcast script JSON with transcripts + podcast_script = json.loads(content) + if "podcast_transcripts" not in podcast_script: + return False + transcripts = podcast_script.get("podcast_transcripts", []) + if not transcripts or len(transcripts) == 0: + return False + # Check if transcripts have required fields (speaker_id and dialog) + for transcript in transcripts: + if "speaker_id" not in transcript or "dialog" not in transcript: + return False + dialog = transcript.get("dialog", "").strip() + if not dialog or len(dialog) < 1: + return False + return True + except json.JSONDecodeError: + return False + return False + + def _generate_overview_content(api_key, base_url, model, overview_prompt, input_prompt, output_language: Optional[str] = None) -> Tuple[str, str, str]: """Generates overview content using OpenAI CLI, and extracts title and tags.""" print(f"\nGenerating overview with OpenAI CLI (Output Language: {output_language})...") - try: - # Replace the placeholder with the actual output language - formatted_overview_prompt = overview_prompt.replace("{{outlang}}", output_language if output_language is not None else "Make sure the input language is set as the output language") - - openai_client_overview = OpenAICli(api_key=api_key, base_url=base_url, model=model, system_message=formatted_overview_prompt) - overview_response_generator = openai_client_overview.chat_completion(messages=[{"role": "user", "content": input_prompt}]) - overview_content = "".join([chunk.choices[0].delta.content for chunk in overview_response_generator if chunk.choices and chunk.choices[0].delta.content]) - # Extract title (first line) and tags (second line) - lines = overview_content.strip().split('\n') - title = lines[0].strip() if len(lines) > 0 else "" - tags = "" - # 重复判断3次是否有非空值,没有值就取下一行 - for i in range(1, min(len(lines), 4)): # 检查第2到第4行 (索引1到3) - current_tags = lines[i].strip() - if current_tags: - tags = current_tags - # 保留取到tags的索引行,从下一行开始截取到最后一行,保存数据到overview_content - overview_content = "\n".join(lines[i+1:]).strip() - break - else: # 如果循环结束没有找到非空tags,则从第二行开始截取 - overview_content = "\n".join(lines[1:]).strip() + max_retries = 3 + attempt = 0 - print(f"Extracted Title: {title}") - print(f"Extracted Tags: {tags}") - print("Generated Overview:") - print(overview_content[:100]) - - return overview_content, title, tags - except Exception as e: - raise RuntimeError(f"Error generating overview: {e}") + while attempt < max_retries: + try: + # Replace the placeholder with the actual output language + formatted_overview_prompt = overview_prompt.replace("{{outlang}}", output_language if output_language is not None else "Make sure the input language is set as the output language") + + openai_client_overview = OpenAICli(api_key=api_key, base_url=base_url, model=model, system_message=formatted_overview_prompt) + overview_response_generator = openai_client_overview.chat_completion(messages=[{"role": "user", "content": input_prompt}]) + overview_content = "".join([chunk.choices[0].delta.content for chunk in overview_response_generator if chunk.choices and chunk.choices[0].delta.content]) + + # Extract title (first line) and tags (second line) + lines = overview_content.strip().split('\n') + title = lines[0].strip() if len(lines) > 0 else "" + tags = "" + # 重复判断3次是否有非空值,没有值就取下一行 + for i in range(1, min(len(lines), 4)): # 检查第2到第4行 (索引1到3) + current_tags = lines[i].strip() + if current_tags: + tags = current_tags + # 保留取到tags的索引行,从下一行开始截取到最后一行,保存数据到overview_content + overview_content = "\n".join(lines[i+1:]).strip() + break + else: # 如果循环结束没有找到非空tags,则从第二行开始截取 + overview_content = "\n".join(lines[1:]).strip() + + # Check if the generated content meets quality standards + if _is_content_quality_acceptable(overview_content, title, tags, "overview"): + print(f"Generated overview content meets quality standards on attempt {attempt + 1}") + print(f"Extracted Title: {title}") + print(f"Extracted Tags: {tags}") + print("Generated Overview:") + print(overview_content[:100]) + + return overview_content, title, tags + else: + print(f"Generated overview content did not meet quality standards, attempt {attempt + 1}/{max_retries}") + attempt += 1 + if attempt >= max_retries: + raise RuntimeError(f"Failed to generate acceptable overview content after {max_retries} attempts. Content may be too short or missing required elements.") + else: + print(f"Retrying overview generation...") + continue + except Exception as e: + attempt += 1 + if attempt >= max_retries: + raise RuntimeError(f"Error generating overview after {max_retries} attempts: {e}") + else: + print(f"Attempt {attempt}/{max_retries} failed: {e}. Retrying...") + time.sleep(1 * attempt) # Exponential backoff def _generate_podcast_script(api_key, base_url, model, podscript_prompt, overview_content): """Generates and parses podcast script JSON using OpenAI CLI.""" print("\nGenerating podcast script with OpenAI CLI...") - # Initialize podscript_json_str outside try block to ensure it's always defined - podscript_json_str = "" - try: - openai_client_podscript = OpenAICli(api_key=api_key, base_url=base_url, model=model, system_message=podscript_prompt) - # Generate the response string first - podscript_json_str = "".join([chunk.choices[0].delta.content for chunk in openai_client_podscript.chat_completion(messages=[{"role": "user", "content": overview_content}]) if chunk.choices and chunk.choices[0].delta.content]) - podcast_script = None - decoder = json.JSONDecoder() - idx = 0 - valid_json_str = "" + max_retries = 3 + attempt = 0 - while idx < len(podscript_json_str): - try: - obj, end = decoder.raw_decode(podscript_json_str[idx:]) - if isinstance(obj, dict) and "podcast_transcripts" in obj: - podcast_script = obj - valid_json_str = podscript_json_str[idx : idx + end] - break - idx += end - except json.JSONDecodeError: - idx += 1 - next_brace = podscript_json_str.find('{', idx) - if next_brace != -1: - idx = next_brace + while attempt < max_retries: + # Initialize podscript_json_str outside try block to ensure it's always defined + podscript_json_str = "" + try: + openai_client_podscript = OpenAICli(api_key=api_key, base_url=base_url, model=model, system_message=podscript_prompt) + # Generate the response string first + podscript_json_str = "".join([chunk.choices[0].delta.content for chunk in openai_client_podscript.chat_completion(messages=[{"role": "user", "content": overview_content}]) if chunk.choices and chunk.choices[0].delta.content]) + + podcast_script = None + decoder = json.JSONDecoder() + idx = 0 + valid_json_str = "" + + while idx < len(podscript_json_str): + try: + obj, end = decoder.raw_decode(podscript_json_str[idx:]) + if isinstance(obj, dict) and "podcast_transcripts" in obj: + podcast_script = obj + valid_json_str = podscript_json_str[idx : idx + end] + break + idx += end + except json.JSONDecodeError: + idx += 1 + next_brace = podscript_json_str.find('{', idx) + if next_brace != -1: + idx = next_brace + else: + break + + if podcast_script is None: + print(f"Could not find a valid podcast script JSON object with 'podcast_transcripts' key in response, attempt {attempt + 1}/{max_retries}") + attempt += 1 + if attempt >= max_retries: + raise ValueError(f"Error: Could not find a valid podcast script JSON object with 'podcast_transcripts' key in response. Raw response: {podscript_json_str}") else: - break + print(f"Retrying podcast script generation...") + continue - if podcast_script is None: - raise ValueError(f"Error: Could not find a valid podcast script JSON object with 'podcast_transcripts' key in response. Raw response: {podscript_json_str}") + print("\nGenerated Podcast Script Length:"+ str(len(podcast_script.get("podcast_transcripts") or []))) + print(valid_json_str[:100] + "...") - print("\nGenerated Podcast Script Length:"+ str(len(podcast_script.get("podcast_transcripts") or []))) - print(valid_json_str[:100] + "...") - if not podcast_script.get("podcast_transcripts"): - raise ValueError("Error: 'podcast_transcripts' array is empty or not found in the generated script. Nothing to convert to audio.") - return podcast_script - except json.JSONDecodeError as e: - raise ValueError(f"Error decoding JSON from podcast script response: {e}. Raw response: {podscript_json_str}") - except Exception as e: - raise RuntimeError(f"Error generating podcast script: {e}") + if not podcast_script.get("podcast_transcripts"): + print(f"'podcast_transcripts' array is empty or not found in the generated script, attempt {attempt + 1}/{max_retries}") + attempt += 1 + if attempt >= max_retries: + raise ValueError("Error: 'podcast_transcripts' array is empty or not found in the generated script. Nothing to convert to audio.") + else: + print(f"Retrying podcast script generation...") + continue -def generate_audio_for_item(item, config_data, tts_adapter: TTSAdapter, max_retries: int = 3): + # Check if the generated script meets quality standards + if _is_content_quality_acceptable(valid_json_str, "", "", "script"): + print(f"Generated podcast script meets quality standards on attempt {attempt + 1}") + return podcast_script + else: + print(f"Generated podcast script did not meet quality standards, attempt {attempt + 1}/{max_retries}") + attempt += 1 + if attempt >= max_retries: + raise ValueError(f"Failed to generate acceptable podcast script after {max_retries} attempts. Script may be missing required elements.") + else: + print(f"Retrying podcast script generation...") + continue + except json.JSONDecodeError as e: + attempt += 1 + if attempt >= max_retries: + raise ValueError(f"Error decoding JSON from podcast script response: {e}. Raw response: {podscript_json_str}") + else: + print(f"JSON decode error on attempt {attempt}: {e}. Retrying...") + time.sleep(1 * attempt) # Exponential backoff + except Exception as e: + attempt += 1 + if attempt >= max_retries: + raise RuntimeError(f"Error generating podcast script after {max_retries} attempts: {e}") + else: + print(f"Attempt {attempt}/{max_retries} failed: {e}. Retrying...") + time.sleep(1 * attempt) # Exponential backoff + +def generate_audio_for_item(item, config_data, tts_adapter, max_retries: int = 3): """Generate audio for a single podcast transcript item using the provided TTS adapter.""" speaker_id = item.get("speaker_id") dialog = item.get("dialog") @@ -515,11 +640,14 @@ def generate_audio_for_item(item, config_data, tts_adapter: TTSAdapter, max_retr voice_code = None volume_adjustment = 0.0 # 默认值为 0.0 speed_adjustment = 0.0 # 默认值为 0.0 + voice_tts_provider = None # 默认使用主要的 TTS 提供商 if config_data and "podUsers" in config_data and 0 <= speaker_id < len(config_data["podUsers"]): pod_user_entry = config_data["podUsers"][speaker_id] voice_code = pod_user_entry.get("code") + voice_tts_provider = pod_user_entry.get("owner") # 获取特定于该说话者的 TTS 提供商 + # 从 voices 列表中获取对应的 volume_adjustment voice_map = {voice.get("code"): voice for voice in config_data.get("voices", []) if voice.get("code")} volume_adjustment = voice_map.get(voice_code, {}).get("volume_adjustment", 0.0) @@ -527,15 +655,17 @@ def generate_audio_for_item(item, config_data, tts_adapter: TTSAdapter, max_retr if not voice_code: raise ValueError(f"No voice code found for speaker_id {speaker_id}. Cannot generate audio for this dialog.") - + + # 如果 tts_adapter 是映射对象,则根据 voice_tts_provider 选择对应的适配器 + selected_adapter = tts_adapter[voice_tts_provider] # print(f"dialog-before: {dialog}") dialog = re.sub(r'[^\w\s\-,,.。??!!\u4e00-\u9fa5]', '', dialog) print(f"dialog: {dialog}") for attempt in range(max_retries): try: - print(f"Calling TTS API for speaker {speaker_id} ({voice_code}) (Attempt {attempt + 1}/{max_retries})...") - temp_audio_file = tts_adapter.generate_audio( + print(f"Calling TTS API for speaker {speaker_id} ({voice_code}) with adapter (Attempt {attempt + 1}/{max_retries})...") + temp_audio_file = selected_adapter.generate_audio( text=dialog, voice_code=voice_code, output_dir=output_dir, @@ -554,7 +684,7 @@ def generate_audio_for_item(item, config_data, tts_adapter: TTSAdapter, max_retr except Exception as e: # Catch other unexpected errors raise RuntimeError(f"An unexpected error occurred for speaker {speaker_id} ({voice_code}) on attempt {attempt + 1}: {e}") -def _generate_all_audio_files(podcast_script, config_data, tts_adapter: TTSAdapter, threads): +def _generate_all_audio_files(podcast_script, config_data, tts_adapter, threads): """Orchestrates the generation of individual audio files.""" os.makedirs(output_dir, exist_ok=True) print("\nGenerating audio files...") @@ -634,15 +764,41 @@ def _create_ffmpeg_file_list(audio_files, expected_count: int): from typing import cast # Add import for cast -def _initialize_tts_adapter(config_data: dict, tts_providers_config_content: Optional[str] = None) -> TTSAdapter: - +def initialize_tts_provider_configs(): """ - 根据配置数据初始化并返回相应的 TTS 适配器。 + 初始化并缓存所有 TTS 提供商的配置 + """ + global tts_provider_configs_cache + global tts_provider_map + + # 清空现有缓存 + tts_provider_configs_cache = {} + + # 加载预定义映射中的配置文件 + for provider, config_path in tts_provider_map.items(): + try: + config_data = _load_json_config(config_path) + tts_provider_configs_cache[provider] = config_data # 例如 'doubao-tts' -> 'doubao' + except FileNotFoundError: + print(f"Warning: Configuration file not found for {provider}: {config_path}") + except json.JSONDecodeError as e: + print(f"Warning: Invalid JSON in configuration file for {provider}: {config_path}, Error: {e}") + except Exception as e: + print(f"Warning: Could not load configuration for {provider}: {config_path}, Error: {e}") + +def _initialize_tts_adapter(config_data: dict, tts_providers_config_content: Optional[str] = None) -> dict: + """ + 根据配置数据初始化并返回相应的 TTS 适配器映射对象。 + 支持逗号分隔的 tts_provider 值,返回每个 provider 对应的适配器映射对象 """ tts_provider = config_data.get("tts_provider") if not tts_provider: raise ValueError("TTS provider is not specified in the configuration.") + # 如果缓存为空,则初始化缓存 + if not tts_provider_configs_cache: + initialize_tts_provider_configs() + tts_providers_config = {} try: if tts_providers_config_content: @@ -653,50 +809,64 @@ def _initialize_tts_adapter(config_data: dict, tts_providers_config_content: Opt except Exception as e: print(f"Warning: Could not load tts_providers.json: {e}") - # 获取当前 tts_provider 的额外参数 - current_tts_extra_params = tts_providers_config.get(tts_provider.split('-')[0], {}) # 例如 'doubao-tts' -> 'doubao' + # 支持逗号分隔的 tts_provider + providers = [provider.strip() for provider in tts_provider.split(',')] + + adapters_map = {} + for provider in providers: + # 从缓存中获取当前 tts_provider 的额外参数 + current_tts_config_params = tts_provider_configs_cache.get(provider, {}) + current_tts_extra_params = tts_providers_config.get(provider.split('-')[0], {}) # 例如 'doubao-tts' -> 'doubao' - if tts_provider == "index-tts": - api_url = config_data.get("apiUrl") - if not api_url: - raise ValueError("IndexTTS apiUrl is not configured.") - return IndexTTSAdapter(api_url_template=cast(str, api_url), tts_extra_params=cast(dict, current_tts_extra_params)) - elif tts_provider == "edge-tts": - api_url = config_data.get("apiUrl") - if not api_url: - raise ValueError("EdgeTTS apiUrl is not configured.") - return EdgeTTSAdapter(api_url_template=cast(str, api_url), tts_extra_params=cast(dict, current_tts_extra_params)) + if provider == "index-tts": + # 优先从 config_data 获取,如果没有则从缓存中获取 + api_url = config_data.get("apiUrl") or current_tts_config_params.get("apiUrl") + if not api_url: + raise ValueError("IndexTTS apiUrl is not configured.") + adapters_map[provider] = IndexTTSAdapter(api_url_template=cast(str, api_url), tts_extra_params=cast(dict, current_tts_extra_params)) + elif provider == "edge-tts": + # 优先从 config_data 获取,如果没有则从缓存中获取 + api_url = config_data.get("apiUrl") or current_tts_config_params.get("apiUrl") + if not api_url: + raise ValueError("EdgeTTS apiUrl is not configured.") + adapters_map[provider] = EdgeTTSAdapter(api_url_template=cast(str, api_url), tts_extra_params=cast(dict, current_tts_extra_params)) - elif tts_provider == "fish-audio": - api_url = config_data.get("apiUrl") - headers = config_data.get("headers") - request_payload = config_data.get("request_payload") - if not all([api_url, headers, request_payload]): - raise ValueError("FishAudio requires apiUrl, headers, and request_payload configuration.") - return FishAudioAdapter(api_url=cast(str, api_url), headers=cast(dict, headers), request_payload_template=cast(dict, request_payload), tts_extra_params=cast(dict, current_tts_extra_params)) - elif tts_provider == "minimax": - api_url = config_data.get("apiUrl") - headers = config_data.get("headers") - request_payload = config_data.get("request_payload") - if not all([api_url, headers, request_payload]): - raise ValueError("Minimax requires apiUrl, headers, and request_payload configuration.") - return MinimaxAdapter(api_url=cast(str, api_url), headers=cast(dict, headers), request_payload_template=cast(dict, request_payload), tts_extra_params=cast(dict, current_tts_extra_params)) - elif tts_provider == "doubao-tts": - api_url = config_data.get("apiUrl") - headers = config_data.get("headers") - request_payload = config_data.get("request_payload") - if not all([api_url, headers, request_payload]): - raise ValueError("DoubaoTTS requires apiUrl, headers, and request_payload configuration.") - return DoubaoTTSAdapter(api_url=cast(str, api_url), headers=cast(dict, headers), request_payload_template=cast(dict, request_payload), tts_extra_params=cast(dict, current_tts_extra_params)) - elif tts_provider == "gemini-tts": - api_url = config_data.get("apiUrl") - headers = config_data.get("headers") - request_payload = config_data.get("request_payload") - if not all([api_url, headers, request_payload]): - raise ValueError("GeminiTTS requires apiUrl, headers, and request_payload configuration.") - return GeminiTTSAdapter(api_url=cast(str, api_url), headers=cast(dict, headers), request_payload_template=cast(dict, request_payload), tts_extra_params=cast(dict, current_tts_extra_params)) - else: - raise ValueError(f"Unsupported TTS provider: {tts_provider}") + elif provider == "fish-audio": + # 优先从 config_data 获取,如果没有则从缓存中获取 + api_url = config_data.get("apiUrl") or current_tts_config_params.get("apiUrl") + headers = config_data.get("headers") or current_tts_config_params.get("headers") + request_payload = config_data.get("request_payload") or current_tts_config_params.get("request_payload") + if not all([api_url, headers, request_payload]): + raise ValueError("FishAudio requires apiUrl, headers, and request_payload configuration.") + adapters_map[provider] = FishAudioAdapter(api_url=cast(str, api_url), headers=cast(dict, headers), request_payload_template=cast(dict, request_payload), tts_extra_params=cast(dict, current_tts_extra_params)) + elif provider == "minimax": + # 优先从 config_data 获取,如果没有则从缓存中获取 + api_url = config_data.get("apiUrl") or current_tts_config_params.get("apiUrl") + headers = config_data.get("headers") or current_tts_config_params.get("headers") + request_payload = config_data.get("request_payload") or current_tts_config_params.get("request_payload") + if not all([api_url, headers, request_payload]): + raise ValueError("Minimax requires apiUrl, headers, and request_payload configuration.") + adapters_map[provider] = MinimaxAdapter(api_url=cast(str, api_url), headers=cast(dict, headers), request_payload_template=cast(dict, request_payload), tts_extra_params=cast(dict, current_tts_extra_params)) + elif provider == "doubao-tts": + # 优先从 config_data 获取,如果没有则从缓存中获取 + api_url = config_data.get("apiUrl") or current_tts_config_params.get("apiUrl") + headers = config_data.get("headers") or current_tts_config_params.get("headers") + request_payload = config_data.get("request_payload") or current_tts_config_params.get("request_payload") + if not all([api_url, headers, request_payload]): + raise ValueError("DoubaoTTS requires apiUrl, headers, and request_payload configuration.") + adapters_map[provider] = DoubaoTTSAdapter(api_url=cast(str, api_url), headers=cast(dict, headers), request_payload_template=cast(dict, request_payload), tts_extra_params=cast(dict, current_tts_extra_params)) + elif provider == "gemini-tts": + # 优先从 config_data 获取,如果没有则从缓存中获取 + api_url = config_data.get("apiUrl") or current_tts_config_params.get("apiUrl") + headers = config_data.get("headers") or current_tts_config_params.get("headers") + request_payload = config_data.get("request_payload") or current_tts_config_params.get("request_payload") + if not all([api_url, headers, request_payload]): + raise ValueError("GeminiTTS requires apiUrl, headers, and request_payload configuration.") + adapters_map[provider] = GeminiTTSAdapter(api_url=cast(str, api_url), headers=cast(dict, headers), request_payload_template=cast(dict, request_payload), tts_extra_params=cast(dict, current_tts_extra_params)) + else: + raise ValueError(f"Unsupported TTS provider: {provider}") + + return adapters_map def generate_podcast_audio(): args = _parse_arguments() @@ -714,7 +884,7 @@ def generate_podcast_audio(): overview_content, title, tags = _generate_overview_content(api_key, base_url, model, overview_prompt, input_prompt, args.output_language) podcast_script = _generate_podcast_script(api_key, base_url, model, podscript_prompt, overview_content) - tts_adapter = _initialize_tts_adapter(config_data) # 初始化 TTS 适配器 + tts_adapter = _initialize_tts_adapter(config_data) # 初始化 TTS 适配器,现在返回适配器映射 audio_files = _generate_all_audio_files(podcast_script, config_data, tts_adapter, args.threads) file_list_path_created = _create_ffmpeg_file_list(audio_files, len(podcast_script.get("podcast_transcripts", []))) @@ -744,8 +914,8 @@ def generate_podcast_audio_api(args, config_path: str, input_txt_content: str, t str: The path to the generated audio file. """ print("Starting podcast audio generation...") - config_data = _load_configuration_path(config_path) podUsers = json.loads(podUsers_json_content) + config_data = _load_configuration_path(config_path, podUsers) config_data["podUsers"] = podUsers final_api_key, final_base_url, final_model = _prepare_openai_settings(args, config_data) @@ -761,7 +931,7 @@ def generate_podcast_audio_api(args, config_path: str, input_txt_content: str, t overview_content, title, tags = _generate_overview_content(final_api_key, final_base_url, final_model, overview_prompt, input_prompt, args.output_language) podcast_script = _generate_podcast_script(final_api_key, final_base_url, final_model, podscript_prompt, overview_content) - tts_adapter = _initialize_tts_adapter(config_data, tts_providers_config_content) # 初始化 TTS 适配器 + tts_adapter = _initialize_tts_adapter(config_data, tts_providers_config_content) # 初始化 TTS 适配器,现在返回适配器映射 audio_files = _generate_all_audio_files(podcast_script, config_data, tts_adapter, args.threads) file_list_path_created = _create_ffmpeg_file_list(audio_files, len(podcast_script.get("podcast_transcripts", []))) @@ -787,6 +957,9 @@ def generate_podcast_audio_api(args, config_path: str, input_txt_content: str, t if __name__ == "__main__": + # Initialize TTS provider configs cache at startup + initialize_tts_provider_configs() + start_time = time.time() try: generate_podcast_audio() diff --git a/web/src/app/[lang]/page.tsx b/web/src/app/[lang]/page.tsx index 596e319..edee43c 100644 --- a/web/src/app/[lang]/page.tsx +++ b/web/src/app/[lang]/page.tsx @@ -56,7 +56,8 @@ export default function HomePage({ params }: { params: Promise<{ lang: string }> const { lang } = use(params); const { t } = useTranslation(lang, 'home'); const { toasts, success, error, warning, info, removeToast } = useToast(); - const { executeOnce } = usePreventDuplicateCall(); + const { executeOnce: executeOncePodcasts } = usePreventDuplicateCall(); + const { executeOnce: executeOnceCredits } = usePreventDuplicateCall(); const router = useRouter(); // Initialize useRouter // 辅助函数:将 API 响应映射为 PodcastItem 数组 @@ -72,7 +73,7 @@ export default function HomePage({ params }: { params: Promise<{ lang: string }> }, audio_duration: task.audio_duration || '00:00', playCount: 0, - createdAt: task.timestamp ? new Date(task.timestamp * 1000).toISOString() : new Date().toISOString(), + createdAt: task.timestamp ? new Date(task.timestamp * 1000).toISOString() : '', // 使用空字符串而不是当前时间,避免水合错误 audioUrl: task.audioUrl ? task.audioUrl : '', tags: task.tags ? task.tags.split('#').map((tag: string) => tag.trim()).filter((tag: string) => !!tag) : task.status === 'failed' ? [task.error] : [t('podcastTagsPlaceholder')], status: task.status, @@ -105,18 +106,11 @@ export default function HomePage({ params }: { params: Promise<{ lang: string }> // 播客详情页状态 // 从后端获取积分数据和初始化数据加载 - const initialized = React.useRef(false); // 使用 useRef 追踪是否已初始化 - useEffect(() => { - // 确保只在组件首次挂载时执行一次 - if (!initialized.current) { - initialized.current = true; - - // 首次加载时获取播客列表和积分/用户信息 - fetchRecentPodcasts(); - // fetchCreditsAndUserInfo(); // 在fetchRecentPodcasts中调用 - - } + console.log('HomePage mounted: 初始化数据加载'); + // 首次加载时获取播客列表和积分/用户信息 + fetchRecentPodcasts(); + // fetchCreditsAndUserInfo(); // 在fetchRecentPodcasts中调用 // 设置定时器每20秒刷新一次 // const interval = setInterval(() => { @@ -124,7 +118,10 @@ export default function HomePage({ params }: { params: Promise<{ lang: string }> // }, 20000); // // 清理定时器 - // return () => clearInterval(interval); + // return () => { + // clearInterval(interval); + // console.log('HomePage unmounted: 清理定时器'); + // }; }, []); // 空依赖数组,只在组件挂载时执行一次 // 加载设置 @@ -267,7 +264,7 @@ export default function HomePage({ params }: { params: Promise<{ lang: string }> // 获取最近播客列表 - 使用防重复调用机制 const fetchRecentPodcasts = async () => { - const result = await executeOnce(async () => { + const result = await executeOncePodcasts(async () => { const response = await trackedFetch('/api/podcast-status', { method: 'GET', headers: { @@ -282,6 +279,7 @@ export default function HomePage({ params }: { params: Promise<{ lang: string }> }); if (!result) { + console.log('fetchRecentPodcasts: 重复调用已跳过'); return; // 如果是重复调用,直接返回 } @@ -298,61 +296,83 @@ export default function HomePage({ params }: { params: Promise<{ lang: string }> error(t('error.dataProcessing'), err instanceof Error ? err.message : t('error.cantProcessPodcastList')); } - fetchCreditsAndUserInfo(); + // 调用积分和用户信息获取(也有防重复机制) + await fetchCreditsAndUserInfo(); }; - // 新增辅助函数:获取积分和用户信息 + // 新增辅助函数:获取积分和用户信息 - 使用防重复调用机制 const fetchCreditsAndUserInfo = async () => { + const result = await executeOnceCredits(async () => { + const results = { + credits: 0, + transactions: [] as any[], + user: null as any, + }; + + // 获取积分 try { - const pointsResponse = await fetch('/api/points', { - method: 'GET', - headers: { - 'x-next-locale': lang, - }, - }); - if (pointsResponse.ok) { - const data = await pointsResponse.json(); - if (data.success) { - setCredits(data.points); - } else { - console.error('Failed to fetch credits:', data.error); - setCredits(0); // 获取失败则设置为0 - } + const pointsResponse = await trackedFetch('/api/points', { + method: 'GET', + headers: { + 'x-next-locale': lang, + }, + }); + if (pointsResponse.ok) { + const data = await pointsResponse.json(); + if (data.success) { + results.credits = data.points; } else { - console.error('Failed to fetch credits with status:', pointsResponse.status); - setCredits(0); // 获取失败则设置为0 + console.error('Failed to fetch credits:', data.error); } + } else { + console.error('Failed to fetch credits with status:', pointsResponse.status); + } } catch (error) { - console.error('Error fetching credits:', error); - setCredits(0); // 发生错误则设置为0 + console.error('Error fetching credits:', error); } + // 获取积分历史 try { - const transactionsResponse = await fetch('/api/points/transactions', { - method: 'GET', - headers: { - 'x-next-locale': lang, - }, - }); - if (transactionsResponse.ok) { - const data = await transactionsResponse.json(); - if (data.success) { - setPointHistory(data.transactions); - } else { - console.error('Failed to fetch point transactions:', data.error); - setPointHistory([]); - } + const transactionsResponse = await trackedFetch('/api/points/transactions', { + method: 'GET', + headers: { + 'x-next-locale': lang, + }, + }); + if (transactionsResponse.ok) { + const data = await transactionsResponse.json(); + if (data.success) { + results.transactions = data.transactions; } else { - console.error('Failed to fetch point transactions with status:', transactionsResponse.status); - setPointHistory([]); + console.error('Failed to fetch point transactions:', data.error); } + } else { + console.error('Failed to fetch point transactions with status:', transactionsResponse.status); + } } catch (error) { - console.error('Error fetching point transactions:', error); - setPointHistory([]); + console.error('Error fetching point transactions:', error); } - const { session, user } = await getSessionData(); - setUser(user); // 设置用户信息 + // 获取用户信息 + try { + const { session, user } = await getSessionData(); + results.user = user; + } catch (error) { + console.error('Error fetching session data:', error); + } + + return results; + }); + + if (!result) { + console.log('fetchCreditsAndUserInfo: 重复调用已跳过'); + return; // 如果是重复调用,直接返回 + } + + // 更新状态 + setCredits(result.credits); + setPointHistory(result.transactions); + setUser(result.user); }; const renderMainContent = () => { diff --git a/web/src/app/api/config/route.ts b/web/src/app/api/config/route.ts index 66613b6..dcae853 100644 --- a/web/src/app/api/config/route.ts +++ b/web/src/app/api/config/route.ts @@ -33,6 +33,7 @@ const TTS_PROVIDER_ORDER = [ 'fish-audio', 'gemini-tts', 'index-tts', + 'webvoice', ]; // 获取配置文件列表 diff --git a/web/src/app/api/newuser/route.ts b/web/src/app/api/newuser/route.ts index 2066a3c..41543ba 100644 --- a/web/src/app/api/newuser/route.ts +++ b/web/src/app/api/newuser/route.ts @@ -6,18 +6,17 @@ import { fallbackLng } from '@/i18n/settings'; export async function GET(request: NextRequest) { const sessionData = await getSessionData(); - let baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "/"; - const pathname = request.nextUrl.searchParams.get('pathname'); - if(!!pathname){ - baseUrl += pathname.replace('/',''); - } + const pathname = request.nextUrl.searchParams.get('pathname') || ''; - // 如果没有获取到 session,直接重定向到根目录 + // 如果没有获取到 session,直接重定向 if (!sessionData?.user) { - const url = new URL(baseUrl, request.url); + const url = new URL(request.url); + url.pathname = pathname || '/'; + url.search = ''; return NextResponse.redirect(url); } + const lng = !pathname ? fallbackLng : pathname.replace('/',''); const { t } = await getTranslation(lng, 'components'); const userId = sessionData.user.id; // 获取 userId @@ -41,8 +40,11 @@ export async function GET(request: NextRequest) { console.log(t('newUser.pointsAccountExists', { userId })); } - // 创建一个 URL 对象,指向要重定向到的根目录 - const url = new URL(baseUrl, request.url); + // 构建重定向 URL + const url = new URL(request.url); + url.pathname = pathname ? `${pathname}/` : '/'; + url.search = ''; + // 返回重定向响应 return NextResponse.redirect(url); } \ No newline at end of file diff --git a/web/src/components/ConfigSelector.tsx b/web/src/components/ConfigSelector.tsx index 85ce725..2ab9084 100644 --- a/web/src/components/ConfigSelector.tsx +++ b/web/src/components/ConfigSelector.tsx @@ -26,7 +26,7 @@ const ConfigSelector: React.FC = ({ }) => { const { t } = useTranslation(lang, 'components'); // 初始化 useTranslation 并指定命名空间 const [configFiles, setConfigFiles] = useState([]); - const [selectedConfig, setSelectedConfig] = useState(''); + const [selectedConfig, setSelectedConfig] = useState(); const [currentConfig, setCurrentConfig] = useState(null); const [voices, setVoices] = useState([]); // 新增 voices 状态 const [isOpen, setIsOpen] = useState(false); @@ -52,6 +52,9 @@ const ConfigSelector: React.FC = ({ return !!(settings.minimax?.group_id && settings.minimax?.api_key); case 'gemini': return !!(settings.gemini?.api_key); + case 'webvoice': + // webvoice 使用浏览器内置的 Web Speech API,无需额外配置 + return true; default: return false; } @@ -95,36 +98,43 @@ const ConfigSelector: React.FC = ({ loadConfigFilesCalled.current = true; try { - const response = await fetch('/api/config', { - method: 'GET', - headers: { - 'x-next-locale': lang, - }, - }); - const result = await response.json(); + // const response = await fetch('/api/config', { + // method: 'GET', + // headers: { + // 'x-next-locale': lang, + // }, + // }); + // const result = await response.json(); - if (result.success && Array.isArray(result.data)) { + // if (result.success && Array.isArray(result.data)) { // 过滤出已配置的TTS选项 - const settings = await getTTSProviders(lang); - const availableConfigs = result.data.filter((config: ConfigFile) => - isTTSConfigured(config.name, settings) - ); - + // const settings = await getTTSProviders(lang); + // const availableConfigs = result.data.filter((config: ConfigFile) => + // isTTSConfigured(config.name, settings) + // ); + + const availableConfigs = [ + { + name: 'webvoice.json', + displayName: 'webvoice', + path: 'webvoice.json', + }, + ]; setConfigFiles(availableConfigs); // 默认选择第一个可用配置 if (availableConfigs.length > 0 && !selectedConfig) { - setSelectedConfig(availableConfigs[0].name); - loadConfig(availableConfigs[0].name); + setSelectedConfig(availableConfigs[0].name); + loadConfig(availableConfigs[0].name); } else if (availableConfigs.length === 0) { // 如果没有可用配置,清空当前选择 setSelectedConfig(''); setCurrentConfig(null); onConfigChange?.(null as any, '', []); // 传递空数组作为 voices } - } else { - console.error('Invalid config files data:', result); - setConfigFiles([]); - } + // } else { + // console.error('Invalid config files data:', result); + // setConfigFiles([]); + // } } catch (error) { console.error('Failed to process config files:', error); setConfigFiles([]); @@ -163,56 +173,61 @@ const ConfigSelector: React.FC = ({ return (
- {/* 配置选择器 */} - + {/* 隐藏TTS选择按钮 */} + {false && ( + <> + {/* 配置选择器 */} + - {/* 下拉菜单 */} - {isOpen && ( -
- {Array.isArray(configFiles) && configFiles.length > 0 ? configFiles.map((config) => ( - + )) : ( +
+
{t('configSelector.noAvailableTTSConfig')}
+
{t('configSelector.pleaseConfigTTS')}
-
- {selectedConfig === config.name && ( - )} - - )) : ( -
-
{t('configSelector.noAvailableTTSConfig')}
-
{t('configSelector.pleaseConfigTTS')}
-
- )}
- )} + )} - - {/* 点击外部关闭下拉菜单 */} - {isOpen && ( -
setIsOpen(false)} - /> + + {/* 点击外部关闭下拉菜单 */} + {isOpen && ( +
setIsOpen(false)} + /> + )} + )}
); diff --git a/web/src/components/ConfirmModal.tsx b/web/src/components/ConfirmModal.tsx index 5d0a0e8..9f36a43 100644 --- a/web/src/components/ConfirmModal.tsx +++ b/web/src/components/ConfirmModal.tsx @@ -96,7 +96,7 @@ const ConfirmModal: FC = ({ diff --git a/web/src/components/NotificationBanner.tsx b/web/src/components/NotificationBanner.tsx index 2a4e2b6..449cb12 100644 --- a/web/src/components/NotificationBanner.tsx +++ b/web/src/components/NotificationBanner.tsx @@ -17,17 +17,24 @@ const NotificationBanner: React.FC = ({ lang }) => { const { t } = useTranslation(lang, 'components'); - const [isVisible, setIsVisible] = useState(true); + const [isVisible, setIsVisible] = useState(false); // 初始为 false 避免水合错误 const [isClosing, setIsClosing] = useState(false); + const [isMounted, setIsMounted] = useState(false); - // 从本地存储获取通知状态,避免重复显示 + // 组件挂载后再检查 localStorage useEffect(() => { + setIsMounted(true); const hasClosed = localStorage.getItem('notificationBannerClosed'); - if (hasClosed) { - setIsVisible(false); + if (!hasClosed) { + setIsVisible(true); } }, []); + // 在挂载前不渲染任何内容,避免水合不匹配 + if (!isMounted) { + return null; + } + const handleClose = () => { setIsClosing(true); // 添加关闭动画 diff --git a/web/src/components/PodcastCreator.tsx b/web/src/components/PodcastCreator.tsx index 8204bc5..7d83195 100644 --- a/web/src/components/PodcastCreator.tsx +++ b/web/src/components/PodcastCreator.tsx @@ -128,11 +128,17 @@ const PodcastCreator: React.FC = ({ const [showLoginModal, setShowLoginModal] = useState(false); // 控制登录模态框的显示 const [showConfirmModal, setShowConfirmModal] = useState(false); // 控制确认模态框的显示 const [voices, setVoices] = useState([]); // 从 ConfigSelector 获取 voices - const [selectedPodcastVoices, setSelectedPodcastVoices] = useState<{[key: string]: Voice[]}>(() => { - // 从 localStorage 读取缓存的说话人配置 + const [selectedPodcastVoices, setSelectedPodcastVoices] = useState<{[key: string]: Voice[]}>({}); // 初始为空对象,避免水合错误 + const [isVoicesLoaded, setIsVoicesLoaded] = useState(false); + + // 组件挂载后从 localStorage 加载说话人配置 + useEffect(() => { const cachedVoices = getItem<{[key: string]: Voice[]}>('podcast-selected-voices'); - return cachedVoices || {}; - }); // 新增:单独存储选中的说话人 + if (cachedVoices) { + setSelectedPodcastVoices(cachedVoices); + } + setIsVoicesLoaded(true); + }, []); const [selectedConfig, setSelectedConfig] = useState(null); const [selectedConfigName, setSelectedConfigName] = useState(''); // 新增状态来存储配置文件的名称 const fileInputRef = useRef(null); @@ -367,158 +373,158 @@ const PodcastCreator: React.FC = ({
{/* 工具栏 */} -
- {/* 左侧配置选项 */} -
- {/* TTS配置选择 */} -
- { // 接收新的 voices 参数 +
+ {/* 隐藏的 TTS 配置选择器 */} +
+ { setSelectedConfig(config); - setSelectedConfigName(name); // 更新配置名称状态 - setVoices(newVoices); // 更新 voices 状态 + setSelectedConfigName(name); + setVoices(newVoices); }} className="w-full" - lang={lang} // 传递 lang - />
+ lang={lang} + /> +
- {/* 说话人按钮 */} -
-
+ - {/* 语言选择 */} -
- - -
+ {/* 语言选择 */} +
+ + +
- {/* 时长选择 */} -
- - -
-
+ {/* 时长选择 */} +
+ + +
- {/* 右侧操作按钮 todo */} -
- {/* 文件上传 */} - {/* - */} - - {/* 粘贴链接 */} - {/* */} - - {/* 复制 */} - {/* */} - - {/* 积分显示 */} -
- + {/* 积分显示 */} +
+ - {credits} + {credits}
+
+ {/* 右侧操作按钮 */} +
+ {/* 文件上传 */} + {/* + */} + + {/* 粘贴链接 */} + {/* */} + + {/* 复制 */} + {/* */} {/* 签到按钮 */} + -
{/* 创作按钮 */} -
-
{/* Voices Modal */} {selectedConfig && ( diff --git a/web/src/components/VoicesModal.tsx b/web/src/components/VoicesModal.tsx index dd230ef..e6de2cc 100644 --- a/web/src/components/VoicesModal.tsx +++ b/web/src/components/VoicesModal.tsx @@ -198,11 +198,16 @@ const VoicesModal: React.FC = ({ isOpen, onClose, voices, onSe audio.pause(); setPlayingVoiceId(null); } else { + // 先暂停其他正在播放的音频 if (playingVoiceId && audioRefs.current.has(playingVoiceId)) { audioRefs.current.get(playingVoiceId)?.pause(); } - audio.play(); - setPlayingVoiceId(voice.code!); + // 尝试播放音频,处理可能的失败 + audio.play().catch((error) => { + console.error('音频播放失败:', error); + setPlayingVoiceId(null); + }); + // 注意:状态会在 onPlay 事件中设置,而不是在这里 } } }} @@ -216,8 +221,18 @@ const VoicesModal: React.FC = ({ isOpen, onClose, voices, onSe else audioRefs.current.delete(voice.code!); }} src={voice.audio} + onPlay={() => setPlayingVoiceId(voice.code!)} onEnded={() => setPlayingVoiceId(null)} - onPause={() => setPlayingVoiceId(null)} + onPause={(e) => { + // 只在音频真正暂停时清除状态(不是因为切换到其他音频) + if (playingVoiceId === voice.code) { + setPlayingVoiceId(null); + } + }} + onError={() => { + console.error('音频加载失败:', voice.audio); + setPlayingVoiceId(null); + }} preload="none" className="hidden" />