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

/// 同声翻译选哪条讯飞微软会话。
///
/// 两端都在同传表里:一条 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;
}