You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
5.9 KiB
169 lines
5.9 KiB
/// 同声翻译选哪条讯飞微软会话。
|
|
///
|
|
/// 两端都在同传表里:一条 AST,发音人用短名。
|
|
/// 源或目标是中文、粤语、爱尔兰语:不启 AST,识别走微软实时转写,合成用区域码音色。
|
|
/// 两边都不在这两类里:不启动。
|
|
class SimulRoute {
|
|
const SimulRoute.ast({
|
|
required this.lang,
|
|
required this.targetLang,
|
|
required this.speaker,
|
|
}) : ast = true,
|
|
languageName = '';
|
|
|
|
const SimulRoute.splice({
|
|
required this.languageName,
|
|
required this.speaker,
|
|
}) : ast = false,
|
|
lang = '',
|
|
targetLang = '';
|
|
|
|
final bool ast;
|
|
/// AST 的源语种短码,例如 ja。
|
|
final String lang;
|
|
/// AST 的目标语种短码,例如 en。
|
|
final String targetLang;
|
|
/// AST 用短名(JennyNeural)。拼接用区域码音色(en-US-JennyNeural)。
|
|
final String speaker;
|
|
/// 拼接识别的 language_name,例如 zh-CN。
|
|
final String languageName;
|
|
}
|
|
|
|
class SimulMicrosoft {
|
|
static SimulRoute? plan(String source, String target) {
|
|
final from = _norm(source);
|
|
final to = _norm(target);
|
|
if (_ast.containsKey(from) && _ast.containsKey(to)) {
|
|
return SimulRoute.ast(
|
|
lang: from,
|
|
targetLang: to,
|
|
speaker: _ast[to]!.speaker,
|
|
);
|
|
}
|
|
if (_splice.containsKey(from) || _splice.containsKey(to)) {
|
|
final locale = _listenLocale(from);
|
|
final voice = _speakVoice(to);
|
|
if (locale == null || voice == null) return null;
|
|
return SimulRoute.splice(languageName: locale, speaker: voice);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// 同传能力 ast_msft 没授权时,用同一对语种走微软转写加合成。
|
|
/// 识别用区域码,合成用完整神经音色,不再用 AST 的短名。
|
|
static SimulRoute spliceInstead(SimulRoute route) {
|
|
final locale = _listenLocale(route.lang);
|
|
final voice = _speakVoice(route.targetLang);
|
|
if (locale == null || voice == null) return route;
|
|
return SimulRoute.splice(languageName: locale, speaker: voice);
|
|
}
|
|
|
|
/// 界面短码收成表里的键。香港繁体要在「凡是 zh- 都当普通话」之前认出来。
|
|
static String _norm(String code) {
|
|
final raw = code.trim().toLowerCase().replaceAll('_', '-');
|
|
if (raw == 'yue' || raw.startsWith('yue-')) return 'yue';
|
|
if (raw == 'ga' || raw.startsWith('ga-')) return 'ga';
|
|
if (raw == 'zh-hant-hk' || raw == 'zh-hk') return 'zh-HK';
|
|
if (raw == 'zh-hant' || raw == 'zh-tw' || raw.startsWith('zh-hant')) {
|
|
return 'zh-Hant';
|
|
}
|
|
if (raw == 'zh' || raw.startsWith('zh-')) return 'zh';
|
|
final dash = raw.indexOf('-');
|
|
return dash < 0 ? raw : raw.substring(0, dash);
|
|
}
|
|
|
|
/// 讯飞 SDK 文本翻译(gtrans)的语种码。中文用 zh-CN,不要收成 zh。
|
|
static String translateCode(String code) {
|
|
switch (_norm(code)) {
|
|
case 'zh':
|
|
return 'zh-CN';
|
|
case 'zh-Hant':
|
|
return 'zh-TW';
|
|
case 'zh-HK':
|
|
return 'zh-HK';
|
|
case 'yue':
|
|
return 'yue';
|
|
case 'ga':
|
|
return 'ga';
|
|
default:
|
|
return _norm(code);
|
|
}
|
|
}
|
|
|
|
static String? _listenLocale(String key) {
|
|
final splice = _splice[key];
|
|
if (splice != null) return splice.locale;
|
|
return _ast[key]?.locale;
|
|
}
|
|
|
|
static String? _speakVoice(String key) {
|
|
final splice = _splice[key];
|
|
if (splice != null) return splice.voice;
|
|
final ast = _ast[key];
|
|
if (ast == null) return null;
|
|
return '${ast.locale}-${ast.speaker}';
|
|
}
|
|
|
|
/// 同传表。speaker 是 AST 的短名,locale 是拼接到 OnlineTTS 时用的区域码。
|
|
static const _ast = <String, _AstLang>{
|
|
'ar': _AstLang('ar-EG', 'AbdullahNeural'),
|
|
'bg': _AstLang('bg-BG', 'BorislavNeural'),
|
|
'bn': _AstLang('bn-IN', 'BashkarNeural'),
|
|
'ca': _AstLang('ca-ES', 'AlbaNeural'),
|
|
'cs': _AstLang('cs-CZ', 'AntoninNeural'),
|
|
'da': _AstLang('da-DK', 'ChristelNeural'),
|
|
'de': _AstLang('de-DE', 'AmalaNeural'),
|
|
'el': _AstLang('el-GR', 'AthinaNeural'),
|
|
'en': _AstLang('en-US', 'JennyNeural'),
|
|
'es': _AstLang('es-ES', 'ElviraNeural'),
|
|
'et': _AstLang('et-EE', 'AnuNeural'),
|
|
'fa': _AstLang('fa-IR', 'DilaraNeural'),
|
|
'fi': _AstLang('fi-FI', 'HarriNeural'),
|
|
'fr': _AstLang('fr-FR', 'DeniseNeural'),
|
|
'hi': _AstLang('hi-IN', 'MadhurNeural'),
|
|
'hu': _AstLang('hu-HU', 'NoemiNeural'),
|
|
'id': _AstLang('id-ID', 'ArdiNeural'),
|
|
'it': _AstLang('it-IT', 'ElsaNeural'),
|
|
'ja': _AstLang('ja-JP', 'NanamiNeural'),
|
|
'ko': _AstLang('ko-KR', 'SunHiNeural'),
|
|
'lt': _AstLang('lt-LT', 'LeonasNeural'),
|
|
'lv': _AstLang('lv-LV', 'EveritaNeural'),
|
|
'ms': _AstLang('ms-MY', 'OsmanNeural'),
|
|
'nl': _AstLang('nl-NL', 'ArnaudNeural'),
|
|
'pl': _AstLang('pl-PL', 'AgnieszkaNeural'),
|
|
'pt': _AstLang('pt-BR', 'AntonioNeural'),
|
|
'ro': _AstLang('ro-RO', 'AlinaNeural'),
|
|
'ru': _AstLang('ru-RU', 'DariyaNeural'),
|
|
'sk': _AstLang('sk-SK', 'LukasNeural'),
|
|
'sl': _AstLang('sl-SI', 'PetraNeural'),
|
|
'sv': _AstLang('sv-SE', 'HilleviNeural'),
|
|
'sw': _AstLang('sw-KE', 'DaudiNeural'),
|
|
'ta': _AstLang('ta-IN', 'AnbuNeural'),
|
|
'th': _AstLang('th-TH', 'AcharaNeural'),
|
|
'tr': _AstLang('tr-TR', 'AhmetNeural'),
|
|
'uk': _AstLang('uk-UA', 'OstapNeural'),
|
|
'ur': _AstLang('ur-IN', 'AsadNeural'),
|
|
'vi': _AstLang('vi-VN', 'HoaiMyNeural'),
|
|
};
|
|
|
|
/// 微软同传表没有的三语。繁体中文按普通话识别,合成用繁体音色。
|
|
static const _splice = <String, _SpliceLang>{
|
|
'zh': _SpliceLang('zh-CN', 'zh-CN-XiaoxiaoNeural'),
|
|
'zh-Hant': _SpliceLang('zh-CN', 'zh-TW-HsiaoChenNeural'),
|
|
'yue': _SpliceLang('yue-CN', 'yue-CN-XiaoMinNeural'),
|
|
'zh-HK': _SpliceLang('zh-HK', 'zh-HK-HiuMaanNeural'),
|
|
'ga': _SpliceLang('ga-IE', 'ga-IE-OrlaNeural'),
|
|
};
|
|
}
|
|
|
|
class _AstLang {
|
|
const _AstLang(this.locale, this.speaker);
|
|
final String locale;
|
|
final String speaker;
|
|
}
|
|
|
|
class _SpliceLang {
|
|
const _SpliceLang(this.locale, this.voice);
|
|
final String locale;
|
|
final String voice;
|
|
}
|
|
|