Skip to main content

audio_cpp/
types.rs

1//! 与 `capi.cpp` JSON 输出一一对应的结构化类型。
2//!
3//! 这些类型由 serde 从 shim 返回的 JSON 反序列化而来,字段名与
4//! `capi.cpp` 的 `dump_*` 系列函数保持一致。
5
6use std::collections::BTreeMap;
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10/// 任务种类(映射 `audiocpp_model_create_task_session` 的 `task` 参数)。
11///
12/// serde 表示与 [`TaskKind::as_str`] 一致(`"sep"` 而非 `"sourceseparation"`),
13/// 与 C 边界字符串一一对应。
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum TaskKind {
16    /// 语音活动检测(VAD)
17    Vad,
18    /// 语音识别(ASR)
19    Asr,
20    /// 说话人分离(Diarization)
21    Diar,
22    /// 音频源分离(Source Separation,如 Demucs 分离人声/鼓/贝斯/其他)
23    SourceSeparation,
24    /// 音频生成(Audio Generation,如 MiniMax Music3 音乐生成)
25    AudioGeneration,
26    /// 语音合成(TTS)
27    Tts,
28    /// 语音克隆(Voice Cloning)
29    VoiceCloning,
30    /// 语音转换(Voice Conversion)
31    VoiceConversion,
32    /// 语音到语音(Speech-to-Speech)
33    SpeechToSpeech,
34    /// 强制对齐(Alignment)
35    Alignment,
36    /// 声音设计(Voice Design)
37    VoiceDesign,
38    /// 说话人识别(Speaker Recognition)
39    SpeakerRecognition,
40    /// 歌唱语音转换(SVC)
41    Svc,
42    /// MIDI 生成
43    Midi,
44    /// 轮次检测(Turn Detection,如 Smart Turn 会话是否说完;仅离线)
45    TurnDetection,
46}
47
48impl TaskKind {
49    /// 转换为传给 C 边界的字符串。
50    pub fn as_str(self) -> &'static str {
51        match self {
52            TaskKind::Vad => "vad",
53            TaskKind::Asr => "asr",
54            TaskKind::Diar => "diar",
55            TaskKind::SourceSeparation => "sep",
56            TaskKind::AudioGeneration => "gen",
57            TaskKind::Tts => "tts",
58            TaskKind::VoiceCloning => "clon",
59            TaskKind::VoiceConversion => "vc",
60            TaskKind::SpeechToSpeech => "s2s",
61            TaskKind::Alignment => "align",
62            TaskKind::VoiceDesign => "vdes",
63            TaskKind::SpeakerRecognition => "spk",
64            TaskKind::Svc => "svc",
65            TaskKind::Midi => "midi",
66            TaskKind::TurnDetection => "turn",
67        }
68    }
69}
70
71impl Serialize for TaskKind {
72    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
73        serializer.serialize_str(self.as_str())
74    }
75}
76
77impl<'de> Deserialize<'de> for TaskKind {
78    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
79        let s = String::deserialize(deserializer)?;
80        match s.as_str() {
81            "vad" => Ok(TaskKind::Vad),
82            "asr" => Ok(TaskKind::Asr),
83            "diar" => Ok(TaskKind::Diar),
84            "sep" => Ok(TaskKind::SourceSeparation),
85            "gen" => Ok(TaskKind::AudioGeneration),
86            "tts" => Ok(TaskKind::Tts),
87            "clon" => Ok(TaskKind::VoiceCloning),
88            "vc" => Ok(TaskKind::VoiceConversion),
89            "s2s" => Ok(TaskKind::SpeechToSpeech),
90            "align" => Ok(TaskKind::Alignment),
91            "vdes" => Ok(TaskKind::VoiceDesign),
92            "spk" => Ok(TaskKind::SpeakerRecognition),
93            "svc" => Ok(TaskKind::Svc),
94            "midi" => Ok(TaskKind::Midi),
95            "turn" => Ok(TaskKind::TurnDetection),
96            other => Err(serde::de::Error::unknown_variant(
97                other,
98                &[
99                    "vad", "asr", "diar", "sep", "gen", "tts", "clon", "vc", "s2s", "align",
100                    "vdes", "spk", "svc", "midi", "turn",
101                ],
102            )),
103        }
104    }
105}
106
107/// 会话运行模式(离线 / 流式)。
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
109#[serde(rename_all = "lowercase")]
110pub enum RunMode {
111    /// 离线:一次请求 → 一次结果。
112    Offline,
113    /// 流式:持续送入音频、持续产出事件。
114    Streaming,
115}
116
117impl RunMode {
118    /// 转换为传给 C 边界的字符串。
119    pub fn as_str(self) -> &'static str {
120        match self {
121            RunMode::Offline => "offline",
122            RunMode::Streaming => "streaming",
123        }
124    }
125}
126
127/// 模型族(映射 `Registry::load` 的 `family_hint` 参数)。
128///
129/// 枚举覆盖上游 `engine_runtime` 的全部 loader 族,`as_str()` 返回传给
130/// C 边界的字符串。GGUF / NeMo safetensors 无法被引擎自动探测族别,加载
131/// 时必须显式指定;用枚举代替裸字符串可避免拼写错误。
132///
133/// 上游社区持续新增模型,本枚举可能滞后:unknown 族可直接用
134/// [`ModelFamily::Custom`] 传任意字符串,`From<&str>` 也会把未收录的名字
135/// 自动映射到 `Custom`。
136#[derive(Debug, Clone, PartialEq, Eq, Hash)]
137pub enum ModelFamily {
138    // ---- VAD ----
139    /// Silero VAD(内置权重,通常可省略 family_hint)
140    SileroVad,
141    /// MarbleNet VAD(NeMo 格式,须显式指定)
142    MarblenetVad,
143    /// PulseVAD(16kHz 单声道语音活动检测,2.1K student / 81K teacher;
144    /// 仅离线,阈值选项键为 `threshold`)
145    Pulsevad,
146
147    // ---- ASR ----
148    /// Qwen3 ASR
149    Qwen3Asr,
150    /// Citrinet ASR
151    CitrinetAsr,
152    /// SenseVoice ASR(阿里,社区模型)
153    SenseAsr,
154    /// Fun ASR Nano
155    FunAsrNano,
156    /// Higgs Audio STT
157    HiggsAudioStt,
158    /// Hviske ASR
159    HviskeAsr,
160    /// Kroko ASR
161    KrokoAsr,
162    /// Nemotron ASR
163    NemotronAsr,
164    /// Nemotron 3 Diarization(NVIDIA 八说话人流式分离,社区预览;
165    /// CMake target `nemotron_3_diar`,用 `model-nemotron-3-diar` feature 编译;
166    /// task 为 `diar`,离线+原生批+流式,16kHz 输入)
167    Nemotron3Diar,
168    /// Parakeet TDT
169    ParakeetTdt,
170    /// Vibevoice ASR
171    VibevoiceAsr,
172    /// VibeVoice ASR 流式 7B(离线+流式;与 VibevoiceAsr 同属
173    /// `vibevoice_asr` CMake target,用 `model-vibevoice-asr` feature 编译)
174    VibevoiceAsrStreaming,
175    /// FireRed Audio(语音理解 / 生成,含 Qwen3.5 运行时)
176    FireredAudio,
177    /// Granite 5 ASR(IBM Granite Speech 5.0 470M TurboCTC ASR,社区模型)
178    Granite5Asr,
179    /// Audio8 ASR(社区 ASR 模型)
180    Audio8Asr,
181    /// Audio8 TTS(社区 TTS 模型)
182    Audio8Tts,
183    /// VibeASR(社区 INT8/ternary ASR)
184    Vibeasr,
185    /// Moonshine ASR(社区流式 ASR,离线+流式)
186    MoonshineAsr,
187    /// Niagara ASR(ABR Niagara 英文批处理 ASR,仅离线)
188    NiagaraAsr,
189    /// Canary ASR(NVIDIA Canary 180M Flash 多语 ASR/翻译,仅离线;
190    /// en/de/es/fr,选项 `target_language`/`pnc`)
191    CanaryAsr,
192    /// Cohere ASR(Cohere Transcribe 多语转写,仅离线;14 语种,
193    /// 选项 `pnc`/`max_tokens`)
194    CohereAsr,
195    /// MOSS-Transcribe-Diarize(Whisper 编码器 + Qwen3 解码器,
196    /// 转写+说话人分离+时间戳联合输出;离线+流式)
197    MossTranscribeDiarize,
198    /// Confucius4-R2T2(网易实时流式 ASR,社区模型;离线+流式)
199    Confucius4R2t2,
200    /// Audio Flamingo(转写/描述/问答,speech+music+环境音;task 为 `asr` 仅离线)
201    AudioFlamingo,
202    /// CrisperWhisper(Whisper 逐字/意图转写;task 为 `asr`/`align`,离线+流式)
203    Crisperwhisper,
204    /// GigaAM ASR(v3 与多语 Conformer,CTC/RNN-T 解码;task 为 `asr` 仅离线)
205    GigaamAsr,
206    /// Index-Echo-S2TT(语音到文本翻译,双语时间戳字幕;task 为 `asr` 仅离线)
207    IndexEcho,
208    /// LFM2-Audio(Liquid LFM2.5-Audio,FastConformer 编码器;task 为 `asr` 仅离线)
209    Lfm2Audio,
210    /// OWSM(ESPnet Open Whisper 式多语 ASR/翻译;task 为 `asr`,离线+流式)
211    Owsm,
212    /// OWSM-CTC(ESPnet 非自回归多语 ASR/翻译;task 为 `asr`,离线+流式)
213    OwsmCtc,
214    /// SAMSONE(紧凑音频语言模型:描述/问答/声音音乐语音理解;
215    /// task 为 `asr` 仅离线)
216    Samsone,
217
218    // ---- TTS ----
219    /// Qwen3 TTS
220    Qwen3Tts,
221    /// Confucius4 TTS
222    Confucius4Tts,
223    /// Dots TTS
224    DotsTts,
225    /// Fish Audio
226    FishAudio,
227    /// GLM TTS
228    GlmTts,
229    /// Higgs Audio TTS
230    HiggsAudioTts,
231    /// Index TTS2
232    IndexTts2,
233    /// Irodori TTS
234    IrodoriTts,
235    /// MOSS TTS Local
236    MossTtsLocal,
237    /// MOSS TTS Nano
238    MossTtsNano,
239    /// MOSS-TTS-v1.5(8B delay-pattern 零样本声音克隆 TTS,en/zh;
240    /// task 为 `tts`/`clon` 仅离线;克隆需参考音频,请求选项
241    /// `tokens`/`instruct`/`language`/`moss_tts_v15.weight_type`)
242    MossTtsV15,
243    /// MOSS VoiceGen(语音生成)
244    MossVoicegen,
245    /// Neutts
246    Neutts,
247    /// Outetts
248    Outetts,
249    /// Pocket TTS
250    PocketTts,
251    /// VieNeu TTS v3 Turbo(越南语/英语 48kHz TTS + 即时声音克隆,社区模型;
252    /// 上游族名由 `vietneu_tts` 改名而来,旧名仍可经 `From<&str>` 解析;
253    /// task 为 `tts`/`clon` 仅离线;文本输入为 SEA-G2P 音素)
254    VieneuV3Turbo,
255    /// FireRed TTS3(零样本语音合成)
256    Fireredtts3,
257    /// F5 TTS
258    F5Tts,
259    /// Magpie TTS
260    MagpieTts,
261    /// PersonaPlex(多角色对话语音)
262    Personaplex,
263    /// MiniMax H3
264    MinimaxH3,
265    /// SanoTTS(社区 TTS,en/vi/id 共 7 个声音)
266    Sanotts,
267    /// Sopro V2 Turbo TTS
268    SoproTts,
269    /// MiraTTS(社区 TTS)
270    MiraTts,
271    /// CosyVoice3 TTS
272    Cosyvoice3,
273    /// BreezeTTS
274    BreezeTts,
275    /// Soprano TTS
276    SopranoTts,
277    /// Echo TTS
278    EchoTts,
279    /// VoxCPM1 TTS
280    Voxcpm1,
281    /// Kokoro TTS(Kokoro 82M 多语 TTS,仅离线)
282    KokoroTts,
283    /// ZipVoice(k2-fsa 社区流匹配 TTS,含 Distill 变体;en/zh;
284    /// task 为 `tts`/`clon` 仅离线;零样本克隆需 `reference_text` 选项)
285    Zipvoice,
286    /// AuK(腾讯 AuK / AuK-Flash 语音生成与编辑;
287    /// task 为 `tts`/`edit` 仅离线)
288    Auk,
289    /// Piper TTS(社区 VITS TTS,首包英文 `en_US-lessac-medium`,22.05kHz mono;
290    /// task 为 `tts` 仅离线;请求选项 `speed`/`variation`/`duration_variation`/`seed`)
291    PiperTts,
292    /// KittenTTS Mini 0.8(社区英文 TTS,80M 参数,8 个内置声音,24kHz mono;
293    /// task 为 `tts` 仅离线;请求选项 `speed`/`seed`,`--voice-id` 选声音)
294    KittenTts,
295    /// Kitten TTS 2(原生多语 Qwen3 语音生成 + S3 meanflow 解码 + 参考音频克隆;
296    /// task 为 `tts`/`clon` 仅离线)
297    KittenTts2,
298    /// KugelAudio-0-Open(预置音色 TTS,增量音频输出;task 为 `tts`,离线+流式)
299    Kugelaudio,
300    /// Maya1(3B 表现力英文 TTS,自然语言音色设计 + 行内情感控制;
301    /// task 为 `tts` 仅离线)
302    Maya1,
303    /// MOSS-TTSD(8B delay-pattern 对话 TTS:单遍多说话人 + 逐人零样本克隆,
304    /// 24kHz 16 codebook;task 为 `tts`/`clon` 仅离线)
305    MossTtsd,
306
307    // ---- 说话人分离 / 语音转换 / 音乐生成 ----
308    /// SortFormer 说话人分离(Diar)
309    SortformerDiar,
310    /// SortFormer v2.1 说话人分离(离线+流式,社区模型;
311    /// CMake target `sortformer_diar_v2`,用 `model-sortformer-diar-v2` feature 编译)
312    SortformerDiarV2,
313    /// Seed VC(语音转换)
314    SeedVc,
315    /// RVC(语音转换)
316    Rvc,
317    /// MeanVC2(语音转换)
318    Meanvc2,
319    /// Tone Color VC(独立语音转换,需参考说话人录音;task 为 `vc` 仅离线)
320    ToneColorVc,
321    /// Chatterbox(说话人还原 / TTS)
322    Chatterbox,
323    /// Chatterbox Turbo TTS(快速推理变体)
324    ChatterboxTurbo,
325    /// Vevo2(零样本 TTS / VC / 语音编辑)
326    Vevo2,
327    /// VoxCPM2
328    Voxcpm2,
329    /// Complex Inversion(多任务音频)
330    AceStep,
331
332    // ---- 音乐 / 音频生成与分离 ----
333    /// HTDemucs 音乐源分离
334    Htdemucs,
335    /// HTDemucs 6-stem(鼓/贝斯/人声/其他/吉他/钢琴六轨;task 为 `sep` 仅离线;
336    /// 与 `htdemucs` 同属 `demucs` CMake target,用 `model-demucs` feature 编译)
337    Htdemucs6Stems,
338    /// Mel Band Roformer(乐音建模)
339    MelBandRoformer,
340    /// BSRoformer
341    BsRoformer,
342    /// Muscriptor(乐理描述)
343    Muscriptor,
344    /// Omnivoice(条件自回归生成)
345    Omnivoice,
346    /// Stable Audio(音频生成)
347    StableAudio,
348    /// MiniMax Music3(音乐生成)
349    MinimaxMusic3,
350    /// Supertonic(音乐生成)
351    Supertonic,
352    /// YuE2(音乐生成:符号 ABC 规划 + 语义 codec + NAR 声学流合成;
353    /// 仅离线,task 为 `gen`;请求为纯文本(歌词)+ `style` 选项,不消费音频)
354    Yue2,
355    /// SheetSage2(音频转符号谱:输入录音,输出 ABC 乐谱产物;
356    /// 仅离线,task 为 `midi`;结果在 `artifact_output` / `output_artifacts`)
357    Sheetsage2,
358    /// Voxtral Realtime
359    VoxtralRealtime,
360    /// AudioSR(语音/音频超分辨率)
361    Audiosr,
362    /// Apollo(44.1kHz 压缩音频音乐修复;task 为 `s2s` 仅离线)
363    Apollo,
364    /// UniverSR(复数 STFT 流匹配音频超分至 48kHz;task 为 `s2s` 仅离线;
365    /// 选项 `input_sample_rate`/`sampler_mode`/`num_inference_steps`/
366    /// `guidance_scale`/`seed`)
367    Universr,
368    /// 内置音频工具(降噪/增强/超分:deepfilternet2、rnnoise、zipenhancer、
369    /// gtcrn 系列、flashsr;task 为 `s2s` 仅离线;加载时 `model_path`
370    /// 传权重文件或其所在目录(权重需另行下载,非内嵌),另须经 `load_options`
371    /// 传 `{"utility":"<工具 ID>"}`(如 `"rnnoise"`)指定实现)
372    BuiltinAudioUtils,
373    /// ControlFoley(可控电影音效生成)
374    ControlFoley,
375    /// MidoAudio / MIdashengLM 生成式音频
376    MidashEnglmGen,
377    /// LiveAvatar(Wan S2V 音频驱动数字人;task 为 `sfx`(即 `gen`),
378    /// 离线+流式)
379    Liveavatar,
380    /// RE-USE(采样率无关语音修复,双向 Mamba;task 为 `s2s` 仅离线)
381    Reuse,
382    /// SAM Audio(prompt 条件音频分离;task 为 `s2s` 仅离线)
383    SamAudio,
384    /// Sidon v0.1(单说话人语音修复,48kHz 输出;task 为 `s2s` 仅离线)
385    Sidon,
386    /// Smart Turn(音频原生轮次完成检测,Whisper-Tiny 编码器;
387    /// task 为 `turn` 仅离线,16kHz mono,选项 `threshold`,结果在
388    /// `custom_schema_output`(schema `smart_turn.v1`))
389    SmartTurn,
390
391    // ---- 其他 ----
392    /// Miocodec / Miotts(小米语音)
393    Miocodec,
394    /// Miotts(小米 TTS)
395    Miotts,
396    /// Vibevoice(语音转换)
397    Vibevoice,
398    /// Dramabox(剧本播客)
399    Dramabox,
400    /// Heartmula
401    Heartmula,
402    /// Inflect V2
403    InflectV2,
404    /// Qwen3 Forced Aligner(强制对齐)
405    Qwen3ForcedAligner,
406    /// MMS Forced Aligner(强制对齐,社区模型)
407    MmsForcedAligner,
408
409    /// 未收录的模型族名(原样传给 C 边界)。
410    Custom(String),
411}
412
413impl ModelFamily {
414    /// 按文件名关键词推断模型族。
415    ///
416    /// GGUF / NeMo safetensors 无法被引擎自动探测族别,加载时必须显式指定。
417    /// 上游社区习惯以模型族名命名权重文件(如 `qwen3-asr-0.6b-q8_0.gguf`、
418    /// `sortformer-diar-4spk-v1-q8_0.gguf`),本函数从文件名中的关键词
419    /// 匹配到对应 [`ModelFamily`];无法识别时返回 `None`,调用方应回退到
420    /// 显式 hint 或 [`ModelFamily::Custom`]。
421    ///
422    /// 匹配规则按关键词**首次命中**,文件名需包含明确的族名(如 `qwen3`、
423    /// `citrinet`、`moss-tts-nano`)。`#family=` 片段(`path#family=qwen3_asr`)
424    /// 会被剥离后参与匹配,同时优先于关键词命中。
425    ///
426    /// # 示例
427    ///
428    /// ```
429    /// use audio_cpp::ModelFamily;
430    ///
431    /// assert_eq!(
432    ///     ModelFamily::from_path("models/qwen3-asr-0.6b-q8_0.gguf"),
433    ///     Some(ModelFamily::Qwen3Asr),
434    /// );
435    /// assert_eq!(
436    ///     ModelFamily::from_path("model.gguf#family=citrinet_asr"),
437    ///     Some(ModelFamily::CitrinetAsr),
438    /// );
439    /// assert_eq!(ModelFamily::from_path("my-weights.gguf"), None);
440    /// ```
441    pub fn from_path(path: &str) -> Option<ModelFamily> {
442        let lower = path.to_ascii_lowercase();
443        // `#family=xxx` 片段:显式指定优先,直接映射后返回。
444        if let Some((_, f)) = lower.rsplit_once("#family=") {
445            return Some(ModelFamily::from(f.trim()));
446        }
447        // 关键词 → 族:按文件名常用命名约定匹配。
448        const KEYWORDS: &[(&str, ModelFamily)] = &[
449            ("silero_vad", ModelFamily::SileroVad),
450            ("silero-vad", ModelFamily::SileroVad),
451            ("marblenet_vad", ModelFamily::MarblenetVad),
452            ("marblenet-vad", ModelFamily::MarblenetVad),
453            ("marblenet", ModelFamily::MarblenetVad),
454            ("pulsevad", ModelFamily::Pulsevad),
455            ("pulse-vad", ModelFamily::Pulsevad),
456            ("pulse", ModelFamily::Pulsevad),
457            ("qwen3_asr", ModelFamily::Qwen3Asr),
458            ("qwen3-asr", ModelFamily::Qwen3Asr),
459            ("qwen3_tts", ModelFamily::Qwen3Tts),
460            ("qwen3-tts", ModelFamily::Qwen3Tts),
461            ("qwen3_forced_aligner", ModelFamily::Qwen3ForcedAligner),
462            ("qwen3-forced-aligner", ModelFamily::Qwen3ForcedAligner),
463            ("qwen3", ModelFamily::Qwen3Asr),
464            ("citrinet", ModelFamily::CitrinetAsr),
465            ("sense_asr", ModelFamily::SenseAsr),
466            ("sense-asr", ModelFamily::SenseAsr),
467            ("sensevoice", ModelFamily::SenseAsr),
468            ("fun-asr-nano", ModelFamily::FunAsrNano),
469            ("fun_asr_nano", ModelFamily::FunAsrNano),
470            ("funasr", ModelFamily::FunAsrNano),
471            ("higgs_audio_stt", ModelFamily::HiggsAudioStt),
472            ("higgs_audio_tts", ModelFamily::HiggsAudioTts),
473            ("higgs", ModelFamily::HiggsAudioStt),
474            ("hviske", ModelFamily::HviskeAsr),
475            ("kroko", ModelFamily::KrokoAsr),
476            ("nemotron_3_diar", ModelFamily::Nemotron3Diar),
477            ("nemotron-3-diar", ModelFamily::Nemotron3Diar),
478            ("nemotron_3", ModelFamily::Nemotron3Diar),
479            ("nemotron-3", ModelFamily::Nemotron3Diar),
480            ("nemotron", ModelFamily::NemotronAsr),
481            ("parakeet", ModelFamily::ParakeetTdt),
482            (
483                "vibevoice_asr_streaming",
484                ModelFamily::VibevoiceAsrStreaming,
485            ),
486            (
487                "vibevoice-asr-streaming",
488                ModelFamily::VibevoiceAsrStreaming,
489            ),
490            ("vibevoice_asr", ModelFamily::VibevoiceAsr),
491            ("firered_audio", ModelFamily::FireredAudio),
492            ("firered-audio", ModelFamily::FireredAudio),
493            ("granite5asr", ModelFamily::Granite5Asr),
494            ("granite-speech", ModelFamily::Granite5Asr),
495            ("granite_speech", ModelFamily::Granite5Asr),
496            ("granite", ModelFamily::Granite5Asr),
497            ("audio8_asr", ModelFamily::Audio8Asr),
498            ("audio8-asr", ModelFamily::Audio8Asr),
499            ("audio8asr", ModelFamily::Audio8Asr),
500            ("arkasr", ModelFamily::Audio8Asr),
501            ("audio8_tts", ModelFamily::Audio8Tts),
502            ("audio8-tts", ModelFamily::Audio8Tts),
503            ("audio8tts", ModelFamily::Audio8Tts),
504            ("fireredtts3", ModelFamily::Fireredtts3),
505            ("firered_tts3", ModelFamily::Fireredtts3),
506            ("firered-tts3", ModelFamily::Fireredtts3),
507            ("vibevoice", ModelFamily::Vibevoice),
508            // confucius4_r2t2 必须排在裸 "confucius" 之前,否则会被吞掉。
509            ("confucius4_r2t2", ModelFamily::Confucius4R2t2),
510            ("confucius4-r2t2", ModelFamily::Confucius4R2t2),
511            ("r2t2", ModelFamily::Confucius4R2t2),
512            ("confucius", ModelFamily::Confucius4Tts),
513            ("dots_tts", ModelFamily::DotsTts),
514            ("dots-tts", ModelFamily::DotsTts),
515            ("fish_audio", ModelFamily::FishAudio),
516            ("fish-audio", ModelFamily::FishAudio),
517            ("glm_tts", ModelFamily::GlmTts),
518            ("glm-tts", ModelFamily::GlmTts),
519            ("index_tts2", ModelFamily::IndexTts2),
520            ("index-tts2", ModelFamily::IndexTts2),
521            ("irodori", ModelFamily::IrodoriTts),
522            // moss_transcribe_diarize 必须排在裸 "moss" 之前,否则会被吞掉。
523            (
524                "moss_transcribe_diarize",
525                ModelFamily::MossTranscribeDiarize,
526            ),
527            (
528                "moss-transcribe-diarize",
529                ModelFamily::MossTranscribeDiarize,
530            ),
531            ("moss-tts-nano", ModelFamily::MossTtsNano),
532            ("moss_tts_nano", ModelFamily::MossTtsNano),
533            ("moss-tts-local", ModelFamily::MossTtsLocal),
534            ("moss_tts_local", ModelFamily::MossTtsLocal),
535            // moss_tts_v15 / moss_ttsd 必须排在裸 "moss" 之前,否则会被吞掉。
536            ("moss-tts-v15", ModelFamily::MossTtsV15),
537            ("moss_tts_v15", ModelFamily::MossTtsV15),
538            ("moss_ttsd", ModelFamily::MossTtsd),
539            ("moss-ttsd", ModelFamily::MossTtsd),
540            ("mossttsd", ModelFamily::MossTtsd),
541            ("moss_voicegen", ModelFamily::MossVoicegen),
542            ("moss-voicegen", ModelFamily::MossVoicegen),
543            ("moss", ModelFamily::MossTtsNano),
544            ("neutts", ModelFamily::Neutts),
545            ("outetts", ModelFamily::Outetts),
546            ("pocket_tts", ModelFamily::PocketTts),
547            ("pocket-tts", ModelFamily::PocketTts),
548            ("vietneu", ModelFamily::VieneuV3Turbo),
549            ("vieneu", ModelFamily::VieneuV3Turbo),
550            ("f5_tts", ModelFamily::F5Tts),
551            ("f5-tts", ModelFamily::F5Tts),
552            ("magpie", ModelFamily::MagpieTts),
553            ("personaplex", ModelFamily::Personaplex),
554            ("soprano_tts", ModelFamily::SopranoTts),
555            ("soprano-tts", ModelFamily::SopranoTts),
556            ("soprano", ModelFamily::SopranoTts),
557            // index_echo 必须排在裸 "echo" 之前,否则会被吞掉。
558            ("index_echo", ModelFamily::IndexEcho),
559            ("index-echo", ModelFamily::IndexEcho),
560            ("indexecho", ModelFamily::IndexEcho),
561            ("echo_tts", ModelFamily::EchoTts),
562            ("echo-tts", ModelFamily::EchoTts),
563            ("echo", ModelFamily::EchoTts),
564            ("kokoro_tts", ModelFamily::KokoroTts),
565            ("kokoro-tts", ModelFamily::KokoroTts),
566            ("kokoro", ModelFamily::KokoroTts),
567            ("piper_tts", ModelFamily::PiperTts),
568            ("piper-tts", ModelFamily::PiperTts),
569            ("piper", ModelFamily::PiperTts),
570            // kitten_tts2 必须排在 kitten_tts / 裸 "kitten" 之前,否则会被吞掉。
571            ("kitten_tts2", ModelFamily::KittenTts2),
572            ("kitten-tts2", ModelFamily::KittenTts2),
573            ("kitten_tts_2", ModelFamily::KittenTts2),
574            ("kittentts2", ModelFamily::KittenTts2),
575            ("kitten_tts", ModelFamily::KittenTts),
576            ("kitten-tts", ModelFamily::KittenTts),
577            ("kitten", ModelFamily::KittenTts),
578            ("kugelaudio", ModelFamily::Kugelaudio),
579            ("kugel-audio", ModelFamily::Kugelaudio),
580            ("kugel_audio", ModelFamily::Kugelaudio),
581            ("kugel", ModelFamily::Kugelaudio),
582            ("maya1", ModelFamily::Maya1),
583            ("maya-1", ModelFamily::Maya1),
584            ("maya_1", ModelFamily::Maya1),
585            ("maya", ModelFamily::Maya1),
586            ("voxcpm1", ModelFamily::Voxcpm1),
587            ("voxcpm-1", ModelFamily::Voxcpm1),
588            ("sanotts", ModelFamily::Sanotts),
589            ("sano-tts", ModelFamily::Sanotts),
590            ("sano_tts", ModelFamily::Sanotts),
591            ("sopro_tts", ModelFamily::SoproTts),
592            ("sopro-tts", ModelFamily::SoproTts),
593            ("sopro", ModelFamily::SoproTts),
594            ("mira_tts", ModelFamily::MiraTts),
595            ("mira-tts", ModelFamily::MiraTts),
596            ("miratts", ModelFamily::MiraTts),
597            ("mira", ModelFamily::MiraTts),
598            ("cosyvoice3", ModelFamily::Cosyvoice3),
599            ("cosyvoice-3", ModelFamily::Cosyvoice3),
600            ("cosyvoice", ModelFamily::Cosyvoice3),
601            ("breeze_tts", ModelFamily::BreezeTts),
602            ("breeze-tts", ModelFamily::BreezeTts),
603            ("breeze", ModelFamily::BreezeTts),
604            ("vibeasr", ModelFamily::Vibeasr),
605            ("vibe-asr", ModelFamily::Vibeasr),
606            ("moonshine_asr", ModelFamily::MoonshineAsr),
607            ("moonshine-asr", ModelFamily::MoonshineAsr),
608            ("moonshine", ModelFamily::MoonshineAsr),
609            ("niagara_asr", ModelFamily::NiagaraAsr),
610            ("niagara-asr", ModelFamily::NiagaraAsr),
611            ("niagara", ModelFamily::NiagaraAsr),
612            ("canary_asr", ModelFamily::CanaryAsr),
613            ("canary-asr", ModelFamily::CanaryAsr),
614            ("canary", ModelFamily::CanaryAsr),
615            ("cohere_asr", ModelFamily::CohereAsr),
616            ("cohere-asr", ModelFamily::CohereAsr),
617            ("cohere", ModelFamily::CohereAsr),
618            ("audio_flamingo", ModelFamily::AudioFlamingo),
619            ("audio-flamingo", ModelFamily::AudioFlamingo),
620            ("audioflamingo", ModelFamily::AudioFlamingo),
621            ("crisperwhisper", ModelFamily::Crisperwhisper),
622            ("crisper-whisper", ModelFamily::Crisperwhisper),
623            ("crisper_whisper", ModelFamily::Crisperwhisper),
624            ("gigaam_asr", ModelFamily::GigaamAsr),
625            ("gigaam-asr", ModelFamily::GigaamAsr),
626            ("gigaam", ModelFamily::GigaamAsr),
627            ("lfm2_audio", ModelFamily::Lfm2Audio),
628            ("lfm2-audio", ModelFamily::Lfm2Audio),
629            ("lfm2", ModelFamily::Lfm2Audio),
630            // owsm_ctc 必须排在裸 "owsm" 之前,否则会被吞掉。
631            ("owsm_ctc", ModelFamily::OwsmCtc),
632            ("owsm-ctc", ModelFamily::OwsmCtc),
633            ("owsmctc", ModelFamily::OwsmCtc),
634            ("owsm", ModelFamily::Owsm),
635            ("samsone", ModelFamily::Samsone),
636            ("sam-sone", ModelFamily::Samsone),
637            ("zipvoice", ModelFamily::Zipvoice),
638            ("zip-voice", ModelFamily::Zipvoice),
639            ("liveavatar", ModelFamily::Liveavatar),
640            ("live-avatar", ModelFamily::Liveavatar),
641            ("reuse", ModelFamily::Reuse),
642            ("re-use", ModelFamily::Reuse),
643            ("sam_audio", ModelFamily::SamAudio),
644            ("sam-audio", ModelFamily::SamAudio),
645            ("samaudio", ModelFamily::SamAudio),
646            ("sidon", ModelFamily::Sidon),
647            ("smart_turn", ModelFamily::SmartTurn),
648            ("smart-turn", ModelFamily::SmartTurn),
649            ("smartturn", ModelFamily::SmartTurn),
650            ("auk", ModelFamily::Auk),
651            ("minimax_h3", ModelFamily::MinimaxH3),
652            ("minimax-h3", ModelFamily::MinimaxH3),
653            ("sortformer_diar_v2", ModelFamily::SortformerDiarV2),
654            ("sortformer-diar-v2", ModelFamily::SortformerDiarV2),
655            ("sortformer_diar", ModelFamily::SortformerDiar),
656            ("sortformer-diar", ModelFamily::SortformerDiar),
657            ("sortformer", ModelFamily::SortformerDiar),
658            ("seed_vc", ModelFamily::SeedVc),
659            ("seed-vc", ModelFamily::SeedVc),
660            ("seedvc", ModelFamily::SeedVc),
661            ("rvc", ModelFamily::Rvc),
662            ("meanvc2", ModelFamily::Meanvc2),
663            ("mean-vc2", ModelFamily::Meanvc2),
664            ("tone_color_vc", ModelFamily::ToneColorVc),
665            ("tone-color-vc", ModelFamily::ToneColorVc),
666            ("tone_color", ModelFamily::ToneColorVc),
667            ("tonecolor", ModelFamily::ToneColorVc),
668            ("chatterbox_turbo", ModelFamily::ChatterboxTurbo),
669            ("chatterbox-turbo", ModelFamily::ChatterboxTurbo),
670            ("chatterbox", ModelFamily::Chatterbox),
671            ("vevo2", ModelFamily::Vevo2),
672            ("voxcpm2", ModelFamily::Voxcpm2),
673            ("ace_step", ModelFamily::AceStep),
674            ("ace-step", ModelFamily::AceStep),
675            // htdemucs_6stems(含别名 6s)必须排在裸 "htdemucs" 之前,否则会被吞掉。
676            ("htdemucs_6stems", ModelFamily::Htdemucs6Stems),
677            ("htdemucs-6stems", ModelFamily::Htdemucs6Stems),
678            ("htdemucs_6s", ModelFamily::Htdemucs6Stems),
679            ("htdemucs-6s", ModelFamily::Htdemucs6Stems),
680            ("htdemucs", ModelFamily::Htdemucs),
681            ("demucs", ModelFamily::Htdemucs),
682            ("mel-band-roformer", ModelFamily::MelBandRoformer),
683            ("mel_band_roformer", ModelFamily::MelBandRoformer),
684            ("bs-roformer", ModelFamily::BsRoformer),
685            ("bs_roformer", ModelFamily::BsRoformer),
686            ("muscriptor", ModelFamily::Muscriptor),
687            ("omnivoice", ModelFamily::Omnivoice),
688            ("stable_audio", ModelFamily::StableAudio),
689            ("stable-audio", ModelFamily::StableAudio),
690            ("minimax_music3", ModelFamily::MinimaxMusic3),
691            ("minimax-music3", ModelFamily::MinimaxMusic3),
692            ("supertonic", ModelFamily::Supertonic),
693            ("yue2", ModelFamily::Yue2),
694            ("yue-2", ModelFamily::Yue2),
695            ("sheetsage2", ModelFamily::Sheetsage2),
696            ("sheetsage-2", ModelFamily::Sheetsage2),
697            ("sheetsage", ModelFamily::Sheetsage2),
698            ("voxtral-realtime", ModelFamily::VoxtralRealtime),
699            ("voxtral", ModelFamily::VoxtralRealtime),
700            ("audiosr", ModelFamily::Audiosr),
701            ("audio-sr", ModelFamily::Audiosr),
702            ("apollo", ModelFamily::Apollo),
703            ("universr", ModelFamily::Universr),
704            ("univer-sr", ModelFamily::Universr),
705            ("controlfoley", ModelFamily::ControlFoley),
706            ("control-foley", ModelFamily::ControlFoley),
707            ("midashenglm_gen", ModelFamily::MidashEnglmGen),
708            ("midashenglm-gen", ModelFamily::MidashEnglmGen),
709            ("midashen", ModelFamily::MidashEnglmGen),
710            ("builtin_audio_utils", ModelFamily::BuiltinAudioUtils),
711            ("builtin-audio-utils", ModelFamily::BuiltinAudioUtils),
712            ("deepfilternet", ModelFamily::BuiltinAudioUtils),
713            ("rnnoise", ModelFamily::BuiltinAudioUtils),
714            ("zipenhancer", ModelFamily::BuiltinAudioUtils),
715            ("gtcrn", ModelFamily::BuiltinAudioUtils),
716            ("flashsr", ModelFamily::BuiltinAudioUtils),
717            ("miocodec", ModelFamily::Miocodec),
718            ("miotts", ModelFamily::Miotts),
719            ("dramabox", ModelFamily::Dramabox),
720            ("heartmula", ModelFamily::Heartmula),
721            ("inflect", ModelFamily::InflectV2),
722            ("mms_forced_aligner", ModelFamily::MmsForcedAligner),
723            ("mms", ModelFamily::MmsForcedAligner),
724            ("forced-aligner", ModelFamily::Qwen3ForcedAligner),
725        ];
726        KEYWORDS
727            .iter()
728            .find(|(k, _)| lower.contains(k))
729            .map(|(_, family)| family.clone())
730    }
731
732    /// 转换为传给 C 边界的字符串。
733    pub fn as_str(&self) -> &str {
734        match self {
735            ModelFamily::SileroVad => "silero_vad",
736            ModelFamily::MarblenetVad => "marblenet_vad",
737            ModelFamily::Pulsevad => "pulsevad",
738            ModelFamily::Qwen3Asr => "qwen3_asr",
739            ModelFamily::CitrinetAsr => "citrinet_asr",
740            ModelFamily::SenseAsr => "sense_asr",
741            ModelFamily::FunAsrNano => "fun_asr_nano",
742            ModelFamily::HiggsAudioStt => "higgs_audio_stt",
743            ModelFamily::HviskeAsr => "hviske_asr",
744            ModelFamily::KrokoAsr => "kroko_asr",
745            ModelFamily::NemotronAsr => "nemotron_asr",
746            ModelFamily::Nemotron3Diar => "nemotron_3_diar",
747            ModelFamily::ParakeetTdt => "parakeet_tdt",
748            ModelFamily::VibevoiceAsr => "vibevoice_asr",
749            ModelFamily::VibevoiceAsrStreaming => "vibevoice_asr_streaming",
750            ModelFamily::FireredAudio => "firered_audio",
751            ModelFamily::Granite5Asr => "granite5asr",
752            ModelFamily::Audio8Asr => "audio8_asr",
753            ModelFamily::Audio8Tts => "audio8_tts",
754            ModelFamily::Vibeasr => "vibeasr",
755            ModelFamily::MoonshineAsr => "moonshine_asr",
756            ModelFamily::NiagaraAsr => "niagara_asr",
757            ModelFamily::CanaryAsr => "canary_asr",
758            ModelFamily::CohereAsr => "cohere_asr",
759            ModelFamily::MossTranscribeDiarize => "moss_transcribe_diarize",
760            ModelFamily::Confucius4R2t2 => "confucius4_r2t2",
761            ModelFamily::AudioFlamingo => "audio_flamingo",
762            ModelFamily::Crisperwhisper => "crisperwhisper",
763            ModelFamily::GigaamAsr => "gigaam_asr",
764            ModelFamily::IndexEcho => "index_echo",
765            ModelFamily::Lfm2Audio => "lfm2_audio",
766            ModelFamily::Owsm => "owsm",
767            ModelFamily::OwsmCtc => "owsm_ctc",
768            ModelFamily::Samsone => "samsone",
769            ModelFamily::Qwen3Tts => "qwen3_tts",
770            ModelFamily::Confucius4Tts => "confucius4_tts",
771            ModelFamily::DotsTts => "dots_tts",
772            ModelFamily::FishAudio => "fish_audio",
773            ModelFamily::GlmTts => "glm_tts",
774            ModelFamily::HiggsAudioTts => "higgs_audio_tts",
775            ModelFamily::IndexTts2 => "index_tts2",
776            ModelFamily::IrodoriTts => "irodori_tts",
777            ModelFamily::MossTtsLocal => "moss_tts_local",
778            ModelFamily::MossTtsNano => "moss_tts_nano",
779            ModelFamily::MossTtsV15 => "moss_tts_v15",
780            ModelFamily::MossVoicegen => "moss_voicegen",
781            ModelFamily::Neutts => "neutts",
782            ModelFamily::Outetts => "outetts",
783            ModelFamily::PocketTts => "pocket_tts",
784            ModelFamily::VieneuV3Turbo => "vieneu_v3_turbo",
785            ModelFamily::Fireredtts3 => "fireredtts3",
786            ModelFamily::F5Tts => "f5_tts",
787            ModelFamily::MagpieTts => "magpie_tts",
788            ModelFamily::Personaplex => "personaplex",
789            ModelFamily::SopranoTts => "soprano_tts",
790            ModelFamily::EchoTts => "echo_tts",
791            ModelFamily::Voxcpm1 => "voxcpm1",
792            ModelFamily::KokoroTts => "kokoro_tts",
793            ModelFamily::Zipvoice => "zipvoice",
794            ModelFamily::Auk => "auk",
795            ModelFamily::PiperTts => "piper_tts",
796            ModelFamily::KittenTts => "kitten_tts",
797            ModelFamily::KittenTts2 => "kitten_tts2",
798            ModelFamily::Kugelaudio => "kugelaudio",
799            ModelFamily::Maya1 => "maya1",
800            ModelFamily::MossTtsd => "moss_ttsd",
801            ModelFamily::MinimaxH3 => "minimax_h3",
802            ModelFamily::Sanotts => "sanotts",
803            ModelFamily::SoproTts => "sopro_tts",
804            ModelFamily::MiraTts => "mira_tts",
805            ModelFamily::Cosyvoice3 => "cosyvoice3",
806            ModelFamily::BreezeTts => "breeze_tts",
807            ModelFamily::SortformerDiar => "sortformer_diar",
808            ModelFamily::SortformerDiarV2 => "sortformer_diar_v2",
809            ModelFamily::SeedVc => "seed_vc",
810            ModelFamily::Rvc => "rvc",
811            ModelFamily::Meanvc2 => "meanvc2",
812            ModelFamily::ToneColorVc => "tone_color_vc",
813            ModelFamily::Chatterbox => "chatterbox",
814            ModelFamily::ChatterboxTurbo => "chatterbox_turbo",
815            ModelFamily::Vevo2 => "vevo2",
816            ModelFamily::Voxcpm2 => "voxcpm2",
817            ModelFamily::AceStep => "ace_step",
818            ModelFamily::Htdemucs => "htdemucs",
819            ModelFamily::Htdemucs6Stems => "htdemucs_6stems",
820            ModelFamily::MelBandRoformer => "mel_band_roformer",
821            ModelFamily::BsRoformer => "bs_roformer",
822            ModelFamily::Muscriptor => "muscriptor",
823            ModelFamily::Omnivoice => "omnivoice",
824            ModelFamily::StableAudio => "stable_audio",
825            ModelFamily::MinimaxMusic3 => "minimax_music3",
826            ModelFamily::Supertonic => "supertonic",
827            ModelFamily::Yue2 => "yue2",
828            ModelFamily::Sheetsage2 => "sheetsage2",
829            ModelFamily::VoxtralRealtime => "voxtral_realtime",
830            ModelFamily::Audiosr => "audiosr",
831            ModelFamily::Apollo => "apollo",
832            ModelFamily::Universr => "universr",
833            ModelFamily::BuiltinAudioUtils => "builtin_audio_utils",
834            ModelFamily::ControlFoley => "controlfoley",
835            ModelFamily::MidashEnglmGen => "midashenglm_gen",
836            ModelFamily::Liveavatar => "liveavatar",
837            ModelFamily::Reuse => "reuse",
838            ModelFamily::SamAudio => "sam_audio",
839            ModelFamily::Sidon => "sidon",
840            ModelFamily::SmartTurn => "smart_turn",
841            ModelFamily::Miocodec => "miocodec",
842            ModelFamily::Miotts => "miotts",
843            ModelFamily::Vibevoice => "vibevoice",
844            ModelFamily::Dramabox => "dramabox",
845            ModelFamily::Heartmula => "heartmula",
846            ModelFamily::InflectV2 => "inflect_v2",
847            ModelFamily::Qwen3ForcedAligner => "qwen3_forced_aligner",
848            ModelFamily::MmsForcedAligner => "mms_forced_aligner",
849            ModelFamily::Custom(s) => s,
850        }
851    }
852}
853
854impl From<&str> for ModelFamily {
855    /// 从字符串构造:收录的名字映射到对应变体,未收录的落入 `Custom`。
856    fn from(s: &str) -> Self {
857        match s {
858            "silero_vad" => ModelFamily::SileroVad,
859            "marblenet_vad" => ModelFamily::MarblenetVad,
860            "pulsevad" => ModelFamily::Pulsevad,
861            "qwen3_asr" => ModelFamily::Qwen3Asr,
862            "citrinet_asr" => ModelFamily::CitrinetAsr,
863            "sense_asr" => ModelFamily::SenseAsr,
864            "fun_asr_nano" => ModelFamily::FunAsrNano,
865            "higgs_audio_stt" => ModelFamily::HiggsAudioStt,
866            "hviske_asr" => ModelFamily::HviskeAsr,
867            "kroko_asr" => ModelFamily::KrokoAsr,
868            "nemotron_asr" => ModelFamily::NemotronAsr,
869            "nemotron_3_diar" => ModelFamily::Nemotron3Diar,
870            "parakeet_tdt" => ModelFamily::ParakeetTdt,
871            "vibevoice_asr" => ModelFamily::VibevoiceAsr,
872            "vibevoice_asr_streaming" => ModelFamily::VibevoiceAsrStreaming,
873            "firered_audio" => ModelFamily::FireredAudio,
874            "granite5asr" => ModelFamily::Granite5Asr,
875            "granite_speech5_asr" => ModelFamily::Granite5Asr,
876            "granite_speech" => ModelFamily::Granite5Asr,
877            "granite_speech5_ctc" => ModelFamily::Granite5Asr,
878            "audio8_asr" => ModelFamily::Audio8Asr,
879            "arkasr" => ModelFamily::Audio8Asr,
880            "audio8_tts" => ModelFamily::Audio8Tts,
881            "vibeasr" => ModelFamily::Vibeasr,
882            "moonshine_asr" => ModelFamily::MoonshineAsr,
883            "niagara_asr" => ModelFamily::NiagaraAsr,
884            "canary_asr" => ModelFamily::CanaryAsr,
885            "cohere_asr" => ModelFamily::CohereAsr,
886            "moss_transcribe_diarize" => ModelFamily::MossTranscribeDiarize,
887            "confucius4_r2t2" => ModelFamily::Confucius4R2t2,
888            "audio_flamingo" => ModelFamily::AudioFlamingo,
889            "crisperwhisper" => ModelFamily::Crisperwhisper,
890            "gigaam_asr" => ModelFamily::GigaamAsr,
891            "index_echo" => ModelFamily::IndexEcho,
892            "lfm2_audio" => ModelFamily::Lfm2Audio,
893            "owsm" => ModelFamily::Owsm,
894            "owsm_ctc" => ModelFamily::OwsmCtc,
895            "samsone" => ModelFamily::Samsone,
896            "qwen3_tts" => ModelFamily::Qwen3Tts,
897            "confucius4_tts" => ModelFamily::Confucius4Tts,
898            "dots_tts" => ModelFamily::DotsTts,
899            "fish_audio" => ModelFamily::FishAudio,
900            "glm_tts" => ModelFamily::GlmTts,
901            "higgs_audio_tts" => ModelFamily::HiggsAudioTts,
902            "index_tts2" => ModelFamily::IndexTts2,
903            "irodori_tts" => ModelFamily::IrodoriTts,
904            "moss_tts_local" => ModelFamily::MossTtsLocal,
905            "moss_tts_nano" => ModelFamily::MossTtsNano,
906            "moss_tts_v15" => ModelFamily::MossTtsV15,
907            "moss_voicegen" => ModelFamily::MossVoicegen,
908            "neutts" => ModelFamily::Neutts,
909            "outetts" => ModelFamily::Outetts,
910            "pocket_tts" => ModelFamily::PocketTts,
911            "vietneu_tts" => ModelFamily::VieneuV3Turbo,
912            "vieneu_v3_turbo" => ModelFamily::VieneuV3Turbo,
913            "fireredtts3" => ModelFamily::Fireredtts3,
914            "f5_tts" => ModelFamily::F5Tts,
915            "magpie_tts" => ModelFamily::MagpieTts,
916            "personaplex" => ModelFamily::Personaplex,
917            "soprano_tts" => ModelFamily::SopranoTts,
918            "echo_tts" => ModelFamily::EchoTts,
919            "voxcpm1" => ModelFamily::Voxcpm1,
920            "kokoro_tts" => ModelFamily::KokoroTts,
921            "zipvoice" => ModelFamily::Zipvoice,
922            "auk" => ModelFamily::Auk,
923            "piper_tts" => ModelFamily::PiperTts,
924            "kitten_tts" => ModelFamily::KittenTts,
925            "kitten_tts2" => ModelFamily::KittenTts2,
926            "kugelaudio" => ModelFamily::Kugelaudio,
927            "maya1" => ModelFamily::Maya1,
928            "moss_ttsd" => ModelFamily::MossTtsd,
929            "minimax_h3" => ModelFamily::MinimaxH3,
930            "sanotts" => ModelFamily::Sanotts,
931            "sopro_tts" => ModelFamily::SoproTts,
932            "sopro" => ModelFamily::SoproTts,
933            "sopro_v2" => ModelFamily::SoproTts,
934            "sopro_v2_turbo" => ModelFamily::SoproTts,
935            "mira_tts" => ModelFamily::MiraTts,
936            "miratts" => ModelFamily::MiraTts,
937            "mira" => ModelFamily::MiraTts,
938            "MiraTTS" => ModelFamily::MiraTts,
939            "cosyvoice3" => ModelFamily::Cosyvoice3,
940            "breeze_tts" => ModelFamily::BreezeTts,
941            "sortformer_diar" => ModelFamily::SortformerDiar,
942            "sortformer_diar_v2" => ModelFamily::SortformerDiarV2,
943            "seed_vc" => ModelFamily::SeedVc,
944            "rvc" => ModelFamily::Rvc,
945            "meanvc2" => ModelFamily::Meanvc2,
946            "tone_color_vc" => ModelFamily::ToneColorVc,
947            "chatterbox" => ModelFamily::Chatterbox,
948            "chatterbox_turbo" => ModelFamily::ChatterboxTurbo,
949            "vevo2" => ModelFamily::Vevo2,
950            "voxcpm2" => ModelFamily::Voxcpm2,
951            "ace_step" => ModelFamily::AceStep,
952            "htdemucs" => ModelFamily::Htdemucs,
953            "htdemucs_6stems" => ModelFamily::Htdemucs6Stems,
954            "htdemucs_6s" => ModelFamily::Htdemucs6Stems,
955            "mel_band_roformer" => ModelFamily::MelBandRoformer,
956            "bs_roformer" => ModelFamily::BsRoformer,
957            "muscriptor" => ModelFamily::Muscriptor,
958            "omnivoice" => ModelFamily::Omnivoice,
959            "stable_audio" => ModelFamily::StableAudio,
960            "minimax_music3" => ModelFamily::MinimaxMusic3,
961            "supertonic" => ModelFamily::Supertonic,
962            "yue2" => ModelFamily::Yue2,
963            "sheetsage2" => ModelFamily::Sheetsage2,
964            "voxtral_realtime" => ModelFamily::VoxtralRealtime,
965            "audiosr" => ModelFamily::Audiosr,
966            "apollo" => ModelFamily::Apollo,
967            "universr" => ModelFamily::Universr,
968            "builtin_audio_utils" => ModelFamily::BuiltinAudioUtils,
969            "controlfoley" => ModelFamily::ControlFoley,
970            "midashenglm_gen" => ModelFamily::MidashEnglmGen,
971            "liveavatar" => ModelFamily::Liveavatar,
972            "reuse" => ModelFamily::Reuse,
973            "sam_audio" => ModelFamily::SamAudio,
974            "sidon" => ModelFamily::Sidon,
975            "smart_turn" => ModelFamily::SmartTurn,
976            "miocodec" => ModelFamily::Miocodec,
977            "miotts" => ModelFamily::Miotts,
978            "vibevoice" => ModelFamily::Vibevoice,
979            "dramabox" => ModelFamily::Dramabox,
980            "heartmula" => ModelFamily::Heartmula,
981            "inflect_v2" => ModelFamily::InflectV2,
982            "qwen3_forced_aligner" => ModelFamily::Qwen3ForcedAligner,
983            "mms_forced_aligner" => ModelFamily::MmsForcedAligner,
984            other => ModelFamily::Custom(other.to_owned()),
985        }
986    }
987}
988
989impl From<String> for ModelFamily {
990    fn from(s: String) -> Self {
991        ModelFamily::from(s.as_str())
992    }
993}
994
995impl AsRef<str> for ModelFamily {
996    fn as_ref(&self) -> &str {
997        self.as_str()
998    }
999}
1000
1001impl std::fmt::Display for ModelFamily {
1002    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1003        f.write_str(self.as_str())
1004    }
1005}
1006
1007/// 计算后端(映射 `audiocpp_model_create_task_session` 的 `backend` 参数)。
1008#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1009#[serde(rename_all = "lowercase")]
1010pub enum Backend {
1011    /// CPU
1012    Cpu,
1013    /// CUDA
1014    Cuda,
1015    /// HIP / ROCm
1016    Hip,
1017    /// Vulkan
1018    Vulkan,
1019    /// Metal
1020    Metal,
1021    /// 自动选择最佳可用后端
1022    Best,
1023}
1024
1025impl Backend {
1026    /// 转换为传给 C 边界的字符串。
1027    pub fn as_str(self) -> &'static str {
1028        match self {
1029            Backend::Cpu => "cpu",
1030            Backend::Cuda => "cuda",
1031            Backend::Hip => "hip",
1032            Backend::Vulkan => "vulkan",
1033            Backend::Metal => "metal",
1034            Backend::Best => "best",
1035        }
1036    }
1037}
1038
1039/// 计算设备(`audiocpp_registry_devices_json` 的一项)。
1040#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1041pub struct Device {
1042    /// 后端名,如 `"CPU"`。
1043    pub backend: String,
1044    /// 设备序号。
1045    pub index: i32,
1046    /// 设备名。
1047    pub name: String,
1048    /// 设备类型,如 `"CPU"`。
1049    #[serde(rename = "type")]
1050    pub kind: String,
1051}
1052
1053/// 模型族 loader 的声明信息(`audiocpp_registry_loaders_json` 的一项)。
1054#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1055pub struct LoaderInfo {
1056    /// 模型族名。
1057    pub family: String,
1058    /// 能力集合。
1059    pub capabilities: Capabilities,
1060    /// 指令策略描述。
1061    pub instructions_policy: String,
1062    /// 可用的 API 端点。
1063    pub api_endpoints: Vec<String>,
1064}
1065
1066/// 能力集合(`audiocpp_model_capabilities_json` / loader 声明)。
1067#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1068pub struct Capabilities {
1069    /// 支持的任务及其模式。
1070    pub supported_tasks: Vec<SupportedTask>,
1071    /// 支持的语言。
1072    pub languages: Vec<String>,
1073    /// 是否支持说话人参考音频。
1074    pub supports_speaker_reference: bool,
1075    /// 是否支持风格条件。
1076    pub supports_style_condition: bool,
1077    /// 是否支持时间戳。
1078    pub supports_timestamps: bool,
1079}
1080
1081/// 一个受支持的任务及其可用模式。
1082#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1083pub struct SupportedTask {
1084    /// 任务名,如 `"vad"`。
1085    pub task: String,
1086    /// 可用模式,如 `["offline","streaming"]`。
1087    pub modes: Vec<String>,
1088}
1089
1090/// 已加载模型的元数据(`audiocpp_model_metadata_json`)。
1091#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1092pub struct ModelMetadata {
1093    /// 模型族。
1094    pub family: String,
1095    /// 变体。
1096    pub variant: String,
1097    /// 描述。
1098    pub description: String,
1099    /// 候选配置。
1100    pub config_candidates: Vec<String>,
1101    /// 候选权重。
1102    pub weight_candidates: Vec<String>,
1103}
1104
1105/// 一段音频在采样轴上的时间范围。
1106#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1107pub struct TimeSpan {
1108    /// 起始采样点(含)。
1109    pub start_sample: i64,
1110    /// 结束采样点(不含)。
1111    pub end_sample: i64,
1112}
1113
1114/// 语音活动片段。
1115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1116pub struct SpeechSegment {
1117    /// 时间范围。
1118    pub span: TimeSpan,
1119    /// 置信度(0..1)。
1120    pub confidence: f32,
1121    /// 附带文本(如 ASR 片段)。
1122    pub text: String,
1123}
1124
1125/// 说话人分离中的一段发言(谁在什么时间说话)。
1126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1127pub struct SpeakerTurn {
1128    /// 说话人 id,如 `"speaker_0"`。
1129    pub speaker_id: String,
1130    /// 发言时间范围。
1131    pub span: TimeSpan,
1132    /// 置信度(0..1)。
1133    pub confidence: f32,
1134    /// 该段文本(部分模型产出,如带转写的说话人分离)。
1135    #[serde(default)]
1136    pub text: String,
1137}
1138
1139/// 词级时间戳(ASR 逐词对齐)。
1140#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1141pub struct WordTimestamp {
1142    /// 时间范围(采样点)。
1143    pub span: TimeSpan,
1144    /// 词文本。
1145    pub word: String,
1146    /// 置信度(0..1)。
1147    pub confidence: f32,
1148}
1149
1150/// 模型产出的产物(embedding / 状态 / 对齐结果等)。
1151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1152pub struct VoiceArtifact {
1153    /// 产物种类(如 `speaker_embedding` / `transcript_alignment`)。
1154    pub kind: String,
1155    /// 产物 id。
1156    pub id: String,
1157    /// 二进制载荷的 base64 编码(无载荷时为空字符串)。
1158    #[serde(default)]
1159    pub payload_base64: String,
1160    /// 附加元数据。
1161    #[serde(default)]
1162    pub meta: BTreeMap<String, String>,
1163}
1164
1165/// CLI 选项信息(模型支持的请求 / 会话 / 加载选项)。
1166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1167pub struct CliOption {
1168    /// 选项名。
1169    pub name: String,
1170    /// 值名称(帮助文本)。
1171    pub value_name: String,
1172    /// 描述。
1173    pub description: String,
1174    /// 是否必填。
1175    pub required: bool,
1176    /// 默认值(可选)。
1177    #[serde(default, skip_serializing_if = "Option::is_none")]
1178    pub default_value: Option<String>,
1179    /// 最小值(可选,字符串形式)。
1180    #[serde(default, skip_serializing_if = "Option::is_none")]
1181    pub min_value: Option<String>,
1182    /// 最大值(可选,字符串形式)。
1183    #[serde(default, skip_serializing_if = "Option::is_none")]
1184    pub max_value: Option<String>,
1185}
1186
1187/// 模型预检时发现的配置 / 权重资产。
1188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1189pub struct NamedAsset {
1190    /// 资产 id。
1191    pub id: String,
1192    /// 资产路径。
1193    pub path: String,
1194}
1195
1196/// 模型的三类 CLI 选项(请求 / 会话 / 加载)。
1197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1198pub struct CliInterface {
1199    /// 请求级选项(如 `audio_chunk_seconds`)。
1200    #[serde(default)]
1201    pub request_options: Vec<CliOption>,
1202    /// 会话级选项。
1203    #[serde(default)]
1204    pub session_options: Vec<CliOption>,
1205    /// 加载级选项(如 `weight_id`)。
1206    #[serde(default)]
1207    pub load_options: Vec<CliOption>,
1208}
1209
1210/// 模型预检结果(`Registry::inspect`)。
1211#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1212pub struct ModelInspection {
1213    /// 模型元数据。
1214    pub metadata: ModelMetadata,
1215    /// 能力集合。
1216    pub capabilities: Capabilities,
1217    /// CLI 选项(请求 / 会话 / 加载)。
1218    pub cli: CliInterface,
1219    /// 发现的配置文件。
1220    #[serde(default)]
1221    pub discovered_configs: Vec<NamedAsset>,
1222    /// 发现的权重文件。
1223    #[serde(default)]
1224    pub discovered_weights: Vec<NamedAsset>,
1225    /// 模型根目录。
1226    #[serde(default)]
1227    pub model_root: String,
1228}
1229
1230/// 文本输出(如 ASR / TTS 的文本)。
1231#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1232pub struct TextOutput {
1233    /// 文本内容。
1234    pub text: String,
1235    /// 语言。
1236    pub language: String,
1237}
1238
1239/// 音频输出的描述信息(采样率 / 声道 / 长度)。
1240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1241pub struct AudioBufferInfo {
1242    /// 采样率。
1243    pub sample_rate: i32,
1244    /// 声道数。
1245    pub channels: i32,
1246    /// 采样数。
1247    pub sample_count: usize,
1248    /// 实际采样数据(f32,-1..1)。仅当生成音频需要回传时存在
1249    /// (如 TTS 的 `audio_output`);VAD / ASR 通常不携带。
1250    #[serde(default)]
1251    pub samples: Option<Vec<f32>>,
1252}
1253
1254/// 带名字的音频输出(如 TTS 的多段输出)。
1255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1256pub struct NamedAudioOutput {
1257    /// 输出 id。
1258    pub id: String,
1259    /// 音频描述。
1260    pub audio: AudioBufferInfo,
1261    /// 附加元数据。
1262    pub meta: BTreeMap<String, String>,
1263}
1264
1265/// 自定义结构化输出(上游 `TaskResult::custom_schema_output`)。
1266///
1267/// 目前仅 Smart Turn(task `turn`)产出:`schema` 为 `"smart_turn.v1"`,
1268/// `data` 为 `{"complete": bool, "probability": number}`(`threshold`
1269/// 请求选项判定,默认 0.5)。`data` 为任意 JSON,用 `serde_json::Value` 承载。
1270#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1271pub struct CustomSchemaOutput {
1272    /// 输出 schema 标识,如 `"smart_turn.v1"`。
1273    pub schema: String,
1274    /// 结构化数据载荷。
1275    pub data: serde_json::Value,
1276}
1277
1278/// 一次任务执行的完整结果(`audiocpp_session_run_offline` / `finish`)。
1279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1280pub struct TaskResult {
1281    /// 检测到的语音片段。
1282    pub speech_segments: Vec<SpeechSegment>,
1283    /// 说话人分离的发言分段(存在时)。
1284    pub speaker_turns: Vec<SpeakerTurn>,
1285    /// 文本输出(存在时)。
1286    pub text_output: Option<TextOutput>,
1287    /// 音频输出(存在时)。
1288    pub audio_output: Option<AudioBufferInfo>,
1289    /// 命名音频输出列表。
1290    pub named_audio_outputs: Vec<NamedAudioOutput>,
1291    /// 词级时间戳(存在时)。
1292    ///
1293    /// 是否输出按模型族而定,空数组只表示"本次无词戳",先查选项再报缺口:
1294    /// - Qwen3 ASR:默认**不**输出;需请求选项 `return_timestamps=true`(见
1295    ///   [`crate::options::request::RETURN_TIMESTAMPS`]),且建会话时经会话选项
1296    ///   配好对齐模型(见
1297    ///   [`crate::options::session::QWEN3_ASR_FORCED_ALIGNER_MODEL_PATH`]),
1298    ///   上游用对齐器内部回填。流式模式不支持词戳(直接抛错)。
1299    /// - 强制对齐族(`qwen3_forced_aligner` / `mms_forced_aligner`,task
1300    ///   `align`)直接输出;ASR 主文本 + 对齐回填是无词戳族的常规降级链。
1301    #[serde(default)]
1302    pub word_timestamps: Vec<WordTimestamp>,
1303    /// 自定义结构化输出(存在时,如 Smart Turn 的 `smart_turn.v1` 轮次判定)。
1304    #[serde(default)]
1305    pub custom_schema_output: Option<CustomSchemaOutput>,
1306    /// 单个产物输出(存在时,如 voice-clone 的 cached_voice_id)。
1307    #[serde(default)]
1308    pub artifact_output: Option<VoiceArtifact>,
1309    /// 多个产物输出(存在时)。
1310    #[serde(default)]
1311    pub output_artifacts: Vec<VoiceArtifact>,
1312}
1313
1314/// 流式会话产出的单个事件。
1315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1316pub struct StreamEvent {
1317    /// 语音活动事件列表。
1318    pub voice_activity: Vec<VoiceActivityEvent>,
1319    /// 部分文本(流式 ASR 等)。
1320    pub partial_text: Option<TextOutput>,
1321    /// 音频输出(存在时)。
1322    pub audio_output: Option<AudioBufferInfo>,
1323    /// 命名音频输出列表(如流式 TTS 的逐块 `chunk_N`)。
1324    #[serde(default)]
1325    pub named_audio_outputs: Vec<NamedAudioOutput>,
1326    /// 说话人分段(流式说话人分离等,存在时)。
1327    #[serde(default)]
1328    pub speaker_turns: Vec<SpeakerTurn>,
1329    /// 词级时间戳(仅产出词戳的族才有;Qwen3 ASR 流式不支持词戳,
1330    /// 见 [`TaskResult::word_timestamps`])。
1331    #[serde(default)]
1332    pub word_timestamps: Vec<WordTimestamp>,
1333    /// 产物输出(流式产出 embedding / 状态等,存在时)。
1334    #[serde(default)]
1335    pub output_artifacts: Vec<VoiceArtifact>,
1336    /// 是否为最终事件。
1337    pub is_final: bool,
1338}
1339
1340/// 语音活动事件(`speech_start` / `speech_end` / `speech_segment`)。
1341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1342pub struct VoiceActivityEvent {
1343    /// 事件类型。
1344    pub kind: String,
1345    /// 触发位置(采样点)。
1346    pub sample: i64,
1347    /// 语音概率。
1348    pub probability: f32,
1349    /// 片段(`speech_segment` 事件携带)。
1350    pub segment: Option<SpeechSegment>,
1351}
1352
1353/// 流式策略描述(`audiocpp_session_streaming_policy_json`)。
1354#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1355pub struct StreamingPolicy {
1356    /// 输入类型,如 `"audio_chunks"`。
1357    pub input: String,
1358    /// 输出类型,如 `"pull_events"` / `"final_result"`。
1359    pub output: String,
1360    /// 推荐的音频分块采样数。
1361    pub preferred_audio_chunk_samples: usize,
1362    /// 推荐的音频分块秒数。
1363    pub preferred_audio_chunk_seconds: f64,
1364}
1365
1366#[cfg(test)]
1367mod tests {
1368    // 测试断言中的 unwrap/expect 是惯用法:失败即测试失败,展开错误链无意义。
1369    #![allow(clippy::unwrap_used, clippy::expect_used)]
1370    use super::*;
1371
1372    /// 所有非 Custom 变体的 `as_str()` 必须能被 `From<&str>` 精确回收到
1373    /// 原变体(而不是落入 `Custom`),且 `as_str` 与 `From` 两张 match
1374    /// 表完全一致——防止新增模型族时漏改其一。
1375    #[test]
1376    fn model_family_roundtrip() {
1377        let mut variants: Vec<ModelFamily> = Vec::new();
1378        macro_rules! push {
1379            ($($v:ident),* $(,)?) => {
1380                $(variants.push(ModelFamily::$v);)*
1381            };
1382        }
1383        push![
1384            // VAD
1385            SileroVad,
1386            MarblenetVad,
1387            Pulsevad,
1388            // ASR
1389            Qwen3Asr,
1390            CitrinetAsr,
1391            SenseAsr,
1392            FunAsrNano,
1393            HiggsAudioStt,
1394            HviskeAsr,
1395            KrokoAsr,
1396            NemotronAsr,
1397            Nemotron3Diar,
1398            ParakeetTdt,
1399            VibevoiceAsr,
1400            VibevoiceAsrStreaming,
1401            FireredAudio,
1402            Granite5Asr,
1403            Audio8Asr,
1404            Audio8Tts,
1405            Vibeasr,
1406            MoonshineAsr,
1407            NiagaraAsr,
1408            CanaryAsr,
1409            CohereAsr,
1410            MossTranscribeDiarize,
1411            Confucius4R2t2,
1412            AudioFlamingo,
1413            Crisperwhisper,
1414            GigaamAsr,
1415            IndexEcho,
1416            Lfm2Audio,
1417            Owsm,
1418            OwsmCtc,
1419            Samsone,
1420            // TTS
1421            Qwen3Tts,
1422            Confucius4Tts,
1423            DotsTts,
1424            FishAudio,
1425            GlmTts,
1426            HiggsAudioTts,
1427            IndexTts2,
1428            IrodoriTts,
1429            MossTtsLocal,
1430            MossTtsNano,
1431            MossTtsV15,
1432            MossVoicegen,
1433            Neutts,
1434            Outetts,
1435            PocketTts,
1436            VieneuV3Turbo,
1437            Fireredtts3,
1438            F5Tts,
1439            MagpieTts,
1440            Personaplex,
1441            SopranoTts,
1442            EchoTts,
1443            Voxcpm1,
1444            KokoroTts,
1445            Zipvoice,
1446            Auk,
1447            PiperTts,
1448            KittenTts,
1449            KittenTts2,
1450            Kugelaudio,
1451            Maya1,
1452            MossTtsd,
1453            MinimaxH3,
1454            Sanotts,
1455            SoproTts,
1456            MiraTts,
1457            Cosyvoice3,
1458            BreezeTts,
1459            // 分离 / 转换 / 音乐
1460            SortformerDiar,
1461            SortformerDiarV2,
1462            SeedVc,
1463            Rvc,
1464            Meanvc2,
1465            ToneColorVc,
1466            Chatterbox,
1467            ChatterboxTurbo,
1468            Vevo2,
1469            Voxcpm2,
1470            AceStep,
1471            Htdemucs,
1472            Htdemucs6Stems,
1473            MelBandRoformer,
1474            BsRoformer,
1475            Muscriptor,
1476            Omnivoice,
1477            StableAudio,
1478            MinimaxMusic3,
1479            Supertonic,
1480            Yue2,
1481            Sheetsage2,
1482            VoxtralRealtime,
1483            Audiosr,
1484            Apollo,
1485            Universr,
1486            BuiltinAudioUtils,
1487            ControlFoley,
1488            MidashEnglmGen,
1489            Liveavatar,
1490            Reuse,
1491            SamAudio,
1492            Sidon,
1493            SmartTurn,
1494            // 其他
1495            Miocodec,
1496            Miotts,
1497            Vibevoice,
1498            Dramabox,
1499            Heartmula,
1500            InflectV2,
1501            Qwen3ForcedAligner,
1502            MmsForcedAligner,
1503        ];
1504        assert!(
1505            variants.len() >= 40,
1506            "ModelFamily 应至少覆盖上游全部 40+ loader 族,当前 {}",
1507            variants.len()
1508        );
1509        for v in variants {
1510            let name = v.as_str();
1511            assert_eq!(
1512                ModelFamily::from(name),
1513                v,
1514                "as_str() 与 From<&str> 不一致:{}",
1515                name
1516            );
1517            // Display / AsRef 应等价于 as_str。
1518            assert_eq!(format!("{v}"), name);
1519            assert_eq!(v.as_ref(), name);
1520        }
1521    }
1522
1523    #[test]
1524    fn model_family_custom_fallback() {
1525        // 未收录的名字落入 Custom 并原样保留。
1526        let m = ModelFamily::from("brand_new_family");
1527        match &m {
1528            ModelFamily::Custom(s) => assert_eq!(s, "brand_new_family"),
1529            other => panic!("未收录名字应落入 Custom,得到 {other:?}"),
1530        }
1531        assert_eq!(m.as_str(), "brand_new_family");
1532        // Custom 值本身不再被 From 回收。
1533        assert_eq!(
1534            ModelFamily::from("brand_new_family"),
1535            ModelFamily::Custom("brand_new_family".to_owned())
1536        );
1537        // From<String> 与 AsRef 一致。
1538        assert_eq!(
1539            ModelFamily::from("qwen3_asr".to_owned()),
1540            ModelFamily::Qwen3Asr
1541        );
1542    }
1543
1544    #[test]
1545    fn model_family_from_path() {
1546        // 关键词命中(常见 GGUF 命名)
1547        assert_eq!(
1548            ModelFamily::from_path("models/qwen3-asr-0.6b-q8_0.gguf"),
1549            Some(ModelFamily::Qwen3Asr)
1550        );
1551        assert_eq!(
1552            ModelFamily::from_path("sortformer-diar-4spk-v1-q8_0.gguf"),
1553            Some(ModelFamily::SortformerDiar)
1554        );
1555        assert_eq!(
1556            ModelFamily::from_path("citrinet-asr-q8_0.gguf"),
1557            Some(ModelFamily::CitrinetAsr)
1558        );
1559        assert_eq!(
1560            ModelFamily::from_path("moss-tts-nano-q8_0.gguf"),
1561            Some(ModelFamily::MossTtsNano)
1562        );
1563        // moss_tts_v15 不被裸 "moss" 关键词吞掉。
1564        assert_eq!(
1565            ModelFamily::from_path("moss-tts-v15-q8_0.gguf"),
1566            Some(ModelFamily::MossTtsV15)
1567        );
1568        // nemotron_3_diar 不被裸 "nemotron" 关键词吞掉。
1569        assert_eq!(
1570            ModelFamily::from_path("nemotron-3-diarization-bf16.gguf"),
1571            Some(ModelFamily::Nemotron3Diar)
1572        );
1573        // vieneu_v3_turbo / 旧名 vietneu 均命中同一族。
1574        assert_eq!(
1575            ModelFamily::from_path("vieneu-v3-turbo-q8_0.gguf"),
1576            Some(ModelFamily::VieneuV3Turbo)
1577        );
1578        assert_eq!(ModelFamily::from("vietneu_tts"), ModelFamily::VieneuV3Turbo);
1579        // 精确规则优先于裸关键词:qwen3-tts / qwen3-forced-aligner 不能判成 Qwen3Asr
1580        assert_eq!(
1581            ModelFamily::from_path("qwen3-tts-12hz-0.6b-base-q8_0.gguf"),
1582            Some(ModelFamily::Qwen3Tts)
1583        );
1584        assert_eq!(
1585            ModelFamily::from_path("qwen3-forced-aligner-q8_0.gguf"),
1586            Some(ModelFamily::Qwen3ForcedAligner)
1587        );
1588        assert_eq!(
1589            ModelFamily::from_path("mira-tts-q8_0.gguf"),
1590            Some(ModelFamily::MiraTts)
1591        );
1592        assert_eq!(
1593            ModelFamily::from_path("htdemucs-6s-q8_0.gguf"),
1594            Some(ModelFamily::Htdemucs6Stems)
1595        );
1596        // htdemucs_6stems 不被裸 "htdemucs" 关键词吞掉;裸名仍判回 Htdemucs。
1597        assert_eq!(
1598            ModelFamily::from_path("htdemucs-q8_0.gguf"),
1599            Some(ModelFamily::Htdemucs)
1600        );
1601        // kitten_tts2 不被 kitten_tts / 裸 "kitten" 吞掉。
1602        assert_eq!(
1603            ModelFamily::from_path("kitten-tts2-q8_0.gguf"),
1604            Some(ModelFamily::KittenTts2)
1605        );
1606        assert_eq!(
1607            ModelFamily::from_path("kitten-tts-q8_0.gguf"),
1608            Some(ModelFamily::KittenTts)
1609        );
1610        // index_echo 不被裸 "echo" 吞掉。
1611        assert_eq!(
1612            ModelFamily::from_path("index-echo-q8_0.gguf"),
1613            Some(ModelFamily::IndexEcho)
1614        );
1615        assert_eq!(
1616            ModelFamily::from_path("echo-tts-q8_0.gguf"),
1617            Some(ModelFamily::EchoTts)
1618        );
1619        // owsm_ctc 不被裸 "owsm" 吞掉。
1620        assert_eq!(
1621            ModelFamily::from_path("owsm-ctc-q8_0.gguf"),
1622            Some(ModelFamily::OwsmCtc)
1623        );
1624        assert_eq!(
1625            ModelFamily::from_path("owsm-q8_0.gguf"),
1626            Some(ModelFamily::Owsm)
1627        );
1628        // moss_ttsd 不被裸 "moss" 吞掉。
1629        assert_eq!(
1630            ModelFamily::from_path("moss-ttsd-q8_0.gguf"),
1631            Some(ModelFamily::MossTtsd)
1632        );
1633        // 新族常规命中抽查。
1634        assert_eq!(
1635            ModelFamily::from_path("smart-turn-q8_0.gguf"),
1636            Some(ModelFamily::SmartTurn)
1637        );
1638        assert_eq!(
1639            ModelFamily::from_path("tone-color-vc-q8_0.gguf"),
1640            Some(ModelFamily::ToneColorVc)
1641        );
1642        assert_eq!(
1643            ModelFamily::from("htdemucs_6s"),
1644            ModelFamily::Htdemucs6Stems
1645        );
1646        // 流式/新变体不被其前缀家族吞掉(子串匹配顺序)
1647        assert_eq!(
1648            ModelFamily::from_path("vibevoice-asr-streaming-7b-q8_0.gguf"),
1649            Some(ModelFamily::VibevoiceAsrStreaming)
1650        );
1651        assert_eq!(
1652            ModelFamily::from_path("sortformer-diar-v2-q8_0.gguf"),
1653            Some(ModelFamily::SortformerDiarV2)
1654        );
1655        assert_eq!(
1656            ModelFamily::from_path("kokoro-tts-82m-q8_0.gguf"),
1657            Some(ModelFamily::KokoroTts)
1658        );
1659        assert_eq!(
1660            ModelFamily::from_path("moonshine-tiny-q8_0.gguf"),
1661            Some(ModelFamily::MoonshineAsr)
1662        );
1663        assert_eq!(
1664            ModelFamily::from_path("niagara-asr-q8_0.gguf"),
1665            Some(ModelFamily::NiagaraAsr)
1666        );
1667        assert_eq!(
1668            ModelFamily::from_path("yue2-music-q8_0.gguf"),
1669            Some(ModelFamily::Yue2)
1670        );
1671        assert_eq!(
1672            ModelFamily::from_path("sheetsage2-q8_0.gguf"),
1673            Some(ModelFamily::Sheetsage2)
1674        );
1675        assert_eq!(
1676            ModelFamily::from_path("pulsevad-2.1k-f32.gguf"),
1677            Some(ModelFamily::Pulsevad)
1678        );
1679        assert_eq!(
1680            ModelFamily::from_path("canary-180m-flash-q8_0.gguf"),
1681            Some(ModelFamily::CanaryAsr)
1682        );
1683        assert_eq!(
1684            ModelFamily::from_path("cohere-transcribe-03-2026-q8_0.gguf"),
1685            Some(ModelFamily::CohereAsr)
1686        );
1687        // moss_transcribe_diarize 不能被裸 "moss" 吞掉
1688        assert_eq!(
1689            ModelFamily::from_path("moss-transcribe-diarize-q8_0.gguf"),
1690            Some(ModelFamily::MossTranscribeDiarize)
1691        );
1692        assert_eq!(
1693            ModelFamily::from_path("apollo-orig.gguf"),
1694            Some(ModelFamily::Apollo)
1695        );
1696        assert_eq!(
1697            ModelFamily::from_path("universr-audio-orig.gguf"),
1698            Some(ModelFamily::Universr)
1699        );
1700        assert_eq!(
1701            ModelFamily::from_path("zipvoice-distill-q8_0.gguf"),
1702            Some(ModelFamily::Zipvoice)
1703        );
1704        assert_eq!(
1705            ModelFamily::from_path("auk-flash-q8_0.gguf"),
1706            Some(ModelFamily::Auk)
1707        );
1708        assert_eq!(
1709            ModelFamily::from_path("liveavatar-nvfp4.gguf"),
1710            Some(ModelFamily::Liveavatar)
1711        );
1712        assert_eq!(
1713            ModelFamily::from_path("piper-en-us-lessac-medium-orig.gguf"),
1714            Some(ModelFamily::PiperTts)
1715        );
1716        assert_eq!(
1717            ModelFamily::from_path("kitten-tts-mini-0.8-orig.gguf"),
1718            Some(ModelFamily::KittenTts)
1719        );
1720        // confucius4_r2t2 不能被裸 "confucius" 吞掉(判成 Confucius4Tts)
1721        assert_eq!(
1722            ModelFamily::from_path("confucius4-r2t2-q8_0.gguf"),
1723            Some(ModelFamily::Confucius4R2t2)
1724        );
1725        assert_eq!(
1726            ModelFamily::from_path("confucius4-tts-q8_0.gguf"),
1727            Some(ModelFamily::Confucius4Tts)
1728        );
1729        // 内置音频工具:model_path 直接传工具 ID 也能映射到族。
1730        assert_eq!(
1731            ModelFamily::from_path("deepfilternet2"),
1732            Some(ModelFamily::BuiltinAudioUtils)
1733        );
1734        // 大小写不敏感
1735        assert_eq!(
1736            ModelFamily::from_path("Qwen3-ASR.Q8_0.GGUF"),
1737            Some(ModelFamily::Qwen3Asr)
1738        );
1739        // #family= 显式覆盖优先于关键词(大小写不敏感)
1740        assert_eq!(
1741            ModelFamily::from_path("model.gguf#family=citrinet_asr"),
1742            Some(ModelFamily::CitrinetAsr)
1743        );
1744        assert_eq!(
1745            ModelFamily::from_path("model.gguf#family=MiraTTS"),
1746            Some(ModelFamily::MiraTts)
1747        );
1748        assert_eq!(
1749            ModelFamily::from_path("model.gguf#family=qwen3_tts"),
1750            Some(ModelFamily::Qwen3Tts)
1751        );
1752        // 无法识别
1753        assert_eq!(ModelFamily::from_path("my-weights.gguf"), None);
1754        assert_eq!(ModelFamily::from_path(""), None);
1755    }
1756
1757    #[test]
1758    fn task_kind_as_str() {
1759        let cases = [
1760            (TaskKind::Vad, "vad"),
1761            (TaskKind::Asr, "asr"),
1762            (TaskKind::Diar, "diar"),
1763            (TaskKind::SourceSeparation, "sep"),
1764            (TaskKind::AudioGeneration, "gen"),
1765            (TaskKind::Tts, "tts"),
1766            (TaskKind::VoiceCloning, "clon"),
1767            (TaskKind::VoiceConversion, "vc"),
1768            (TaskKind::SpeechToSpeech, "s2s"),
1769            (TaskKind::Alignment, "align"),
1770            (TaskKind::VoiceDesign, "vdes"),
1771            (TaskKind::SpeakerRecognition, "spk"),
1772            (TaskKind::Svc, "svc"),
1773            (TaskKind::Midi, "midi"),
1774            (TaskKind::TurnDetection, "turn"),
1775        ];
1776        for (k, want) in cases {
1777            assert_eq!(k.as_str(), want);
1778        }
1779    }
1780
1781    #[test]
1782    fn task_kind_serde_roundtrip() {
1783        // serde 表示必须与 as_str(C 边界字符串)一致,而非变体名的
1784        // 小写形式(如 "sep" 而非 "sourceseparation")。
1785        let all = [
1786            TaskKind::Vad,
1787            TaskKind::Asr,
1788            TaskKind::Diar,
1789            TaskKind::SourceSeparation,
1790            TaskKind::AudioGeneration,
1791            TaskKind::Tts,
1792            TaskKind::VoiceCloning,
1793            TaskKind::VoiceConversion,
1794            TaskKind::SpeechToSpeech,
1795            TaskKind::Alignment,
1796            TaskKind::VoiceDesign,
1797            TaskKind::SpeakerRecognition,
1798            TaskKind::Svc,
1799            TaskKind::Midi,
1800            TaskKind::TurnDetection,
1801        ];
1802        for k in all {
1803            let s = serde_json::to_string(&k).unwrap();
1804            assert_eq!(s, format!("\"{}\"", k.as_str()));
1805            assert_eq!(serde_json::from_str::<TaskKind>(&s).unwrap(), k);
1806        }
1807        assert!(serde_json::from_str::<TaskKind>("\"sourceseparation\"").is_err());
1808    }
1809
1810    #[test]
1811    fn run_mode_as_str() {
1812        assert_eq!(RunMode::Offline.as_str(), "offline");
1813        assert_eq!(RunMode::Streaming.as_str(), "streaming");
1814    }
1815
1816    #[test]
1817    fn backend_as_str() {
1818        let cases = [
1819            (Backend::Cpu, "cpu"),
1820            (Backend::Cuda, "cuda"),
1821            (Backend::Hip, "hip"),
1822            (Backend::Vulkan, "vulkan"),
1823            (Backend::Metal, "metal"),
1824            (Backend::Best, "best"),
1825        ];
1826        for (b, want) in cases {
1827            assert_eq!(b.as_str(), want);
1828        }
1829    }
1830
1831    /// 结构化类型应能从 C 侧 `dump_*` 产出的 JSON 反序列化(serde 契约)。
1832    #[test]
1833    fn structured_types_deserialize() {
1834        let result: TaskResult = serde_json::from_str(
1835            r#"{
1836                "speech_segments": [
1837                    {"span": {"start_sample": 0, "end_sample": 1600}, "confidence": 0.95, "text": ""}
1838                ],
1839                "text_output": {"text": "hi", "language": "en"},
1840                "audio_output": {"sample_rate": 24000, "channels": 1, "sample_count": 0, "samples": []},
1841                "named_audio_outputs": [],
1842                "speaker_turns": [
1843                    {"speaker_id": "speaker_0", "span": {"start_sample": 0, "end_sample": 1600}, "confidence": 0.9, "text": "hello"}
1844                ],
1845                "word_timestamps": [
1846                    {"span": {"start_sample": 0, "end_sample": 320}, "word": "hello", "confidence": 0.99}
1847                ],
1848                "artifact_output": {"kind": "speaker_embedding", "id": "spk1", "payload_base64": "", "meta": {}},
1849                "output_artifacts": [
1850                    {"kind": "custom", "id": "a", "payload_base64": "AQID", "meta": {"k": "v"}}
1851                ]
1852            }"#,
1853        )
1854        .unwrap();
1855        assert_eq!(result.speech_segments.len(), 1);
1856        assert_eq!(result.speech_segments[0].span.start_sample, 0);
1857        assert_eq!(result.speech_segments[0].confidence, 0.95);
1858        assert_eq!(result.text_output.as_ref().unwrap().text, "hi");
1859        assert_eq!(result.audio_output.unwrap().sample_rate, 24000);
1860        assert_eq!(result.speaker_turns[0].text, "hello");
1861        assert_eq!(result.word_timestamps[0].word, "hello");
1862        assert_eq!(
1863            result.artifact_output.as_ref().unwrap().kind,
1864            "speaker_embedding"
1865        );
1866        assert_eq!(result.output_artifacts[0].payload_base64, "AQID");
1867
1868        let ev: StreamEvent = serde_json::from_str(
1869            r#"{
1870                "voice_activity": [
1871                    {"kind": "speech_start", "sample": 100, "probability": 0.8, "segment": null}
1872                ],
1873                "partial_text": null,
1874                "audio_output": null,
1875                "named_audio_outputs": [{"id": "chunk_0", "audio": {"sample_rate": 48000, "channels": 2, "sample_count": 320, "samples": []}, "meta": {}}],
1876                "speaker_turns": [],
1877                "word_timestamps": [],
1878                "output_artifacts": [],
1879                "is_final": true
1880            }"#,
1881        )
1882        .unwrap();
1883        assert_eq!(ev.voice_activity[0].kind, "speech_start");
1884        assert_eq!(ev.named_audio_outputs[0].id, "chunk_0");
1885        assert!(ev.is_final);
1886    }
1887
1888    #[test]
1889    fn model_inspection_deserialize() {
1890        // 验证 inspect 返回的 ModelInspection 结构与上游 dump_inspection 对齐,
1891        // 且缺失字段(无 meta / 无发现资产)能向后兼容。
1892        let info: ModelInspection = serde_json::from_str(
1893            r#"{
1894                "metadata": {
1895                    "family": "qwen3_asr",
1896                    "variant": "q8_0",
1897                    "description": "Qwen3 ASR",
1898                    "config_candidates": ["config.json"],
1899                    "weight_candidates": ["qwen3-asr-q8_0.gguf"]
1900                },
1901                "capabilities": {
1902                    "supported_tasks": [{"task": "asr", "modes": ["offline", "streaming"]}],
1903                    "languages": ["zh", "en"],
1904                    "supports_speaker_reference": false,
1905                    "supports_style_condition": false,
1906                    "supports_timestamps": true
1907                },
1908                "discovered_weights": [
1909                    {"id": "model", "path": "/models/qwen3-asr-q8_0.gguf"}
1910                ],
1911                "cli": {
1912                    "request_options": [
1913                        {"name": "language", "value_name": "CODE", "description": "语言", "required": false, "default_value": "auto"}
1914                    ],
1915                    "session_options": [],
1916                    "load_options": []
1917                }
1918            }"#,
1919        )
1920        .unwrap();
1921        assert_eq!(info.metadata.family, "qwen3_asr");
1922        assert!(
1923            info.capabilities
1924                .supported_tasks
1925                .iter()
1926                .any(|t| t.task == "asr" && t.modes.contains(&"streaming".to_string()))
1927        );
1928        assert_eq!(info.discovered_weights[0].id, "model");
1929        assert_eq!(info.cli.request_options[0].name, "language");
1930        assert_eq!(
1931            info.cli.request_options[0].default_value.as_deref(),
1932            Some("auto")
1933        );
1934    }
1935}