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    /// 唤醒词检测(Wake Word,如 microWakeWord / sherpa KWS;离线+流式)
47    WakeWord,
48    /// 音频分类(Audio Classification,如 CED / AST AudioSet 的 527 类;
49    /// 仅离线,结果在 `custom_schema_output`)
50    AudioClassification,
51}
52
53impl TaskKind {
54    /// 转换为传给 C 边界的字符串。
55    pub fn as_str(self) -> &'static str {
56        match self {
57            TaskKind::Vad => "vad",
58            TaskKind::Asr => "asr",
59            TaskKind::Diar => "diar",
60            TaskKind::SourceSeparation => "sep",
61            TaskKind::AudioGeneration => "gen",
62            TaskKind::Tts => "tts",
63            TaskKind::VoiceCloning => "clon",
64            TaskKind::VoiceConversion => "vc",
65            TaskKind::SpeechToSpeech => "s2s",
66            TaskKind::Alignment => "align",
67            TaskKind::VoiceDesign => "vdes",
68            TaskKind::SpeakerRecognition => "spk",
69            TaskKind::Svc => "svc",
70            TaskKind::Midi => "midi",
71            TaskKind::TurnDetection => "turn",
72            TaskKind::WakeWord => "wake",
73            TaskKind::AudioClassification => "cls",
74        }
75    }
76}
77
78impl Serialize for TaskKind {
79    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
80        serializer.serialize_str(self.as_str())
81    }
82}
83
84impl<'de> Deserialize<'de> for TaskKind {
85    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
86        let s = String::deserialize(deserializer)?;
87        match s.as_str() {
88            "vad" => Ok(TaskKind::Vad),
89            "asr" => Ok(TaskKind::Asr),
90            "diar" => Ok(TaskKind::Diar),
91            "sep" => Ok(TaskKind::SourceSeparation),
92            "gen" => Ok(TaskKind::AudioGeneration),
93            "tts" => Ok(TaskKind::Tts),
94            "clon" => Ok(TaskKind::VoiceCloning),
95            "vc" => Ok(TaskKind::VoiceConversion),
96            "s2s" => Ok(TaskKind::SpeechToSpeech),
97            "align" => Ok(TaskKind::Alignment),
98            "vdes" => Ok(TaskKind::VoiceDesign),
99            "spk" => Ok(TaskKind::SpeakerRecognition),
100            "svc" => Ok(TaskKind::Svc),
101            "midi" => Ok(TaskKind::Midi),
102            "turn" => Ok(TaskKind::TurnDetection),
103            // 上游别名:`wake_word`(`wake` 的别名)、`classify`(`cls` 的别名),
104            // capabilities 等 JSON 里出现的是规范短名,解析时两者都接受。
105            "wake" | "wake_word" => Ok(TaskKind::WakeWord),
106            "cls" | "classify" => Ok(TaskKind::AudioClassification),
107            other => Err(serde::de::Error::unknown_variant(
108                other,
109                &[
110                    "vad", "asr", "diar", "sep", "gen", "tts", "clon", "vc", "s2s", "align",
111                    "vdes", "spk", "svc", "midi", "turn", "wake", "cls",
112                ],
113            )),
114        }
115    }
116}
117
118/// 会话运行模式(离线 / 流式)。
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
120#[serde(rename_all = "lowercase")]
121pub enum RunMode {
122    /// 离线:一次请求 → 一次结果。
123    Offline,
124    /// 流式:持续送入音频、持续产出事件。
125    Streaming,
126}
127
128impl RunMode {
129    /// 转换为传给 C 边界的字符串。
130    pub fn as_str(self) -> &'static str {
131        match self {
132            RunMode::Offline => "offline",
133            RunMode::Streaming => "streaming",
134        }
135    }
136}
137
138/// 模型族(映射 `Registry::load` 的 `family_hint` 参数)。
139///
140/// 枚举覆盖上游 `engine_runtime` 的全部 loader 族,`as_str()` 返回传给
141/// C 边界的字符串。GGUF / NeMo safetensors 无法被引擎自动探测族别,加载
142/// 时必须显式指定;用枚举代替裸字符串可避免拼写错误。
143///
144/// 上游社区持续新增模型,本枚举可能滞后:unknown 族可直接用
145/// [`ModelFamily::Custom`] 传任意字符串,`From<&str>` 也会把未收录的名字
146/// 自动映射到 `Custom`。
147#[derive(Debug, Clone, PartialEq, Eq, Hash)]
148pub enum ModelFamily {
149    // ---- VAD ----
150    /// Silero VAD(内置权重,通常可省略 family_hint)
151    SileroVad,
152    /// MarbleNet VAD(NeMo 格式,须显式指定)
153    MarblenetVad,
154    /// PulseVAD(16kHz 单声道语音活动检测,2.1K student / 81K teacher;
155    /// 仅离线,阈值选项键为 `threshold`)
156    Pulsevad,
157    /// FireRed VAD(DFSMN 语音活动检测,16kHz mono;离线+流式,
158    /// 流式需 Stream-VAD checkpoint;选项 `threshold` 等)
159    FireredVad,
160    /// Earshot VAD(原生 C++ 移植的语音活动检测,16kHz mono,16ms 帧;
161    /// task 为 `vad`,离线+流式;权重为 `earshot_vad.safetensors`(非 GGUF),
162    /// CMake target `earshot_vad`(alias `earshot`);流式硬要求 16kHz 单声道;
163    /// 阈值选项 `threshold`/`neg_threshold`,另支持 `smoothing_factor`/
164    /// `speech_pad_ms`/`min_speech_duration_ms`/`min_silence_duration_ms`/
165    /// `start_delay_ms`/`keep_trailing_segment`/会话选项 `exec_plan`)
166    EarshotVad,
167
168    // ---- ASR ----
169    /// Qwen3 ASR
170    Qwen3Asr,
171    /// Citrinet ASR
172    CitrinetAsr,
173    /// SenseVoice ASR(阿里,社区模型)
174    SenseAsr,
175    /// Fun ASR Nano
176    FunAsrNano,
177    /// Higgs Audio STT
178    HiggsAudioStt,
179    /// Hviske ASR
180    HviskeAsr,
181    /// Hviske ASR v6(丹麦语 Whisper-rotary 编码器 + Qwen3 解码器;
182    /// task 为 `asr` 仅离线,16kHz 输入)
183    HviskeAsrV6,
184    /// Kroko ASR
185    KrokoAsr,
186    /// Nemotron ASR
187    NemotronAsr,
188    /// Nemotron 3 Diarization(NVIDIA 八说话人流式分离,社区预览;
189    /// CMake target `nemotron_3_diar`,用 `model-nemotron-3-diar` feature 编译;
190    /// task 为 `diar`,离线+原生批+流式,16kHz 输入)
191    Nemotron3Diar,
192    /// Nemotron 3.5 Transcribe 30B-A4B(NVIDIA Nemotron-H 语音大模型,实验性;
193    /// CMake target `nemotron_transcribe`,用 `model-nemotron-transcribe` feature
194    /// 编译;task 为 `asr` 仅离线;输出含 `word_timestamps` + `speaker_turns` +
195    /// `custom_schema_output`;请求选项 `template_name`/`return_timestamps`/
196    /// `diarization`,会话选项 `disk_experts`/`pin_experts`/`expert_cache_size`
197    /// 等 CPU 专家卸载系)
198    NemotronTranscribe,
199    /// Parakeet TDT
200    ParakeetTdt,
201    /// Vibevoice ASR
202    VibevoiceAsr,
203    /// VibeVoice ASR 流式 7B(离线+流式;与 VibevoiceAsr 同属
204    /// `vibevoice_asr` CMake target,用 `model-vibevoice-asr` feature 编译)
205    VibevoiceAsrStreaming,
206    /// FireRed Audio(语音理解 / 生成,含 Qwen3.5 运行时)
207    FireredAudio,
208    /// Granite 5 ASR(IBM Granite Speech 5.0 470M TurboCTC ASR,社区模型)
209    Granite5Asr,
210    /// Audio8 ASR(社区 ASR 模型)
211    Audio8Asr,
212    /// Audio8 TTS(社区 TTS 模型)
213    Audio8Tts,
214    /// VibeASR(社区 INT8/ternary ASR)
215    Vibeasr,
216    /// Moonshine ASR(社区流式 ASR,离线+流式)
217    MoonshineAsr,
218    /// Niagara ASR(ABR Niagara 英文批处理 ASR,仅离线)
219    NiagaraAsr,
220    /// Canary ASR(NVIDIA Canary 180M Flash 多语 ASR/翻译,仅离线;
221    /// en/de/es/fr,选项 `target_language`/`pnc`)
222    CanaryAsr,
223    /// Cohere ASR(Cohere Transcribe 多语转写,仅离线;14 语种,
224    /// 选项 `pnc`/`max_tokens`)
225    CohereAsr,
226    /// MOSS-Transcribe-Diarize(Whisper 编码器 + Qwen3 解码器,
227    /// 转写+说话人分离+时间戳联合输出;离线+流式)
228    MossTranscribeDiarize,
229    /// Confucius4-R2T2(网易实时流式 ASR,社区模型;离线+流式)
230    Confucius4R2t2,
231    /// Audio Flamingo(转写/描述/问答,speech+music+环境音;task 为 `asr` 仅离线)
232    AudioFlamingo,
233    /// CrisperWhisper(Whisper 逐字/意图转写;task 为 `asr`/`align`,离线+流式)
234    Crisperwhisper,
235    /// GigaAM ASR(v3 与多语 Conformer,CTC/RNN-T 解码;task 为 `asr` 仅离线)
236    GigaamAsr,
237    /// Index-Echo-S2TT(语音到文本翻译,双语时间戳字幕;task 为 `asr` 仅离线)
238    IndexEcho,
239    /// LFM2-Audio(Liquid LFM2.5-Audio,FastConformer 编码器;task 为 `asr` 仅离线)
240    Lfm2Audio,
241    /// LFM3-Audio(Liquid AI LFM3-Audio-2B,实验性;log-mel 编码器 + LFM3 混合
242    /// conv/attention 主干,TTS 走 MaskGIT depth 解码 + LiquidCodec 得 24kHz;
243    /// CMake target `lfm3_audio`,用 `model-lfm3-audio` feature 编译;
244    /// task 为 `asr`/`tts`/`s2s`,离线+流式,18 语种(无 auto);
245    /// S2S 多轮历史经 `output_artifacts` 的 `lfm3_audio.reply` 产物回传)
246    Lfm3Audio,
247    /// LFM d1-Omni(Liquid AI d1-Omni-2B 音频决策:对音频+具名问题输出
248    /// 校准的是/否、选项、评分与数值估计;CMake target `lfm_d1_omni`,
249    /// 用 `model-lfm-d1-omni` feature 编译;task 为 `cls`(classify)仅离线,
250    /// 多语种,结果在 `custom_schema_output`(schema `audio_decisions`))
251    LfmD1Omni,
252    /// OWSM(ESPnet Open Whisper 式多语 ASR/翻译;task 为 `asr`,离线+流式)
253    Owsm,
254    /// OWSM-CTC(ESPnet 非自回归多语 ASR/翻译;task 为 `asr`,离线+流式)
255    OwsmCtc,
256    /// SAMSONE(紧凑音频语言模型:描述/问答/声音音乐语音理解;
257    /// task 为 `asr` 仅离线)
258    Samsone,
259    /// Whistle ASR(社区离线语音识别,本地 FP32 GGUF;task 为 `asr` 仅离线)
260    WhistleAsr,
261    /// Phonon-2 ASR(Fermion Research 英文 ASR,复用 Parakeet 实现,原生热词
262    /// 与短语级流式;与 `parakeet_tdt` 同属 `parakeet_tdt` CMake target
263    /// (ALIASES),用 `model-phonon2-asr` feature 编译;
264    /// task 为 `asr`,离线+流式,en)
265    Phonon2Asr,
266    /// Underdog Bark 3(Conway Research 英文 ASR,Standard/Plus/Pro,
267    /// 复用 Parakeet 实现;与 `parakeet_tdt` 同 target(ALIASES),
268    /// 用 `model-underdog-bark3` feature 编译;task 为 `asr`,离线+流式,en)
269    UnderdogBark3,
270    /// Haas ASR(Haas-ja-v1.0 日文 KDA-TDT ASR,复用 Parakeet 实现;
271    /// 与 `parakeet_tdt` 同 target(ALIASES),用 `model-haas-asr` feature
272    /// 编译;task 为 `asr`,离线+流式,ja)
273    HaasAsr,
274
275    // ---- TTS ----
276    /// Qwen3 TTS
277    Qwen3Tts,
278    /// Confucius4 TTS
279    Confucius4Tts,
280    /// Dots TTS
281    DotsTts,
282    /// Fish Audio
283    FishAudio,
284    /// Fish Audio Drama 3(原生 MoE 语音生成/声音克隆/续写,社区模型,
285    /// 语言 `auto`;task 为 `tts` 长文 / `clon`,离线+流式;克隆需参考音频
286    /// 及其转写 `reference_text`,多参考/续写走 `multi_reference_cond` /
287    /// `previous_takes` 等 JSON 字符串选项)
288    FishDrama3,
289    /// GLM TTS
290    GlmTts,
291    /// Higgs Audio TTS
292    HiggsAudioTts,
293    /// Index TTS2
294    IndexTts2,
295    /// Irodori TTS
296    IrodoriTts,
297    /// MOSS TTS Local
298    MossTtsLocal,
299    /// MOSS TTS Nano
300    MossTtsNano,
301    /// MOSS-TTS-v1.5(8B delay-pattern 零样本声音克隆 TTS,en/zh;
302    /// task 为 `tts`/`clon` 仅离线;克隆需参考音频,请求选项
303    /// `tokens`/`instruct`/`language`/`moss_tts_v15.weight_type`)
304    MossTtsV15,
305    /// MOSS VoiceGen(语音生成)
306    MossVoicegen,
307    /// Neutts
308    Neutts,
309    /// Outetts
310    Outetts,
311    /// Pocket TTS
312    PocketTts,
313    /// Gradium Phonon(与 PocketTTS 同运行时:Mimi codec + flow 声学模型,
314    /// 预置音色 `default`,可导出/复用 voice state;task 为 `tts`,
315    /// 离线+流式;与 `pocket_tts` 同属 `pocket_tts` CMake target)
316    GradiumPhonon,
317    /// PocketTTS Small(英文 2m/7m/18m 及 ReFL 变体,实验性;
318    /// 独立 CMake target `pocket_tts_small`,用 `model-pocket-tts-small`
319    /// feature 编译;task 为 `tts`/`clon`,离线+流式,en,
320    /// 内置音色如 `alba`,克隆走 voice embeddings)
321    PocketTtsSmall,
322    /// VieNeu TTS v3 Turbo(越南语/英语 48kHz TTS + 即时声音克隆,社区模型;
323    /// 上游族名由 `vietneu_tts` 改名而来,旧名仍可经 `From<&str>` 解析;
324    /// task 为 `tts`/`clon` 仅离线;文本输入为 SEA-G2P 音素)
325    VieneuV3Turbo,
326    /// FireRed TTS3(零样本语音合成)
327    Fireredtts3,
328    /// F5 TTS
329    F5Tts,
330    /// Magpie TTS
331    MagpieTts,
332    /// PersonaPlex(多角色对话语音)
333    Personaplex,
334    /// MiniMax H3
335    MinimaxH3,
336    /// SanoTTS(社区 TTS,en/vi/id 共 7 个声音)
337    Sanotts,
338    /// Sopro V2 Turbo TTS
339    SoproTts,
340    /// MiraTTS(社区 TTS)
341    MiraTts,
342    /// CosyVoice3 TTS
343    Cosyvoice3,
344    /// BreezeTTS
345    BreezeTts,
346    /// Soprano TTS
347    SopranoTts,
348    /// Echo TTS
349    EchoTts,
350    /// VoxCPM1 TTS
351    Voxcpm1,
352    /// Kokoro TTS(Kokoro 82M 多语 TTS,仅离线)
353    KokoroTts,
354    /// ZipVoice(k2-fsa 社区流匹配 TTS,含 Distill 变体;en/zh;
355    /// task 为 `tts`/`clon` 仅离线;零样本克隆需 `reference_text` 选项)
356    Zipvoice,
357    /// AuK(腾讯 AuK / AuK-Flash 语音生成与编辑;
358    /// task 为 `tts`/`edit` 仅离线)
359    Auk,
360    /// Piper TTS(社区 VITS TTS,首包英文 `en_US-lessac-medium`,22.05kHz mono;
361    /// task 为 `tts` 仅离线;请求选项 `speed`/`variation`/`duration_variation`/`seed`)
362    PiperTts,
363    /// KittenTTS Mini 0.8(社区英文 TTS,80M 参数,8 个内置声音,24kHz mono;
364    /// task 为 `tts` 仅离线;请求选项 `speed`/`seed`,`--voice-id` 选声音)
365    KittenTts,
366    /// Kitten TTS 2(原生多语 Qwen3 语音生成 + S3 meanflow 解码 + 参考音频克隆;
367    /// task 为 `tts`/`clon` 仅离线)
368    KittenTts2,
369    /// Plapre 3(丹麦语 TTS:Llama 自回归解码器 + Kanade 音频 codec,
370    /// 内置 5 个音色;task 为 `tts`/`clon`,离线+流式(流式仅出完整文本块);
371    /// 克隆需 `reference_text`,另有 `voice_id`/`context_text`/`edit_*_frame` 等选项)
372    Plapre,
373    /// KugelAudio-0-Open(预置音色 TTS,增量音频输出;task 为 `tts`,离线+流式)
374    Kugelaudio,
375    /// Maya1(3B 表现力英文 TTS,自然语言音色设计 + 行内情感控制;
376    /// task 为 `tts` 仅离线)
377    Maya1,
378    /// MOSS-TTSD(8B delay-pattern 对话 TTS:单遍多说话人 + 逐人零样本克隆,
379    /// 24kHz 16 codebook;task 为 `tts`/`clon` 仅离线)
380    MossTtsd,
381    /// Ming 3.0 TTS 0.5B(Qwen 主干 + flow matching 声学头 + CAMPPlus 说话人
382    /// 条件 + 音频 VAE,zh/en/ja/ko/de/fr/es;task 为 `tts`/`clon`/`vdes`
383    /// 仅离线;`voice_design`/`freeform` 等模板经 `template_name` 选择)
384    Ming3Tts,
385    /// XTTS v2(Coqui 跨语 TTS + 声音克隆:GPT-2 mel-code 生成器 + perceiver
386    /// 条件重采样 + HiFi-GAN 解码器,17 语;task 为 `tts` 仅离线;
387    /// 参考音频即说话人条件,`language` 选项必填)
388    XttsV2,
389
390    // ---- 说话人分离 / 语音转换 / 音乐生成 ----
391    /// SortFormer 说话人分离(Diar)
392    SortformerDiar,
393    /// SortFormer v2.1 说话人分离(离线+流式,社区模型;
394    /// CMake target `sortformer_diar_v2`,用 `model-sortformer-diar-v2` feature 编译)
395    SortformerDiarV2,
396    /// Seed VC(语音转换)
397    SeedVc,
398    /// RVC(语音转换)
399    Rvc,
400    /// MeanVC2(语音转换)
401    Meanvc2,
402    /// Tone Color VC(独立语音转换,需参考说话人录音;task 为 `vc` 仅离线)
403    ToneColorVc,
404    /// Chatterbox(说话人还原 / TTS)
405    Chatterbox,
406    /// Chatterbox Turbo TTS(快速推理变体)
407    ChatterboxTurbo,
408    /// Vevo2(零样本 TTS / VC / 语音编辑)
409    Vevo2,
410    /// VoxCPM2
411    Voxcpm2,
412    /// Complex Inversion(多任务音频)
413    AceStep,
414
415    // ---- 音乐 / 音频生成与分离 ----
416    /// HTDemucs 音乐源分离
417    Htdemucs,
418    /// HTDemucs 6-stem(鼓/贝斯/人声/其他/吉他/钢琴六轨;task 为 `sep` 仅离线;
419    /// 与 `htdemucs` 同属 `demucs` CMake target,用 `model-demucs` feature 编译)
420    Htdemucs6Stems,
421    /// MossFormer2(重叠语音分离,16kHz;task 为 `sep` 仅离线)
422    Mossformer2,
423    /// TF-GridNet(时频 GridNet 重叠语音分离;task 为 `sep` 仅离线)
424    TfGridnet,
425    /// Mel Band Roformer(乐音建模)
426    MelBandRoformer,
427    /// BSRoformer
428    BsRoformer,
429    /// Muscriptor(乐理描述)
430    Muscriptor,
431    /// Omnivoice(条件自回归生成)
432    Omnivoice,
433    /// Stable Audio(音频生成)
434    StableAudio,
435    /// MiniMax Music3(音乐生成)
436    MinimaxMusic3,
437    /// Supertonic(音乐生成)
438    Supertonic,
439    /// YuE2(音乐生成:符号 ABC 规划 + 语义 codec + NAR 声学流合成;
440    /// 仅离线,task 为 `gen`;请求为纯文本(歌词)+ `style` 选项,不消费音频)
441    Yue2,
442    /// Magenta RealTime 2(Google 帧自回归立体声音乐生成,实验性;文本/音频风格/
443    /// MIDI 条件;CMake target `magenta_realtime_2`,用 `model-magenta-realtime-2`
444    /// feature 编译;task 为 `music`(即 `gen`),离线+流式;结果在 `audio_output`;
445    /// 请求选项 `duration_sec`/`guidance_scale`/`note_guidance_scale`/
446    /// `drum_guidance_scale`/`use_mapper`/`pianoroll_file`(JSON 字符串)/`drums`)
447    MagentaRealtime2,
448    /// SheetSage2(音频转符号谱:输入录音,输出 ABC 乐谱产物;
449    /// 仅离线,task 为 `midi`;结果在 `artifact_output` / `output_artifacts`)
450    Sheetsage2,
451    /// Voxtral Realtime
452    VoxtralRealtime,
453    /// Voxtral 4B TTS(Mistral Ministral 系自回归 + 流匹配 TTS,内置 20 个预置音色;
454    /// task 为 `tts`,离线+流式;语言 en/fr/es/de/it/pt/nl/ar/hi)
455    VoxtralTts,
456    /// AudioSR(语音/音频超分辨率)
457    Audiosr,
458    /// Apollo(44.1kHz 压缩音频音乐修复;task 为 `s2s` 仅离线)
459    Apollo,
460    /// UniverSR(复数 STFT 流匹配音频超分至 48kHz;task 为 `s2s` 仅离线;
461    /// 选项 `input_sample_rate`/`sampler_mode`/`num_inference_steps`/
462    /// `guidance_scale`/`seed`)
463    Universr,
464    /// 内置音频工具(降噪/增强/超分:deepfilternet2、rnnoise、zipenhancer、
465    /// gtcrn 系列、flashsr;task 为 `s2s` 仅离线;加载时 `model_path`
466    /// 传权重文件或其所在目录(权重需另行下载,非内嵌),另须经 `load_options`
467    /// 传 `{"utility":"<工具 ID>"}`(如 `"rnnoise"`)指定实现)
468    BuiltinAudioUtils,
469    /// ControlFoley(可控电影音效生成)
470    ControlFoley,
471    /// MidoAudio / MIdashengLM 生成式音频
472    MidashEnglmGen,
473    /// LiveAvatar(Wan S2V 音频驱动数字人;task 为 `sfx`(即 `gen`),
474    /// 离线+流式)
475    Liveavatar,
476    /// LatentSync 1.6(音频驱动唇形同步参考视频,实验性;task 为 `sfx`
477    /// (即 `gen`)仅离线;请求选项 `reference_video_file` 必填,
478    /// 需 FFmpeg 在 PATH)
479    Latentsync,
480    /// SoulX-FlashHead Lite(音频驱动的流行人像视频生成,实验性;
481    /// CMake target `soulx_flashhead`,用 `model-soulx-flashhead` feature 编译;
482    /// task 为 `sfx`(即 `gen`),离线+流式;请求选项 `reference_image_file`
483    /// 必填(单个 path),另有 `height`/`width`/`num_inference_steps`/
484    /// `color_correction_strength`/`audio_guidance_scale`;视频帧经
485    /// `output_artifacts` 回传,会话选项 `exec_plan`(vae.impl ggml/cudnn))
486    SoulxFlashhead,
487    /// RE-USE(采样率无关语音修复,双向 Mamba;task 为 `s2s` 仅离线)
488    Reuse,
489    /// Real-time RE-USE(RE-USE 的实时流式变体,语言无关;独立 CMake target
490    /// `realtime_reuse`,用 `model-realtime-reuse` feature 编译;
491    /// task 为 `s2s`,离线+流式,结果在 `audio_output`)
492    RealtimeReuse,
493    /// SAM Audio(prompt 条件音频分离;task 为 `s2s` 仅离线)
494    SamAudio,
495    /// Sidon v0.1(单说话人语音修复,48kHz 输出;task 为 `s2s` 仅离线)
496    Sidon,
497    /// Smart Turn(音频原生轮次完成检测,Whisper-Tiny 编码器;
498    /// task 为 `turn` 仅离线,16kHz mono,选项 `threshold`,结果在
499    /// `custom_schema_output`(schema `smart_turn.v1`))
500    SmartTurn,
501
502    // ---- 唤醒词 / 音频分类 ----
503    /// microWakeWord(MixedNet TFLite 转 GGUF 的流式唤醒词检测;
504    /// task 为 `wake`(别名 `wake_word`),离线+流式,16kHz mono,
505    /// 检测结果在 `speech_segments`)
506    MicroWakeWord,
507    /// sherpa KWS(Zipformer transducer 关键词检测,sherpa-onnx 导出;
508    /// task 为 `wake`(别名 `wake_word`),离线+流式,16kHz mono,
509    /// 检测结果在 `speech_segments`)
510    SherpaKws,
511    /// OpenWakeWord(共享语音嵌入 CNN + 转换分类器的唤醒词检测,英文
512    /// "alexa";task 为 `wake`(别名 `wake_word`),离线+流式,
513    /// 检测结果在 `speech_segments`,请求选项 `threshold`)
514    OpenWakeWord,
515    /// CED(Consistent Ensemble Distillation ViT 音频分类,527 个 AudioSet
516    /// 类别;task 为 `cls`(别名 `classify`)仅离线,结果在
517    /// `custom_schema_output`(schema `ced.classification.v1`))
518    Ced,
519    /// AST AudioSet(Audio Spectrogram Transformer 多标签分类,527 个
520    /// AudioSet 类别;task 为 `cls`(别名 `classify`)仅离线,结果在
521    /// `custom_schema_output`(schema `ast_audioset.classification.v1`))
522    AstAudioset,
523
524    // ---- 其他 ----
525    /// Miocodec / Miotts(小米语音)
526    Miocodec,
527    /// Miotts(小米 TTS)
528    Miotts,
529    /// Vibevoice(语音转换)
530    Vibevoice,
531    /// Dramabox(剧本播客)
532    Dramabox,
533    /// Heartmula
534    Heartmula,
535    /// Inflect V2
536    InflectV2,
537    /// Qwen3 Forced Aligner(强制对齐)
538    Qwen3ForcedAligner,
539    /// MMS Forced Aligner(强制对齐,社区模型)
540    MmsForcedAligner,
541    /// Ming 3.0 Audio Tiny(Whisper 音频编码器 + 混合 KDA/MLA MoE Thinker,
542    /// 另有连续 latent Talker 产出语音;task 为 `asr`/`s2s`/`tts`/`clon`
543    /// 仅离线,zh/en;内置 300+ Talker 音色,克隆需参考音频)
544    Ming3Audio,
545
546    /// 未收录的模型族名(原样传给 C 边界)。
547    Custom(String),
548}
549
550impl ModelFamily {
551    /// 按文件名关键词推断模型族。
552    ///
553    /// GGUF / NeMo safetensors 无法被引擎自动探测族别,加载时必须显式指定。
554    /// 上游社区习惯以模型族名命名权重文件(如 `qwen3-asr-0.6b-q8_0.gguf`、
555    /// `sortformer-diar-4spk-v1-q8_0.gguf`),本函数从文件名中的关键词
556    /// 匹配到对应 [`ModelFamily`];无法识别时返回 `None`,调用方应回退到
557    /// 显式 hint 或 [`ModelFamily::Custom`]。
558    ///
559    /// 匹配规则按关键词**首次命中**,文件名需包含明确的族名(如 `qwen3`、
560    /// `citrinet`、`moss-tts-nano`)。`#family=` 片段(`path#family=qwen3_asr`)
561    /// 会被剥离后参与匹配,同时优先于关键词命中。
562    ///
563    /// # 示例
564    ///
565    /// ```
566    /// use audio_cpp::ModelFamily;
567    ///
568    /// assert_eq!(
569    ///     ModelFamily::from_path("models/qwen3-asr-0.6b-q8_0.gguf"),
570    ///     Some(ModelFamily::Qwen3Asr),
571    /// );
572    /// assert_eq!(
573    ///     ModelFamily::from_path("model.gguf#family=citrinet_asr"),
574    ///     Some(ModelFamily::CitrinetAsr),
575    /// );
576    /// assert_eq!(ModelFamily::from_path("my-weights.gguf"), None);
577    /// ```
578    pub fn from_path(path: &str) -> Option<ModelFamily> {
579        let lower = path.to_ascii_lowercase();
580        // `#family=xxx` 片段:显式指定优先,直接映射后返回。
581        if let Some((_, f)) = lower.rsplit_once("#family=") {
582            return Some(ModelFamily::from(f.trim()));
583        }
584        // 关键词 → 族:按文件名常用命名约定匹配。
585        const KEYWORDS: &[(&str, ModelFamily)] = &[
586            ("silero_vad", ModelFamily::SileroVad),
587            ("silero-vad", ModelFamily::SileroVad),
588            ("marblenet_vad", ModelFamily::MarblenetVad),
589            ("marblenet-vad", ModelFamily::MarblenetVad),
590            ("marblenet", ModelFamily::MarblenetVad),
591            ("pulsevad", ModelFamily::Pulsevad),
592            ("pulse-vad", ModelFamily::Pulsevad),
593            ("pulse", ModelFamily::Pulsevad),
594            ("firered_vad", ModelFamily::FireredVad),
595            ("firered-vad", ModelFamily::FireredVad),
596            ("fireredvad", ModelFamily::FireredVad),
597            ("earshot_vad", ModelFamily::EarshotVad),
598            ("earshot-vad", ModelFamily::EarshotVad),
599            ("earshot", ModelFamily::EarshotVad),
600            ("qwen3_asr", ModelFamily::Qwen3Asr),
601            ("qwen3-asr", ModelFamily::Qwen3Asr),
602            ("qwen3_tts", ModelFamily::Qwen3Tts),
603            ("qwen3-tts", ModelFamily::Qwen3Tts),
604            ("qwen3_forced_aligner", ModelFamily::Qwen3ForcedAligner),
605            ("qwen3-forced-aligner", ModelFamily::Qwen3ForcedAligner),
606            ("qwen3", ModelFamily::Qwen3Asr),
607            ("citrinet", ModelFamily::CitrinetAsr),
608            ("sense_asr", ModelFamily::SenseAsr),
609            ("sense-asr", ModelFamily::SenseAsr),
610            ("sensevoice", ModelFamily::SenseAsr),
611            ("fun-asr-nano", ModelFamily::FunAsrNano),
612            ("fun_asr_nano", ModelFamily::FunAsrNano),
613            ("funasr", ModelFamily::FunAsrNano),
614            ("higgs_audio_stt", ModelFamily::HiggsAudioStt),
615            ("higgs_audio_tts", ModelFamily::HiggsAudioTts),
616            ("higgs", ModelFamily::HiggsAudioStt),
617            // hviske_asr_v6 必须排在裸 "hviske" 之前,否则会被吞掉。
618            ("hviske_asr_v6", ModelFamily::HviskeAsrV6),
619            ("hviske-asr-v6", ModelFamily::HviskeAsrV6),
620            ("hviske-v6", ModelFamily::HviskeAsrV6),
621            ("hviskev6", ModelFamily::HviskeAsrV6),
622            ("hviske", ModelFamily::HviskeAsr),
623            ("whistle_asr", ModelFamily::WhistleAsr),
624            ("whistle-asr", ModelFamily::WhistleAsr),
625            ("whistle", ModelFamily::WhistleAsr),
626            ("kroko", ModelFamily::KrokoAsr),
627            // nemotron_transcribe 必须排在 nemotron_3_diar / 裸 "nemotron" 之前:
628            // 上游权重目录名 `NVIDIA-Nemotron-3.5-Transcribe-30B-A4B-GGUF` 小写后
629            // 含子串 "nemotron-3",会被 diar 的关键词抢先吞掉。
630            ("nemotron_transcribe", ModelFamily::NemotronTranscribe),
631            ("nemotron-transcribe", ModelFamily::NemotronTranscribe),
632            ("nemotron_3.5_transcribe", ModelFamily::NemotronTranscribe),
633            ("nemotron-3.5-transcribe", ModelFamily::NemotronTranscribe),
634            ("nemotron-3.5", ModelFamily::NemotronTranscribe),
635            ("nemotron_3.5", ModelFamily::NemotronTranscribe),
636            ("nemotron_3_diar", ModelFamily::Nemotron3Diar),
637            ("nemotron-3-diar", ModelFamily::Nemotron3Diar),
638            ("nemotron_3", ModelFamily::Nemotron3Diar),
639            ("nemotron-3", ModelFamily::Nemotron3Diar),
640            ("nemotron", ModelFamily::NemotronAsr),
641            ("parakeet", ModelFamily::ParakeetTdt),
642            // phonon2_asr / underdog_bark3 / haas_asr 与 parakeet_tdt 同属
643            // `parakeet_tdt` target(ALIASES),权重名分别为
644            // phonon-2-mixed-*.gguf / underdog-bark-3.0-*.gguf / haas-ja-v1.0-*.gguf。
645            // phonon-2 现为正式族(此前 gradium_phonon 只收 phonon-31m/40m
646            // 正是给这里留白);"bark" 裸词易误伤,只收带 3 的写法。
647            ("phonon2_asr", ModelFamily::Phonon2Asr),
648            ("phonon2-asr", ModelFamily::Phonon2Asr),
649            ("phonon-2", ModelFamily::Phonon2Asr),
650            ("phonon2", ModelFamily::Phonon2Asr),
651            ("phonon_2", ModelFamily::Phonon2Asr),
652            ("underdog_bark3", ModelFamily::UnderdogBark3),
653            ("underdog-bark3", ModelFamily::UnderdogBark3),
654            ("underdog-bark-3", ModelFamily::UnderdogBark3),
655            ("underdog", ModelFamily::UnderdogBark3),
656            ("bark3", ModelFamily::UnderdogBark3),
657            ("bark-3", ModelFamily::UnderdogBark3),
658            ("haas_asr", ModelFamily::HaasAsr),
659            ("haas-asr", ModelFamily::HaasAsr),
660            ("haas-ja", ModelFamily::HaasAsr),
661            ("haas", ModelFamily::HaasAsr),
662            (
663                "vibevoice_asr_streaming",
664                ModelFamily::VibevoiceAsrStreaming,
665            ),
666            (
667                "vibevoice-asr-streaming",
668                ModelFamily::VibevoiceAsrStreaming,
669            ),
670            ("vibevoice_asr", ModelFamily::VibevoiceAsr),
671            ("firered_audio", ModelFamily::FireredAudio),
672            ("firered-audio", ModelFamily::FireredAudio),
673            ("granite5asr", ModelFamily::Granite5Asr),
674            ("granite-speech", ModelFamily::Granite5Asr),
675            ("granite_speech", ModelFamily::Granite5Asr),
676            ("granite", ModelFamily::Granite5Asr),
677            ("audio8_asr", ModelFamily::Audio8Asr),
678            ("audio8-asr", ModelFamily::Audio8Asr),
679            ("audio8asr", ModelFamily::Audio8Asr),
680            ("arkasr", ModelFamily::Audio8Asr),
681            ("audio8_tts", ModelFamily::Audio8Tts),
682            ("audio8-tts", ModelFamily::Audio8Tts),
683            ("audio8tts", ModelFamily::Audio8Tts),
684            ("fireredtts3", ModelFamily::Fireredtts3),
685            ("firered_tts3", ModelFamily::Fireredtts3),
686            ("firered-tts3", ModelFamily::Fireredtts3),
687            ("vibevoice", ModelFamily::Vibevoice),
688            // confucius4_r2t2 必须排在裸 "confucius" 之前,否则会被吞掉。
689            ("confucius4_r2t2", ModelFamily::Confucius4R2t2),
690            ("confucius4-r2t2", ModelFamily::Confucius4R2t2),
691            ("r2t2", ModelFamily::Confucius4R2t2),
692            ("confucius", ModelFamily::Confucius4Tts),
693            ("dots_tts", ModelFamily::DotsTts),
694            ("dots-tts", ModelFamily::DotsTts),
695            // fish_drama3 与 fish_audio 无子串重叠,但仍按“更具体者优先”排列。
696            ("fish_drama3", ModelFamily::FishDrama3),
697            ("fish-drama3", ModelFamily::FishDrama3),
698            ("fish_audio", ModelFamily::FishAudio),
699            ("fish-audio", ModelFamily::FishAudio),
700            ("glm_tts", ModelFamily::GlmTts),
701            ("glm-tts", ModelFamily::GlmTts),
702            ("index_tts2", ModelFamily::IndexTts2),
703            ("index-tts2", ModelFamily::IndexTts2),
704            ("irodori", ModelFamily::IrodoriTts),
705            // moss_transcribe_diarize 必须排在裸 "moss" 之前,否则会被吞掉。
706            (
707                "moss_transcribe_diarize",
708                ModelFamily::MossTranscribeDiarize,
709            ),
710            (
711                "moss-transcribe-diarize",
712                ModelFamily::MossTranscribeDiarize,
713            ),
714            // mossformer2 必须排在裸 "moss" 之前,否则会被吞掉。
715            ("mossformer2", ModelFamily::Mossformer2),
716            ("mossformer-2", ModelFamily::Mossformer2),
717            ("mossformer", ModelFamily::Mossformer2),
718            ("moss-tts-nano", ModelFamily::MossTtsNano),
719            ("moss_tts_nano", ModelFamily::MossTtsNano),
720            ("moss-tts-local", ModelFamily::MossTtsLocal),
721            ("moss_tts_local", ModelFamily::MossTtsLocal),
722            // moss_tts_v15 / moss_ttsd 必须排在裸 "moss" 之前,否则会被吞掉。
723            ("moss-tts-v15", ModelFamily::MossTtsV15),
724            ("moss_tts_v15", ModelFamily::MossTtsV15),
725            ("moss_ttsd", ModelFamily::MossTtsd),
726            ("moss-ttsd", ModelFamily::MossTtsd),
727            ("mossttsd", ModelFamily::MossTtsd),
728            ("moss_voicegen", ModelFamily::MossVoicegen),
729            ("moss-voicegen", ModelFamily::MossVoicegen),
730            ("moss", ModelFamily::MossTtsNano),
731            ("neutts", ModelFamily::Neutts),
732            ("outetts", ModelFamily::Outetts),
733            ("gradium_phonon", ModelFamily::GradiumPhonon),
734            ("gradium-phonon", ModelFamily::GradiumPhonon),
735            ("gradium", ModelFamily::GradiumPhonon),
736            // 打包权重名为 phonon-31m-en-*.gguf;不收裸 "phonon"(与 31m/40m
737            // 都无子串交叠的 "phonon-2" 已自 cc21c9ea→d2da530b 起归属正式族
738            // Phonon2Asr,见 parakeet 段关键词)。
739            ("phonon-31m", ModelFamily::GradiumPhonon),
740            ("phonon_31m", ModelFamily::GradiumPhonon),
741            ("phonon-40m", ModelFamily::GradiumPhonon),
742            ("phonon_40m", ModelFamily::GradiumPhonon),
743            // pocket_tts_small 必须排在裸 "pocket_tts" 之前(族名含其子串,
744            // 否则会被吞成 PocketTts);官方目录名 PocketTTS-Small-GGUF 由
745            // "pockettts-small" 覆盖。
746            ("pocket_tts_small", ModelFamily::PocketTtsSmall),
747            ("pocket-tts-small", ModelFamily::PocketTtsSmall),
748            ("pockettts-small", ModelFamily::PocketTtsSmall),
749            ("pocket_tts", ModelFamily::PocketTts),
750            ("pocket-tts", ModelFamily::PocketTts),
751            ("vietneu", ModelFamily::VieneuV3Turbo),
752            ("vieneu", ModelFamily::VieneuV3Turbo),
753            ("f5_tts", ModelFamily::F5Tts),
754            ("f5-tts", ModelFamily::F5Tts),
755            ("magpie", ModelFamily::MagpieTts),
756            ("personaplex", ModelFamily::Personaplex),
757            ("soprano_tts", ModelFamily::SopranoTts),
758            ("soprano-tts", ModelFamily::SopranoTts),
759            ("soprano", ModelFamily::SopranoTts),
760            // index_echo 必须排在裸 "echo" 之前,否则会被吞掉。
761            ("index_echo", ModelFamily::IndexEcho),
762            ("index-echo", ModelFamily::IndexEcho),
763            ("indexecho", ModelFamily::IndexEcho),
764            ("echo_tts", ModelFamily::EchoTts),
765            ("echo-tts", ModelFamily::EchoTts),
766            ("echo", ModelFamily::EchoTts),
767            ("kokoro_tts", ModelFamily::KokoroTts),
768            ("kokoro-tts", ModelFamily::KokoroTts),
769            ("kokoro", ModelFamily::KokoroTts),
770            ("piper_tts", ModelFamily::PiperTts),
771            ("piper-tts", ModelFamily::PiperTts),
772            ("piper", ModelFamily::PiperTts),
773            // kitten_tts2 必须排在 kitten_tts / 裸 "kitten" 之前,否则会被吞掉。
774            ("kitten_tts2", ModelFamily::KittenTts2),
775            ("kitten-tts2", ModelFamily::KittenTts2),
776            ("kitten_tts_2", ModelFamily::KittenTts2),
777            ("kittentts2", ModelFamily::KittenTts2),
778            ("kitten_tts", ModelFamily::KittenTts),
779            ("kitten-tts", ModelFamily::KittenTts),
780            ("kitten", ModelFamily::KittenTts),
781            // plapre 置于裸关键词之前不受影响(无前缀家族),单独列出。
782            ("plapre_3", ModelFamily::Plapre),
783            ("plapre-3", ModelFamily::Plapre),
784            ("plapre", ModelFamily::Plapre),
785            ("kugelaudio", ModelFamily::Kugelaudio),
786            ("kugel-audio", ModelFamily::Kugelaudio),
787            ("kugel_audio", ModelFamily::Kugelaudio),
788            ("kugel", ModelFamily::Kugelaudio),
789            ("maya1", ModelFamily::Maya1),
790            ("maya-1", ModelFamily::Maya1),
791            ("maya_1", ModelFamily::Maya1),
792            ("maya", ModelFamily::Maya1),
793            // ming3_audio 必须排在裸 "ming3" 之前,否则会被吞成 Ming3Tts。
794            ("ming3_audio", ModelFamily::Ming3Audio),
795            ("ming3-audio", ModelFamily::Ming3Audio),
796            ("ming-3.0-audio", ModelFamily::Ming3Audio),
797            ("ming_3.0_audio", ModelFamily::Ming3Audio),
798            ("ming3_tts", ModelFamily::Ming3Tts),
799            ("ming3-tts", ModelFamily::Ming3Tts),
800            ("ming-3.0-tts", ModelFamily::Ming3Tts),
801            ("ming_3.0_tts", ModelFamily::Ming3Tts),
802            ("ming3", ModelFamily::Ming3Tts),
803            ("xtts_v2", ModelFamily::XttsV2),
804            ("xtts-v2", ModelFamily::XttsV2),
805            ("xtts", ModelFamily::XttsV2),
806            ("voxcpm1", ModelFamily::Voxcpm1),
807            ("voxcpm-1", ModelFamily::Voxcpm1),
808            ("sanotts", ModelFamily::Sanotts),
809            ("sano-tts", ModelFamily::Sanotts),
810            ("sano_tts", ModelFamily::Sanotts),
811            ("sopro_tts", ModelFamily::SoproTts),
812            ("sopro-tts", ModelFamily::SoproTts),
813            ("sopro", ModelFamily::SoproTts),
814            ("mira_tts", ModelFamily::MiraTts),
815            ("mira-tts", ModelFamily::MiraTts),
816            ("miratts", ModelFamily::MiraTts),
817            ("mira", ModelFamily::MiraTts),
818            ("cosyvoice3", ModelFamily::Cosyvoice3),
819            ("cosyvoice-3", ModelFamily::Cosyvoice3),
820            ("cosyvoice", ModelFamily::Cosyvoice3),
821            ("breeze_tts", ModelFamily::BreezeTts),
822            ("breeze-tts", ModelFamily::BreezeTts),
823            ("breeze", ModelFamily::BreezeTts),
824            ("vibeasr", ModelFamily::Vibeasr),
825            ("vibe-asr", ModelFamily::Vibeasr),
826            ("moonshine_asr", ModelFamily::MoonshineAsr),
827            ("moonshine-asr", ModelFamily::MoonshineAsr),
828            ("moonshine", ModelFamily::MoonshineAsr),
829            ("niagara_asr", ModelFamily::NiagaraAsr),
830            ("niagara-asr", ModelFamily::NiagaraAsr),
831            ("niagara", ModelFamily::NiagaraAsr),
832            ("canary_asr", ModelFamily::CanaryAsr),
833            ("canary-asr", ModelFamily::CanaryAsr),
834            ("canary", ModelFamily::CanaryAsr),
835            ("cohere_asr", ModelFamily::CohereAsr),
836            ("cohere-asr", ModelFamily::CohereAsr),
837            ("cohere", ModelFamily::CohereAsr),
838            ("audio_flamingo", ModelFamily::AudioFlamingo),
839            ("audio-flamingo", ModelFamily::AudioFlamingo),
840            ("audioflamingo", ModelFamily::AudioFlamingo),
841            ("crisperwhisper", ModelFamily::Crisperwhisper),
842            ("crisper-whisper", ModelFamily::Crisperwhisper),
843            ("crisper_whisper", ModelFamily::Crisperwhisper),
844            ("gigaam_asr", ModelFamily::GigaamAsr),
845            ("gigaam-asr", ModelFamily::GigaamAsr),
846            ("gigaam", ModelFamily::GigaamAsr),
847            // lfm_d1_omni 权重名为 d1-Omni-2B-*.gguf(含 mmproj- 前缀的
848            // encoder 文件);"d1" 过短易误伤,只收族名与 d1-omni。
849            ("lfm_d1_omni", ModelFamily::LfmD1Omni),
850            ("lfm-d1-omni", ModelFamily::LfmD1Omni),
851            ("d1_omni", ModelFamily::LfmD1Omni),
852            ("d1-omni", ModelFamily::LfmD1Omni),
853            ("lfm2_audio", ModelFamily::Lfm2Audio),
854            ("lfm2-audio", ModelFamily::Lfm2Audio),
855            ("lfm2", ModelFamily::Lfm2Audio),
856            ("lfm3_audio", ModelFamily::Lfm3Audio),
857            ("lfm3-audio", ModelFamily::Lfm3Audio),
858            ("lfm3", ModelFamily::Lfm3Audio),
859            // owsm_ctc 必须排在裸 "owsm" 之前,否则会被吞掉。
860            ("owsm_ctc", ModelFamily::OwsmCtc),
861            ("owsm-ctc", ModelFamily::OwsmCtc),
862            ("owsmctc", ModelFamily::OwsmCtc),
863            ("owsm", ModelFamily::Owsm),
864            ("samsone", ModelFamily::Samsone),
865            ("sam-sone", ModelFamily::Samsone),
866            ("zipvoice", ModelFamily::Zipvoice),
867            ("zip-voice", ModelFamily::Zipvoice),
868            ("liveavatar", ModelFamily::Liveavatar),
869            ("live-avatar", ModelFamily::Liveavatar),
870            ("latentsync", ModelFamily::Latentsync),
871            ("latent-sync", ModelFamily::Latentsync),
872            ("latent_sync", ModelFamily::Latentsync),
873            ("soulx_flashhead", ModelFamily::SoulxFlashhead),
874            ("soulx-flashhead", ModelFamily::SoulxFlashhead),
875            ("soulx", ModelFamily::SoulxFlashhead),
876            ("flashhead", ModelFamily::SoulxFlashhead),
877            // realtime_reuse 必须排在裸 "reuse"/"re-use" 之前:官方权重目录为
878            // RE-USE-GGUF(小写后含 "re-use"),文件名 realtime-reuse-f32.gguf
879            // (含 "reuse"),两者都会被既有 RE-USE 关键词抢先吞掉。
880            ("realtime_reuse", ModelFamily::RealtimeReuse),
881            ("realtime-reuse", ModelFamily::RealtimeReuse),
882            ("real-time-reuse", ModelFamily::RealtimeReuse),
883            ("realtimereuse", ModelFamily::RealtimeReuse),
884            ("reuse", ModelFamily::Reuse),
885            ("re-use", ModelFamily::Reuse),
886            ("sam_audio", ModelFamily::SamAudio),
887            ("sam-audio", ModelFamily::SamAudio),
888            ("samaudio", ModelFamily::SamAudio),
889            ("sidon", ModelFamily::Sidon),
890            ("smart_turn", ModelFamily::SmartTurn),
891            ("smart-turn", ModelFamily::SmartTurn),
892            ("smartturn", ModelFamily::SmartTurn),
893            ("auk", ModelFamily::Auk),
894            ("minimax_h3", ModelFamily::MinimaxH3),
895            ("minimax-h3", ModelFamily::MinimaxH3),
896            ("sortformer_diar_v2", ModelFamily::SortformerDiarV2),
897            ("sortformer-diar-v2", ModelFamily::SortformerDiarV2),
898            ("sortformer_diar", ModelFamily::SortformerDiar),
899            ("sortformer-diar", ModelFamily::SortformerDiar),
900            ("sortformer", ModelFamily::SortformerDiar),
901            ("seed_vc", ModelFamily::SeedVc),
902            ("seed-vc", ModelFamily::SeedVc),
903            ("seedvc", ModelFamily::SeedVc),
904            ("rvc", ModelFamily::Rvc),
905            ("meanvc2", ModelFamily::Meanvc2),
906            ("mean-vc2", ModelFamily::Meanvc2),
907            ("tone_color_vc", ModelFamily::ToneColorVc),
908            ("tone-color-vc", ModelFamily::ToneColorVc),
909            ("tone_color", ModelFamily::ToneColorVc),
910            ("tonecolor", ModelFamily::ToneColorVc),
911            ("chatterbox_turbo", ModelFamily::ChatterboxTurbo),
912            ("chatterbox-turbo", ModelFamily::ChatterboxTurbo),
913            ("chatterbox", ModelFamily::Chatterbox),
914            ("vevo2", ModelFamily::Vevo2),
915            ("voxcpm2", ModelFamily::Voxcpm2),
916            ("ace_step", ModelFamily::AceStep),
917            ("ace-step", ModelFamily::AceStep),
918            // htdemucs_6stems(含别名 6s)必须排在裸 "htdemucs" 之前,否则会被吞掉。
919            ("htdemucs_6stems", ModelFamily::Htdemucs6Stems),
920            ("htdemucs-6stems", ModelFamily::Htdemucs6Stems),
921            ("htdemucs_6s", ModelFamily::Htdemucs6Stems),
922            ("htdemucs-6s", ModelFamily::Htdemucs6Stems),
923            ("htdemucs", ModelFamily::Htdemucs),
924            ("demucs", ModelFamily::Htdemucs),
925            ("tf_gridnet", ModelFamily::TfGridnet),
926            ("tf-gridnet", ModelFamily::TfGridnet),
927            ("tfgridnet", ModelFamily::TfGridnet),
928            ("gridnet", ModelFamily::TfGridnet),
929            ("mel-band-roformer", ModelFamily::MelBandRoformer),
930            ("mel_band_roformer", ModelFamily::MelBandRoformer),
931            ("bs-roformer", ModelFamily::BsRoformer),
932            ("bs_roformer", ModelFamily::BsRoformer),
933            ("muscriptor", ModelFamily::Muscriptor),
934            ("omnivoice", ModelFamily::Omnivoice),
935            ("stable_audio", ModelFamily::StableAudio),
936            ("stable-audio", ModelFamily::StableAudio),
937            ("minimax_music3", ModelFamily::MinimaxMusic3),
938            ("minimax-music3", ModelFamily::MinimaxMusic3),
939            ("supertonic", ModelFamily::Supertonic),
940            ("yue2", ModelFamily::Yue2),
941            ("magenta_realtime_2", ModelFamily::MagentaRealtime2),
942            ("magenta-realtime-2", ModelFamily::MagentaRealtime2),
943            ("magenta_rt2", ModelFamily::MagentaRealtime2),
944            ("magenta-rt2", ModelFamily::MagentaRealtime2),
945            ("magenta", ModelFamily::MagentaRealtime2),
946            ("yue-2", ModelFamily::Yue2),
947            ("sheetsage2", ModelFamily::Sheetsage2),
948            ("sheetsage-2", ModelFamily::Sheetsage2),
949            ("sheetsage", ModelFamily::Sheetsage2),
950            // voxtral_tts 必须排在裸 "voxtral" 之前,否则会被吞掉。
951            ("voxtral_tts", ModelFamily::VoxtralTts),
952            ("voxtral-tts", ModelFamily::VoxtralTts),
953            ("voxtral-realtime", ModelFamily::VoxtralRealtime),
954            ("voxtral", ModelFamily::VoxtralRealtime),
955            ("audiosr", ModelFamily::Audiosr),
956            ("audio-sr", ModelFamily::Audiosr),
957            ("apollo", ModelFamily::Apollo),
958            ("universr", ModelFamily::Universr),
959            ("univer-sr", ModelFamily::Universr),
960            ("controlfoley", ModelFamily::ControlFoley),
961            ("control-foley", ModelFamily::ControlFoley),
962            ("midashenglm_gen", ModelFamily::MidashEnglmGen),
963            ("midashenglm-gen", ModelFamily::MidashEnglmGen),
964            ("midashen", ModelFamily::MidashEnglmGen),
965            ("builtin_audio_utils", ModelFamily::BuiltinAudioUtils),
966            ("builtin-audio-utils", ModelFamily::BuiltinAudioUtils),
967            ("deepfilternet", ModelFamily::BuiltinAudioUtils),
968            ("rnnoise", ModelFamily::BuiltinAudioUtils),
969            ("zipenhancer", ModelFamily::BuiltinAudioUtils),
970            ("gtcrn", ModelFamily::BuiltinAudioUtils),
971            ("flashsr", ModelFamily::BuiltinAudioUtils),
972            ("miocodec", ModelFamily::Miocodec),
973            ("miotts", ModelFamily::Miotts),
974            ("dramabox", ModelFamily::Dramabox),
975            ("heartmula", ModelFamily::Heartmula),
976            ("inflect", ModelFamily::InflectV2),
977            ("mms_forced_aligner", ModelFamily::MmsForcedAligner),
978            ("mms", ModelFamily::MmsForcedAligner),
979            ("forced-aligner", ModelFamily::Qwen3ForcedAligner),
980            // 唤醒词 / 分类族:`ced` 是 `forced-aligner`(for**ced**)的子串,
981            // 必须排在它之后,否则对齐模型会被误判为 CED。
982            ("micro_wake_word", ModelFamily::MicroWakeWord),
983            ("micro-wake-word", ModelFamily::MicroWakeWord),
984            ("micro_wake", ModelFamily::MicroWakeWord),
985            ("micro-wake", ModelFamily::MicroWakeWord),
986            ("microwake", ModelFamily::MicroWakeWord),
987            ("sherpa_kws", ModelFamily::SherpaKws),
988            ("sherpa-kws", ModelFamily::SherpaKws),
989            ("sherpa", ModelFamily::SherpaKws),
990            // open_wake_word 无裸前缀冲突,紧随其它唤醒词族列出。
991            ("open_wake_word", ModelFamily::OpenWakeWord),
992            ("open-wake-word", ModelFamily::OpenWakeWord),
993            ("openwake", ModelFamily::OpenWakeWord),
994            ("ced", ModelFamily::Ced),
995            ("ast_audioset", ModelFamily::AstAudioset),
996            ("ast-audioset", ModelFamily::AstAudioset),
997            ("audioset", ModelFamily::AstAudioset),
998        ];
999        KEYWORDS
1000            .iter()
1001            .find(|(k, _)| lower.contains(k))
1002            .map(|(_, family)| family.clone())
1003    }
1004
1005    /// 转换为传给 C 边界的字符串。
1006    pub fn as_str(&self) -> &str {
1007        match self {
1008            ModelFamily::SileroVad => "silero_vad",
1009            ModelFamily::MarblenetVad => "marblenet_vad",
1010            ModelFamily::Pulsevad => "pulsevad",
1011            ModelFamily::FireredVad => "firered_vad",
1012            ModelFamily::EarshotVad => "earshot_vad",
1013            ModelFamily::Qwen3Asr => "qwen3_asr",
1014            ModelFamily::CitrinetAsr => "citrinet_asr",
1015            ModelFamily::SenseAsr => "sense_asr",
1016            ModelFamily::FunAsrNano => "fun_asr_nano",
1017            ModelFamily::HiggsAudioStt => "higgs_audio_stt",
1018            ModelFamily::HviskeAsr => "hviske_asr",
1019            ModelFamily::HviskeAsrV6 => "hviske_asr_v6",
1020            ModelFamily::KrokoAsr => "kroko_asr",
1021            ModelFamily::NemotronAsr => "nemotron_asr",
1022            ModelFamily::Nemotron3Diar => "nemotron_3_diar",
1023            ModelFamily::NemotronTranscribe => "nemotron_transcribe",
1024            ModelFamily::ParakeetTdt => "parakeet_tdt",
1025            ModelFamily::VibevoiceAsr => "vibevoice_asr",
1026            ModelFamily::VibevoiceAsrStreaming => "vibevoice_asr_streaming",
1027            ModelFamily::FireredAudio => "firered_audio",
1028            ModelFamily::Granite5Asr => "granite5asr",
1029            ModelFamily::Audio8Asr => "audio8_asr",
1030            ModelFamily::Audio8Tts => "audio8_tts",
1031            ModelFamily::Vibeasr => "vibeasr",
1032            ModelFamily::MoonshineAsr => "moonshine_asr",
1033            ModelFamily::NiagaraAsr => "niagara_asr",
1034            ModelFamily::CanaryAsr => "canary_asr",
1035            ModelFamily::CohereAsr => "cohere_asr",
1036            ModelFamily::MossTranscribeDiarize => "moss_transcribe_diarize",
1037            ModelFamily::Confucius4R2t2 => "confucius4_r2t2",
1038            ModelFamily::AudioFlamingo => "audio_flamingo",
1039            ModelFamily::Crisperwhisper => "crisperwhisper",
1040            ModelFamily::GigaamAsr => "gigaam_asr",
1041            ModelFamily::IndexEcho => "index_echo",
1042            ModelFamily::Lfm2Audio => "lfm2_audio",
1043            ModelFamily::Lfm3Audio => "lfm3_audio",
1044            ModelFamily::LfmD1Omni => "lfm_d1_omni",
1045            ModelFamily::Owsm => "owsm",
1046            ModelFamily::OwsmCtc => "owsm_ctc",
1047            ModelFamily::Samsone => "samsone",
1048            ModelFamily::WhistleAsr => "whistle_asr",
1049            ModelFamily::Phonon2Asr => "phonon2_asr",
1050            ModelFamily::UnderdogBark3 => "underdog_bark3",
1051            ModelFamily::HaasAsr => "haas_asr",
1052            ModelFamily::Qwen3Tts => "qwen3_tts",
1053            ModelFamily::Confucius4Tts => "confucius4_tts",
1054            ModelFamily::DotsTts => "dots_tts",
1055            ModelFamily::FishAudio => "fish_audio",
1056            ModelFamily::FishDrama3 => "fish_drama3",
1057            ModelFamily::GlmTts => "glm_tts",
1058            ModelFamily::HiggsAudioTts => "higgs_audio_tts",
1059            ModelFamily::IndexTts2 => "index_tts2",
1060            ModelFamily::IrodoriTts => "irodori_tts",
1061            ModelFamily::MossTtsLocal => "moss_tts_local",
1062            ModelFamily::MossTtsNano => "moss_tts_nano",
1063            ModelFamily::MossTtsV15 => "moss_tts_v15",
1064            ModelFamily::MossVoicegen => "moss_voicegen",
1065            ModelFamily::Neutts => "neutts",
1066            ModelFamily::Outetts => "outetts",
1067            ModelFamily::PocketTts => "pocket_tts",
1068            ModelFamily::GradiumPhonon => "gradium_phonon",
1069            ModelFamily::PocketTtsSmall => "pocket_tts_small",
1070            ModelFamily::VieneuV3Turbo => "vieneu_v3_turbo",
1071            ModelFamily::Fireredtts3 => "fireredtts3",
1072            ModelFamily::F5Tts => "f5_tts",
1073            ModelFamily::MagpieTts => "magpie_tts",
1074            ModelFamily::Personaplex => "personaplex",
1075            ModelFamily::SopranoTts => "soprano_tts",
1076            ModelFamily::EchoTts => "echo_tts",
1077            ModelFamily::Voxcpm1 => "voxcpm1",
1078            ModelFamily::KokoroTts => "kokoro_tts",
1079            ModelFamily::Zipvoice => "zipvoice",
1080            ModelFamily::Auk => "auk",
1081            ModelFamily::PiperTts => "piper_tts",
1082            ModelFamily::KittenTts => "kitten_tts",
1083            ModelFamily::KittenTts2 => "kitten_tts2",
1084            ModelFamily::Plapre => "plapre",
1085            ModelFamily::Kugelaudio => "kugelaudio",
1086            ModelFamily::Maya1 => "maya1",
1087            ModelFamily::MossTtsd => "moss_ttsd",
1088            ModelFamily::Ming3Tts => "ming3_tts",
1089            ModelFamily::XttsV2 => "xtts_v2",
1090            ModelFamily::MinimaxH3 => "minimax_h3",
1091            ModelFamily::Sanotts => "sanotts",
1092            ModelFamily::SoproTts => "sopro_tts",
1093            ModelFamily::MiraTts => "mira_tts",
1094            ModelFamily::Cosyvoice3 => "cosyvoice3",
1095            ModelFamily::BreezeTts => "breeze_tts",
1096            ModelFamily::SortformerDiar => "sortformer_diar",
1097            ModelFamily::SortformerDiarV2 => "sortformer_diar_v2",
1098            ModelFamily::SeedVc => "seed_vc",
1099            ModelFamily::Rvc => "rvc",
1100            ModelFamily::Meanvc2 => "meanvc2",
1101            ModelFamily::ToneColorVc => "tone_color_vc",
1102            ModelFamily::Chatterbox => "chatterbox",
1103            ModelFamily::ChatterboxTurbo => "chatterbox_turbo",
1104            ModelFamily::Vevo2 => "vevo2",
1105            ModelFamily::Voxcpm2 => "voxcpm2",
1106            ModelFamily::AceStep => "ace_step",
1107            ModelFamily::Htdemucs => "htdemucs",
1108            ModelFamily::Htdemucs6Stems => "htdemucs_6stems",
1109            ModelFamily::Mossformer2 => "mossformer2",
1110            ModelFamily::TfGridnet => "tf_gridnet",
1111            ModelFamily::MelBandRoformer => "mel_band_roformer",
1112            ModelFamily::BsRoformer => "bs_roformer",
1113            ModelFamily::Muscriptor => "muscriptor",
1114            ModelFamily::Omnivoice => "omnivoice",
1115            ModelFamily::StableAudio => "stable_audio",
1116            ModelFamily::MinimaxMusic3 => "minimax_music3",
1117            ModelFamily::Supertonic => "supertonic",
1118            ModelFamily::Yue2 => "yue2",
1119            ModelFamily::MagentaRealtime2 => "magenta_realtime_2",
1120            ModelFamily::Sheetsage2 => "sheetsage2",
1121            ModelFamily::VoxtralRealtime => "voxtral_realtime",
1122            ModelFamily::VoxtralTts => "voxtral_tts",
1123            ModelFamily::Audiosr => "audiosr",
1124            ModelFamily::Apollo => "apollo",
1125            ModelFamily::Universr => "universr",
1126            ModelFamily::BuiltinAudioUtils => "builtin_audio_utils",
1127            ModelFamily::ControlFoley => "controlfoley",
1128            ModelFamily::MidashEnglmGen => "midashenglm_gen",
1129            ModelFamily::Liveavatar => "liveavatar",
1130            ModelFamily::Latentsync => "latentsync",
1131            ModelFamily::SoulxFlashhead => "soulx_flashhead",
1132            ModelFamily::Reuse => "reuse",
1133            ModelFamily::RealtimeReuse => "realtime_reuse",
1134            ModelFamily::SamAudio => "sam_audio",
1135            ModelFamily::Sidon => "sidon",
1136            ModelFamily::SmartTurn => "smart_turn",
1137            ModelFamily::MicroWakeWord => "micro_wake_word",
1138            ModelFamily::SherpaKws => "sherpa_kws",
1139            ModelFamily::OpenWakeWord => "open_wake_word",
1140            ModelFamily::Ced => "ced",
1141            ModelFamily::AstAudioset => "ast_audioset",
1142            ModelFamily::Miocodec => "miocodec",
1143            ModelFamily::Miotts => "miotts",
1144            ModelFamily::Vibevoice => "vibevoice",
1145            ModelFamily::Dramabox => "dramabox",
1146            ModelFamily::Heartmula => "heartmula",
1147            ModelFamily::InflectV2 => "inflect_v2",
1148            ModelFamily::Qwen3ForcedAligner => "qwen3_forced_aligner",
1149            ModelFamily::MmsForcedAligner => "mms_forced_aligner",
1150            ModelFamily::Ming3Audio => "ming3_audio",
1151            ModelFamily::Custom(s) => s,
1152        }
1153    }
1154}
1155
1156impl From<&str> for ModelFamily {
1157    /// 从字符串构造:收录的名字映射到对应变体,未收录的落入 `Custom`。
1158    fn from(s: &str) -> Self {
1159        match s {
1160            "silero_vad" => ModelFamily::SileroVad,
1161            "marblenet_vad" => ModelFamily::MarblenetVad,
1162            "pulsevad" => ModelFamily::Pulsevad,
1163            "firered_vad" => ModelFamily::FireredVad,
1164            "firered-vad" => ModelFamily::FireredVad,
1165            "earshot_vad" => ModelFamily::EarshotVad,
1166            "earshot-vad" => ModelFamily::EarshotVad,
1167            // 上游 loader 声明的家族别名(family_aliases)。
1168            "earshot" => ModelFamily::EarshotVad,
1169            "qwen3_asr" => ModelFamily::Qwen3Asr,
1170            "citrinet_asr" => ModelFamily::CitrinetAsr,
1171            "sense_asr" => ModelFamily::SenseAsr,
1172            "fun_asr_nano" => ModelFamily::FunAsrNano,
1173            "higgs_audio_stt" => ModelFamily::HiggsAudioStt,
1174            "hviske_asr" => ModelFamily::HviskeAsr,
1175            "hviske_asr_v6" => ModelFamily::HviskeAsrV6,
1176            "hviske-asr-v6" => ModelFamily::HviskeAsrV6,
1177            "kroko_asr" => ModelFamily::KrokoAsr,
1178            "nemotron_asr" => ModelFamily::NemotronAsr,
1179            "nemotron_3_diar" => ModelFamily::Nemotron3Diar,
1180            "nemotron_transcribe" => ModelFamily::NemotronTranscribe,
1181            "nemotron-transcribe" => ModelFamily::NemotronTranscribe,
1182            "parakeet_tdt" => ModelFamily::ParakeetTdt,
1183            "vibevoice_asr" => ModelFamily::VibevoiceAsr,
1184            "vibevoice_asr_streaming" => ModelFamily::VibevoiceAsrStreaming,
1185            "firered_audio" => ModelFamily::FireredAudio,
1186            "granite5asr" => ModelFamily::Granite5Asr,
1187            "granite_speech5_asr" => ModelFamily::Granite5Asr,
1188            "granite_speech" => ModelFamily::Granite5Asr,
1189            "granite_speech5_ctc" => ModelFamily::Granite5Asr,
1190            "audio8_asr" => ModelFamily::Audio8Asr,
1191            "arkasr" => ModelFamily::Audio8Asr,
1192            "audio8_tts" => ModelFamily::Audio8Tts,
1193            "vibeasr" => ModelFamily::Vibeasr,
1194            "moonshine_asr" => ModelFamily::MoonshineAsr,
1195            "niagara_asr" => ModelFamily::NiagaraAsr,
1196            "canary_asr" => ModelFamily::CanaryAsr,
1197            "cohere_asr" => ModelFamily::CohereAsr,
1198            "moss_transcribe_diarize" => ModelFamily::MossTranscribeDiarize,
1199            "confucius4_r2t2" => ModelFamily::Confucius4R2t2,
1200            "audio_flamingo" => ModelFamily::AudioFlamingo,
1201            "crisperwhisper" => ModelFamily::Crisperwhisper,
1202            "gigaam_asr" => ModelFamily::GigaamAsr,
1203            "index_echo" => ModelFamily::IndexEcho,
1204            "lfm2_audio" => ModelFamily::Lfm2Audio,
1205            "lfm3_audio" => ModelFamily::Lfm3Audio,
1206            "lfm3-audio" => ModelFamily::Lfm3Audio,
1207            "lfm_d1_omni" => ModelFamily::LfmD1Omni,
1208            "lfm-d1-omni" => ModelFamily::LfmD1Omni,
1209            "owsm" => ModelFamily::Owsm,
1210            "owsm_ctc" => ModelFamily::OwsmCtc,
1211            "samsone" => ModelFamily::Samsone,
1212            "whistle_asr" => ModelFamily::WhistleAsr,
1213            "whistle-asr" => ModelFamily::WhistleAsr,
1214            "phonon2_asr" => ModelFamily::Phonon2Asr,
1215            "phonon2-asr" => ModelFamily::Phonon2Asr,
1216            "underdog_bark3" => ModelFamily::UnderdogBark3,
1217            "underdog-bark3" => ModelFamily::UnderdogBark3,
1218            "haas_asr" => ModelFamily::HaasAsr,
1219            "haas-asr" => ModelFamily::HaasAsr,
1220            "qwen3_tts" => ModelFamily::Qwen3Tts,
1221            "confucius4_tts" => ModelFamily::Confucius4Tts,
1222            "dots_tts" => ModelFamily::DotsTts,
1223            "fish_audio" => ModelFamily::FishAudio,
1224            "fish_drama3" => ModelFamily::FishDrama3,
1225            "fish-drama3" => ModelFamily::FishDrama3,
1226            "glm_tts" => ModelFamily::GlmTts,
1227            "higgs_audio_tts" => ModelFamily::HiggsAudioTts,
1228            "index_tts2" => ModelFamily::IndexTts2,
1229            "irodori_tts" => ModelFamily::IrodoriTts,
1230            "moss_tts_local" => ModelFamily::MossTtsLocal,
1231            "moss_tts_nano" => ModelFamily::MossTtsNano,
1232            "moss_tts_v15" => ModelFamily::MossTtsV15,
1233            "moss_voicegen" => ModelFamily::MossVoicegen,
1234            "neutts" => ModelFamily::Neutts,
1235            "outetts" => ModelFamily::Outetts,
1236            "pocket_tts" => ModelFamily::PocketTts,
1237            "pocket_tts_small" => ModelFamily::PocketTtsSmall,
1238            "pocket-tts-small" => ModelFamily::PocketTtsSmall,
1239            "gradium_phonon" => ModelFamily::GradiumPhonon,
1240            "gradium-phonon" => ModelFamily::GradiumPhonon,
1241            "gradium" => ModelFamily::GradiumPhonon,
1242            "vietneu_tts" => ModelFamily::VieneuV3Turbo,
1243            "vieneu_v3_turbo" => ModelFamily::VieneuV3Turbo,
1244            "fireredtts3" => ModelFamily::Fireredtts3,
1245            "f5_tts" => ModelFamily::F5Tts,
1246            "magpie_tts" => ModelFamily::MagpieTts,
1247            "personaplex" => ModelFamily::Personaplex,
1248            "soprano_tts" => ModelFamily::SopranoTts,
1249            "echo_tts" => ModelFamily::EchoTts,
1250            "voxcpm1" => ModelFamily::Voxcpm1,
1251            "kokoro_tts" => ModelFamily::KokoroTts,
1252            "zipvoice" => ModelFamily::Zipvoice,
1253            "auk" => ModelFamily::Auk,
1254            "piper_tts" => ModelFamily::PiperTts,
1255            "kitten_tts" => ModelFamily::KittenTts,
1256            "kitten_tts2" => ModelFamily::KittenTts2,
1257            "plapre" => ModelFamily::Plapre,
1258            "plapre_3" | "plapre-3" => ModelFamily::Plapre,
1259            "kugelaudio" => ModelFamily::Kugelaudio,
1260            "maya1" => ModelFamily::Maya1,
1261            "moss_ttsd" => ModelFamily::MossTtsd,
1262            "ming3_tts" => ModelFamily::Ming3Tts,
1263            "ming3-tts" => ModelFamily::Ming3Tts,
1264            "ming3_audio" => ModelFamily::Ming3Audio,
1265            "ming3-audio" => ModelFamily::Ming3Audio,
1266            "xtts_v2" => ModelFamily::XttsV2,
1267            "xtts-v2" => ModelFamily::XttsV2,
1268            "xtts" => ModelFamily::XttsV2,
1269            "minimax_h3" => ModelFamily::MinimaxH3,
1270            "sanotts" => ModelFamily::Sanotts,
1271            "sopro_tts" => ModelFamily::SoproTts,
1272            "sopro" => ModelFamily::SoproTts,
1273            "sopro_v2" => ModelFamily::SoproTts,
1274            "sopro_v2_turbo" => ModelFamily::SoproTts,
1275            "mira_tts" => ModelFamily::MiraTts,
1276            "miratts" => ModelFamily::MiraTts,
1277            "mira" => ModelFamily::MiraTts,
1278            "MiraTTS" => ModelFamily::MiraTts,
1279            "cosyvoice3" => ModelFamily::Cosyvoice3,
1280            "breeze_tts" => ModelFamily::BreezeTts,
1281            "sortformer_diar" => ModelFamily::SortformerDiar,
1282            "sortformer_diar_v2" => ModelFamily::SortformerDiarV2,
1283            "seed_vc" => ModelFamily::SeedVc,
1284            "rvc" => ModelFamily::Rvc,
1285            "meanvc2" => ModelFamily::Meanvc2,
1286            "tone_color_vc" => ModelFamily::ToneColorVc,
1287            "chatterbox" => ModelFamily::Chatterbox,
1288            "chatterbox_turbo" => ModelFamily::ChatterboxTurbo,
1289            "vevo2" => ModelFamily::Vevo2,
1290            "voxcpm2" => ModelFamily::Voxcpm2,
1291            "ace_step" => ModelFamily::AceStep,
1292            "htdemucs" => ModelFamily::Htdemucs,
1293            "htdemucs_6stems" => ModelFamily::Htdemucs6Stems,
1294            "htdemucs_6s" => ModelFamily::Htdemucs6Stems,
1295            "mossformer2" => ModelFamily::Mossformer2,
1296            "tf_gridnet" => ModelFamily::TfGridnet,
1297            "tf-gridnet" => ModelFamily::TfGridnet,
1298            "mel_band_roformer" => ModelFamily::MelBandRoformer,
1299            "bs_roformer" => ModelFamily::BsRoformer,
1300            "muscriptor" => ModelFamily::Muscriptor,
1301            "omnivoice" => ModelFamily::Omnivoice,
1302            "stable_audio" => ModelFamily::StableAudio,
1303            "minimax_music3" => ModelFamily::MinimaxMusic3,
1304            "supertonic" => ModelFamily::Supertonic,
1305            "yue2" => ModelFamily::Yue2,
1306            "magenta_realtime_2" => ModelFamily::MagentaRealtime2,
1307            "magenta-realtime-2" => ModelFamily::MagentaRealtime2,
1308            "sheetsage2" => ModelFamily::Sheetsage2,
1309            "voxtral_realtime" => ModelFamily::VoxtralRealtime,
1310            "voxtral_tts" => ModelFamily::VoxtralTts,
1311            "voxtral-tts" => ModelFamily::VoxtralTts,
1312            "audiosr" => ModelFamily::Audiosr,
1313            "apollo" => ModelFamily::Apollo,
1314            "universr" => ModelFamily::Universr,
1315            "builtin_audio_utils" => ModelFamily::BuiltinAudioUtils,
1316            "controlfoley" => ModelFamily::ControlFoley,
1317            "midashenglm_gen" => ModelFamily::MidashEnglmGen,
1318            "liveavatar" => ModelFamily::Liveavatar,
1319            "latentsync" => ModelFamily::Latentsync,
1320            "latent-sync" => ModelFamily::Latentsync,
1321            "soulx_flashhead" => ModelFamily::SoulxFlashhead,
1322            "soulx-flashhead" => ModelFamily::SoulxFlashhead,
1323            "reuse" => ModelFamily::Reuse,
1324            "realtime_reuse" => ModelFamily::RealtimeReuse,
1325            "realtime-reuse" => ModelFamily::RealtimeReuse,
1326            "sam_audio" => ModelFamily::SamAudio,
1327            "sidon" => ModelFamily::Sidon,
1328            "smart_turn" => ModelFamily::SmartTurn,
1329            "micro_wake_word" => ModelFamily::MicroWakeWord,
1330            "micro-wake-word" => ModelFamily::MicroWakeWord,
1331            "sherpa_kws" => ModelFamily::SherpaKws,
1332            "sherpa-kws" => ModelFamily::SherpaKws,
1333            "open_wake_word" => ModelFamily::OpenWakeWord,
1334            "open-wake-word" => ModelFamily::OpenWakeWord,
1335            "ced" => ModelFamily::Ced,
1336            "ast_audioset" => ModelFamily::AstAudioset,
1337            "ast-audioset" => ModelFamily::AstAudioset,
1338            "miocodec" => ModelFamily::Miocodec,
1339            "miotts" => ModelFamily::Miotts,
1340            "vibevoice" => ModelFamily::Vibevoice,
1341            "dramabox" => ModelFamily::Dramabox,
1342            "heartmula" => ModelFamily::Heartmula,
1343            "inflect_v2" => ModelFamily::InflectV2,
1344            "qwen3_forced_aligner" => ModelFamily::Qwen3ForcedAligner,
1345            "mms_forced_aligner" => ModelFamily::MmsForcedAligner,
1346            other => ModelFamily::Custom(other.to_owned()),
1347        }
1348    }
1349}
1350
1351impl From<String> for ModelFamily {
1352    fn from(s: String) -> Self {
1353        ModelFamily::from(s.as_str())
1354    }
1355}
1356
1357impl AsRef<str> for ModelFamily {
1358    fn as_ref(&self) -> &str {
1359        self.as_str()
1360    }
1361}
1362
1363impl std::fmt::Display for ModelFamily {
1364    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1365        f.write_str(self.as_str())
1366    }
1367}
1368
1369/// 计算后端(映射 `audiocpp_model_create_task_session` 的 `backend` 参数)。
1370#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1371#[serde(rename_all = "lowercase")]
1372pub enum Backend {
1373    /// CPU
1374    Cpu,
1375    /// CUDA
1376    Cuda,
1377    /// HIP / ROCm
1378    Hip,
1379    /// Vulkan
1380    Vulkan,
1381    /// Metal
1382    Metal,
1383    /// 自动选择最佳可用后端
1384    Best,
1385}
1386
1387impl Backend {
1388    /// 转换为传给 C 边界的字符串。
1389    pub fn as_str(self) -> &'static str {
1390        match self {
1391            Backend::Cpu => "cpu",
1392            Backend::Cuda => "cuda",
1393            Backend::Hip => "hip",
1394            Backend::Vulkan => "vulkan",
1395            Backend::Metal => "metal",
1396            Backend::Best => "best",
1397        }
1398    }
1399}
1400
1401/// 计算设备(`audiocpp_registry_devices_json` 的一项)。
1402#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1403pub struct Device {
1404    /// 后端名,如 `"CPU"`。
1405    pub backend: String,
1406    /// 设备序号。
1407    pub index: i32,
1408    /// 设备名。
1409    pub name: String,
1410    /// 设备类型,如 `"CPU"`。
1411    #[serde(rename = "type")]
1412    pub kind: String,
1413}
1414
1415/// 模型族 loader 的声明信息(`audiocpp_registry_loaders_json` 的一项)。
1416#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1417pub struct LoaderInfo {
1418    /// 模型族名。
1419    pub family: String,
1420    /// 能力集合。
1421    pub capabilities: Capabilities,
1422    /// 指令策略描述。
1423    pub instructions_policy: String,
1424    /// 可用的 API 端点。
1425    pub api_endpoints: Vec<String>,
1426}
1427
1428/// 能力集合(`audiocpp_model_capabilities_json` / loader 声明)。
1429#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1430pub struct Capabilities {
1431    /// 支持的任务及其模式。
1432    pub supported_tasks: Vec<SupportedTask>,
1433    /// 支持的语言。
1434    pub languages: Vec<String>,
1435    /// 是否支持说话人参考音频。
1436    pub supports_speaker_reference: bool,
1437    /// 是否支持风格条件。
1438    pub supports_style_condition: bool,
1439    /// 是否支持时间戳。
1440    pub supports_timestamps: bool,
1441}
1442
1443/// 一个受支持的任务及其可用模式。
1444#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1445pub struct SupportedTask {
1446    /// 任务名,如 `"vad"`。
1447    pub task: String,
1448    /// 可用模式,如 `["offline","streaming"]`。
1449    pub modes: Vec<String>,
1450}
1451
1452/// 已加载模型的元数据(`audiocpp_model_metadata_json`)。
1453#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1454pub struct ModelMetadata {
1455    /// 模型族。
1456    pub family: String,
1457    /// 变体。
1458    pub variant: String,
1459    /// 描述。
1460    pub description: String,
1461    /// 候选配置。
1462    pub config_candidates: Vec<String>,
1463    /// 候选权重。
1464    pub weight_candidates: Vec<String>,
1465}
1466
1467/// 一段音频在采样轴上的时间范围。
1468#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1469pub struct TimeSpan {
1470    /// 起始采样点(含)。
1471    pub start_sample: i64,
1472    /// 结束采样点(不含)。
1473    pub end_sample: i64,
1474}
1475
1476/// 语音活动片段。
1477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1478pub struct SpeechSegment {
1479    /// 时间范围。
1480    pub span: TimeSpan,
1481    /// 置信度(0..1)。
1482    pub confidence: f32,
1483    /// 附带文本(如 ASR 片段)。
1484    pub text: String,
1485}
1486
1487/// 说话人分离中的一段发言(谁在什么时间说话)。
1488#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1489pub struct SpeakerTurn {
1490    /// 说话人 id,如 `"speaker_0"`。
1491    pub speaker_id: String,
1492    /// 发言时间范围。
1493    pub span: TimeSpan,
1494    /// 置信度(0..1)。
1495    pub confidence: f32,
1496    /// 该段文本(部分模型产出,如带转写的说话人分离)。
1497    #[serde(default)]
1498    pub text: String,
1499}
1500
1501/// 词级时间戳(ASR 逐词对齐)。
1502#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1503pub struct WordTimestamp {
1504    /// 时间范围(采样点)。
1505    pub span: TimeSpan,
1506    /// 词文本。
1507    pub word: String,
1508    /// 置信度(0..1)。
1509    pub confidence: f32,
1510}
1511
1512/// 模型产出的产物(embedding / 状态 / 对齐结果等)。
1513#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1514pub struct VoiceArtifact {
1515    /// 产物种类(如 `speaker_embedding` / `transcript_alignment`)。
1516    pub kind: String,
1517    /// 产物 id。
1518    pub id: String,
1519    /// 二进制载荷的 base64 编码(无载荷时为空字符串)。
1520    #[serde(default)]
1521    pub payload_base64: String,
1522    /// 附加元数据。
1523    #[serde(default)]
1524    pub meta: BTreeMap<String, String>,
1525}
1526
1527/// CLI 选项信息(模型支持的请求 / 会话 / 加载选项)。
1528#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1529pub struct CliOption {
1530    /// 选项名。
1531    pub name: String,
1532    /// 值名称(帮助文本)。
1533    pub value_name: String,
1534    /// 描述。
1535    pub description: String,
1536    /// 是否必填。
1537    pub required: bool,
1538    /// 默认值(可选)。
1539    #[serde(default, skip_serializing_if = "Option::is_none")]
1540    pub default_value: Option<String>,
1541    /// 最小值(可选,字符串形式)。
1542    #[serde(default, skip_serializing_if = "Option::is_none")]
1543    pub min_value: Option<String>,
1544    /// 最大值(可选,字符串形式)。
1545    #[serde(default, skip_serializing_if = "Option::is_none")]
1546    pub max_value: Option<String>,
1547}
1548
1549/// 模型预检时发现的配置 / 权重资产。
1550#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1551pub struct NamedAsset {
1552    /// 资产 id。
1553    pub id: String,
1554    /// 资产路径。
1555    pub path: String,
1556}
1557
1558/// 模型的三类 CLI 选项(请求 / 会话 / 加载)。
1559#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1560pub struct CliInterface {
1561    /// 请求级选项(如 `audio_chunk_seconds`)。
1562    #[serde(default)]
1563    pub request_options: Vec<CliOption>,
1564    /// 会话级选项。
1565    #[serde(default)]
1566    pub session_options: Vec<CliOption>,
1567    /// 加载级选项(如 `weight_id`)。
1568    #[serde(default)]
1569    pub load_options: Vec<CliOption>,
1570}
1571
1572/// 模型预检结果(`Registry::inspect`)。
1573#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1574pub struct ModelInspection {
1575    /// 模型元数据。
1576    pub metadata: ModelMetadata,
1577    /// 能力集合。
1578    pub capabilities: Capabilities,
1579    /// CLI 选项(请求 / 会话 / 加载)。
1580    pub cli: CliInterface,
1581    /// 发现的配置文件。
1582    #[serde(default)]
1583    pub discovered_configs: Vec<NamedAsset>,
1584    /// 发现的权重文件。
1585    #[serde(default)]
1586    pub discovered_weights: Vec<NamedAsset>,
1587    /// 模型根目录。
1588    #[serde(default)]
1589    pub model_root: String,
1590}
1591
1592/// 文本输出(如 ASR / TTS 的文本)。
1593#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1594pub struct TextOutput {
1595    /// 文本内容。
1596    pub text: String,
1597    /// 语言。
1598    pub language: String,
1599}
1600
1601/// 音频输出的描述信息(采样率 / 声道 / 长度)。
1602#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1603pub struct AudioBufferInfo {
1604    /// 采样率。
1605    pub sample_rate: i32,
1606    /// 声道数。
1607    pub channels: i32,
1608    /// 采样数。
1609    pub sample_count: usize,
1610    /// 实际采样数据(f32,-1..1)。仅当生成音频需要回传时存在
1611    /// (如 TTS 的 `audio_output`);VAD / ASR 通常不携带。
1612    #[serde(default)]
1613    pub samples: Option<Vec<f32>>,
1614}
1615
1616/// 带名字的音频输出(如 TTS 的多段输出)。
1617#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1618pub struct NamedAudioOutput {
1619    /// 输出 id。
1620    pub id: String,
1621    /// 音频描述。
1622    pub audio: AudioBufferInfo,
1623    /// 附加元数据。
1624    pub meta: BTreeMap<String, String>,
1625}
1626
1627/// 自定义结构化输出(上游 `TaskResult::custom_schema_output`)。
1628///
1629/// 目前有三类产出:Smart Turn(task `turn`)的 `"smart_turn.v1"`
1630///(`data` 为 `{"complete": bool, "probability": number}`,`threshold`
1631/// 请求选项判定,默认 0.5);CED(task `cls`)的 `"ced.classification.v1"`;
1632/// AST AudioSet(task `cls`)的 `"ast_audioset.classification.v1"`。
1633/// 后两者的 `data` 为 `{"scores": [...]}`(527 个 AudioSet 类别得分)。
1634/// `data` 为任意 JSON,用 `serde_json::Value` 承载。
1635#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1636pub struct CustomSchemaOutput {
1637    /// 输出 schema 标识,如 `"smart_turn.v1"`。
1638    pub schema: String,
1639    /// 结构化数据载荷。
1640    pub data: serde_json::Value,
1641}
1642
1643/// 一次任务执行的完整结果(`audiocpp_session_run_offline` / `finish`)。
1644#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1645pub struct TaskResult {
1646    /// 检测到的语音片段。
1647    pub speech_segments: Vec<SpeechSegment>,
1648    /// 说话人分离的发言分段(存在时)。
1649    pub speaker_turns: Vec<SpeakerTurn>,
1650    /// 文本输出(存在时)。
1651    pub text_output: Option<TextOutput>,
1652    /// 音频输出(存在时)。
1653    pub audio_output: Option<AudioBufferInfo>,
1654    /// 命名音频输出列表。
1655    pub named_audio_outputs: Vec<NamedAudioOutput>,
1656    /// 词级时间戳(存在时)。
1657    ///
1658    /// 是否输出按模型族而定,空数组只表示"本次无词戳",先查选项再报缺口:
1659    /// - Qwen3 ASR:默认**不**输出;需请求选项 `return_timestamps=true`(见
1660    ///   [`crate::options::request::RETURN_TIMESTAMPS`]),且建会话时经会话选项
1661    ///   配好对齐模型(见
1662    ///   [`crate::options::session::QWEN3_ASR_FORCED_ALIGNER_MODEL_PATH`]),
1663    ///   上游用对齐器内部回填。流式模式不支持词戳(直接抛错)。
1664    /// - 强制对齐族(`qwen3_forced_aligner` / `mms_forced_aligner`,task
1665    ///   `align`)直接输出;ASR 主文本 + 对齐回填是无词戳族的常规降级链。
1666    #[serde(default)]
1667    pub word_timestamps: Vec<WordTimestamp>,
1668    /// 自定义结构化输出(存在时,如 Smart Turn 的 `smart_turn.v1` 轮次判定)。
1669    #[serde(default)]
1670    pub custom_schema_output: Option<CustomSchemaOutput>,
1671    /// 单个产物输出(存在时,如 voice-clone 的 cached_voice_id)。
1672    #[serde(default)]
1673    pub artifact_output: Option<VoiceArtifact>,
1674    /// 多个产物输出(存在时)。
1675    #[serde(default)]
1676    pub output_artifacts: Vec<VoiceArtifact>,
1677}
1678
1679/// 流式会话产出的单个事件。
1680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1681pub struct StreamEvent {
1682    /// 语音活动事件列表。
1683    pub voice_activity: Vec<VoiceActivityEvent>,
1684    /// 部分文本(流式 ASR 等)。
1685    pub partial_text: Option<TextOutput>,
1686    /// 可修正的整段转写假设(上游 `StreamEvent::partial_text_snapshot`):
1687    /// 客户端用它替换暂存显示;`partial_text` 仍是仅追加的已确认文本。
1688    /// 仅部分模型按需输出(如 parakeet 系热词流式),缺省为 `None`。
1689    #[serde(default)]
1690    pub partial_text_snapshot: Option<TextOutput>,
1691    /// 音频输出(存在时)。
1692    pub audio_output: Option<AudioBufferInfo>,
1693    /// 命名音频输出列表(如流式 TTS 的逐块 `chunk_N`)。
1694    #[serde(default)]
1695    pub named_audio_outputs: Vec<NamedAudioOutput>,
1696    /// 说话人分段(流式说话人分离等,存在时)。
1697    #[serde(default)]
1698    pub speaker_turns: Vec<SpeakerTurn>,
1699    /// 词级时间戳(仅产出词戳的族才有;Qwen3 ASR 流式不支持词戳,
1700    /// 见 [`TaskResult::word_timestamps`])。
1701    #[serde(default)]
1702    pub word_timestamps: Vec<WordTimestamp>,
1703    /// 产物输出(流式产出 embedding / 状态等,存在时)。
1704    #[serde(default)]
1705    pub output_artifacts: Vec<VoiceArtifact>,
1706    /// 是否为最终事件。
1707    pub is_final: bool,
1708}
1709
1710/// 语音活动事件(`speech_start` / `speech_end` / `speech_segment`)。
1711#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1712pub struct VoiceActivityEvent {
1713    /// 事件类型。
1714    pub kind: String,
1715    /// 触发位置(采样点)。
1716    pub sample: i64,
1717    /// 语音概率。
1718    pub probability: f32,
1719    /// 片段(`speech_segment` 事件携带)。
1720    pub segment: Option<SpeechSegment>,
1721}
1722
1723/// 流式策略描述(`audiocpp_session_streaming_policy_json`)。
1724#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1725pub struct StreamingPolicy {
1726    /// 输入类型,如 `"audio_chunks"`。
1727    pub input: String,
1728    /// 输出类型,如 `"pull_events"` / `"final_result"`。
1729    pub output: String,
1730    /// 推荐的音频分块采样数。
1731    pub preferred_audio_chunk_samples: usize,
1732    /// 推荐的音频分块秒数。
1733    pub preferred_audio_chunk_seconds: f64,
1734}
1735
1736#[cfg(test)]
1737mod tests {
1738    // 测试断言中的 unwrap/expect 是惯用法:失败即测试失败,展开错误链无意义。
1739    #![allow(clippy::unwrap_used, clippy::expect_used)]
1740    use super::*;
1741
1742    /// 所有非 Custom 变体的 `as_str()` 必须能被 `From<&str>` 精确回收到
1743    /// 原变体(而不是落入 `Custom`),且 `as_str` 与 `From` 两张 match
1744    /// 表完全一致——防止新增模型族时漏改其一。
1745    #[test]
1746    fn model_family_roundtrip() {
1747        let mut variants: Vec<ModelFamily> = Vec::new();
1748        macro_rules! push {
1749            ($($v:ident),* $(,)?) => {
1750                $(variants.push(ModelFamily::$v);)*
1751            };
1752        }
1753        push![
1754            // VAD
1755            SileroVad,
1756            MarblenetVad,
1757            Pulsevad,
1758            FireredVad,
1759            EarshotVad,
1760            // ASR
1761            Qwen3Asr,
1762            CitrinetAsr,
1763            SenseAsr,
1764            FunAsrNano,
1765            HiggsAudioStt,
1766            HviskeAsr,
1767            HviskeAsrV6,
1768            KrokoAsr,
1769            NemotronAsr,
1770            Nemotron3Diar,
1771            NemotronTranscribe,
1772            ParakeetTdt,
1773            VibevoiceAsr,
1774            VibevoiceAsrStreaming,
1775            FireredAudio,
1776            Granite5Asr,
1777            Audio8Asr,
1778            Audio8Tts,
1779            Vibeasr,
1780            MoonshineAsr,
1781            NiagaraAsr,
1782            CanaryAsr,
1783            CohereAsr,
1784            MossTranscribeDiarize,
1785            Confucius4R2t2,
1786            AudioFlamingo,
1787            Crisperwhisper,
1788            GigaamAsr,
1789            IndexEcho,
1790            Lfm2Audio,
1791            Lfm3Audio,
1792            LfmD1Omni,
1793            Owsm,
1794            OwsmCtc,
1795            Samsone,
1796            WhistleAsr,
1797            Phonon2Asr,
1798            UnderdogBark3,
1799            HaasAsr,
1800            // TTS
1801            Qwen3Tts,
1802            Confucius4Tts,
1803            DotsTts,
1804            FishAudio,
1805            FishDrama3,
1806            GlmTts,
1807            HiggsAudioTts,
1808            IndexTts2,
1809            IrodoriTts,
1810            MossTtsLocal,
1811            MossTtsNano,
1812            MossTtsV15,
1813            MossVoicegen,
1814            Neutts,
1815            Outetts,
1816            PocketTts,
1817            PocketTtsSmall,
1818            GradiumPhonon,
1819            VieneuV3Turbo,
1820            Fireredtts3,
1821            F5Tts,
1822            MagpieTts,
1823            Personaplex,
1824            SopranoTts,
1825            EchoTts,
1826            Voxcpm1,
1827            KokoroTts,
1828            Zipvoice,
1829            Auk,
1830            PiperTts,
1831            KittenTts,
1832            KittenTts2,
1833            Plapre,
1834            Kugelaudio,
1835            Maya1,
1836            MossTtsd,
1837            Ming3Tts,
1838            XttsV2,
1839            MinimaxH3,
1840            Sanotts,
1841            SoproTts,
1842            MiraTts,
1843            Cosyvoice3,
1844            BreezeTts,
1845            // 分离 / 转换 / 音乐
1846            SortformerDiar,
1847            SortformerDiarV2,
1848            SeedVc,
1849            Rvc,
1850            Meanvc2,
1851            ToneColorVc,
1852            Chatterbox,
1853            ChatterboxTurbo,
1854            Vevo2,
1855            Voxcpm2,
1856            AceStep,
1857            Htdemucs,
1858            Htdemucs6Stems,
1859            Mossformer2,
1860            TfGridnet,
1861            MelBandRoformer,
1862            BsRoformer,
1863            Muscriptor,
1864            Omnivoice,
1865            StableAudio,
1866            MinimaxMusic3,
1867            Supertonic,
1868            Yue2,
1869            MagentaRealtime2,
1870            Sheetsage2,
1871            VoxtralRealtime,
1872            VoxtralTts,
1873            Audiosr,
1874            Apollo,
1875            Universr,
1876            BuiltinAudioUtils,
1877            ControlFoley,
1878            MidashEnglmGen,
1879            Liveavatar,
1880            Latentsync,
1881            SoulxFlashhead,
1882            Reuse,
1883            RealtimeReuse,
1884            SamAudio,
1885            Sidon,
1886            SmartTurn,
1887            // 唤醒词 / 音频分类
1888            MicroWakeWord,
1889            SherpaKws,
1890            OpenWakeWord,
1891            Ced,
1892            AstAudioset,
1893            // 其他
1894            Miocodec,
1895            Miotts,
1896            Vibevoice,
1897            Dramabox,
1898            Heartmula,
1899            InflectV2,
1900            Qwen3ForcedAligner,
1901            MmsForcedAligner,
1902            Ming3Audio,
1903        ];
1904        assert!(
1905            variants.len() >= 40,
1906            "ModelFamily 应至少覆盖上游全部 40+ loader 族,当前 {}",
1907            variants.len()
1908        );
1909        for v in variants {
1910            let name = v.as_str();
1911            assert_eq!(
1912                ModelFamily::from(name),
1913                v,
1914                "as_str() 与 From<&str> 不一致:{}",
1915                name
1916            );
1917            // Display / AsRef 应等价于 as_str。
1918            assert_eq!(format!("{v}"), name);
1919            assert_eq!(v.as_ref(), name);
1920        }
1921    }
1922
1923    #[test]
1924    fn model_family_custom_fallback() {
1925        // 未收录的名字落入 Custom 并原样保留。
1926        let m = ModelFamily::from("brand_new_family");
1927        match &m {
1928            ModelFamily::Custom(s) => assert_eq!(s, "brand_new_family"),
1929            other => panic!("未收录名字应落入 Custom,得到 {other:?}"),
1930        }
1931        assert_eq!(m.as_str(), "brand_new_family");
1932        // Custom 值本身不再被 From 回收。
1933        assert_eq!(
1934            ModelFamily::from("brand_new_family"),
1935            ModelFamily::Custom("brand_new_family".to_owned())
1936        );
1937        // From<String> 与 AsRef 一致。
1938        assert_eq!(
1939            ModelFamily::from("qwen3_asr".to_owned()),
1940            ModelFamily::Qwen3Asr
1941        );
1942    }
1943
1944    #[test]
1945    fn model_family_from_path() {
1946        // 关键词命中(常见 GGUF 命名)
1947        assert_eq!(
1948            ModelFamily::from_path("models/qwen3-asr-0.6b-q8_0.gguf"),
1949            Some(ModelFamily::Qwen3Asr)
1950        );
1951        assert_eq!(
1952            ModelFamily::from_path("sortformer-diar-4spk-v1-q8_0.gguf"),
1953            Some(ModelFamily::SortformerDiar)
1954        );
1955        assert_eq!(
1956            ModelFamily::from_path("citrinet-asr-q8_0.gguf"),
1957            Some(ModelFamily::CitrinetAsr)
1958        );
1959        assert_eq!(
1960            ModelFamily::from_path("moss-tts-nano-q8_0.gguf"),
1961            Some(ModelFamily::MossTtsNano)
1962        );
1963        // fish_drama3 与 fish_audio 互不误判(无子串重叠,回归锁定)。
1964        assert_eq!(
1965            ModelFamily::from_path("fish-drama3-q8_0.gguf"),
1966            Some(ModelFamily::FishDrama3)
1967        );
1968        assert_eq!(
1969            ModelFamily::from_path("fish_audio-f16.gguf"),
1970            Some(ModelFamily::FishAudio)
1971        );
1972        // moss_tts_v15 不被裸 "moss" 关键词吞掉。
1973        assert_eq!(
1974            ModelFamily::from_path("moss-tts-v15-q8_0.gguf"),
1975            Some(ModelFamily::MossTtsV15)
1976        );
1977        // nemotron_3_diar 不被裸 "nemotron" 关键词吞掉。
1978        assert_eq!(
1979            ModelFamily::from_path("nemotron-3-diarization-bf16.gguf"),
1980            Some(ModelFamily::Nemotron3Diar)
1981        );
1982        // vieneu_v3_turbo / 旧名 vietneu 均命中同一族。
1983        assert_eq!(
1984            ModelFamily::from_path("vieneu-v3-turbo-q8_0.gguf"),
1985            Some(ModelFamily::VieneuV3Turbo)
1986        );
1987        assert_eq!(ModelFamily::from("vietneu_tts"), ModelFamily::VieneuV3Turbo);
1988        // 精确规则优先于裸关键词:qwen3-tts / qwen3-forced-aligner 不能判成 Qwen3Asr
1989        assert_eq!(
1990            ModelFamily::from_path("qwen3-tts-12hz-0.6b-base-q8_0.gguf"),
1991            Some(ModelFamily::Qwen3Tts)
1992        );
1993        assert_eq!(
1994            ModelFamily::from_path("qwen3-forced-aligner-q8_0.gguf"),
1995            Some(ModelFamily::Qwen3ForcedAligner)
1996        );
1997        assert_eq!(
1998            ModelFamily::from_path("mira-tts-q8_0.gguf"),
1999            Some(ModelFamily::MiraTts)
2000        );
2001        assert_eq!(
2002            ModelFamily::from_path("htdemucs-6s-q8_0.gguf"),
2003            Some(ModelFamily::Htdemucs6Stems)
2004        );
2005        // htdemucs_6stems 不被裸 "htdemucs" 关键词吞掉;裸名仍判回 Htdemucs。
2006        assert_eq!(
2007            ModelFamily::from_path("htdemucs-q8_0.gguf"),
2008            Some(ModelFamily::Htdemucs)
2009        );
2010        // kitten_tts2 不被 kitten_tts / 裸 "kitten" 吞掉。
2011        assert_eq!(
2012            ModelFamily::from_path("kitten-tts2-q8_0.gguf"),
2013            Some(ModelFamily::KittenTts2)
2014        );
2015        assert_eq!(
2016            ModelFamily::from_path("kitten-tts-q8_0.gguf"),
2017            Some(ModelFamily::KittenTts)
2018        );
2019        // plapre_3 / plapre 均判回 Plapre。
2020        assert_eq!(
2021            ModelFamily::from_path("plapre-3-bf16.gguf"),
2022            Some(ModelFamily::Plapre)
2023        );
2024        // open_wake_word 与 micro_wake_word 互不误判。
2025        assert_eq!(
2026            ModelFamily::from_path("open-wake-word-alexa-f32.gguf"),
2027            Some(ModelFamily::OpenWakeWord)
2028        );
2029        assert_eq!(
2030            ModelFamily::from_path("microwake-mixednet-q8_0.gguf"),
2031            Some(ModelFamily::MicroWakeWord)
2032        );
2033        // index_echo 不被裸 "echo" 吞掉。
2034        assert_eq!(
2035            ModelFamily::from_path("index-echo-q8_0.gguf"),
2036            Some(ModelFamily::IndexEcho)
2037        );
2038        assert_eq!(
2039            ModelFamily::from_path("echo-tts-q8_0.gguf"),
2040            Some(ModelFamily::EchoTts)
2041        );
2042        // owsm_ctc 不被裸 "owsm" 吞掉。
2043        assert_eq!(
2044            ModelFamily::from_path("owsm-ctc-q8_0.gguf"),
2045            Some(ModelFamily::OwsmCtc)
2046        );
2047        assert_eq!(
2048            ModelFamily::from_path("owsm-q8_0.gguf"),
2049            Some(ModelFamily::Owsm)
2050        );
2051        // moss_ttsd 不被裸 "moss" 吞掉。
2052        assert_eq!(
2053            ModelFamily::from_path("moss-ttsd-q8_0.gguf"),
2054            Some(ModelFamily::MossTtsd)
2055        );
2056        // 新族常规命中抽查。
2057        assert_eq!(
2058            ModelFamily::from_path("smart-turn-q8_0.gguf"),
2059            Some(ModelFamily::SmartTurn)
2060        );
2061        // firered_vad 独立成族(非 firered_audio)。
2062        assert_eq!(
2063            ModelFamily::from_path("firered-vad-q8_0.gguf"),
2064            Some(ModelFamily::FireredVad)
2065        );
2066        // hviske_asr_v6 不被裸 "hviske" 吞掉;裸名仍判回 HviskeAsr。
2067        assert_eq!(
2068            ModelFamily::from_path("hviske-asr-v6-q8_0.gguf"),
2069            Some(ModelFamily::HviskeAsrV6)
2070        );
2071        assert_eq!(
2072            ModelFamily::from_path("hviske-asr-q8_0.gguf"),
2073            Some(ModelFamily::HviskeAsr)
2074        );
2075        assert_eq!(
2076            ModelFamily::from_path("whistle-asr-q8_0.gguf"),
2077            Some(ModelFamily::WhistleAsr)
2078        );
2079        // mossformer2 不被裸 "moss" 吞掉。
2080        assert_eq!(
2081            ModelFamily::from_path("mossformer2-ss-16k-f32.gguf"),
2082            Some(ModelFamily::Mossformer2)
2083        );
2084        assert_eq!(
2085            ModelFamily::from_path("tf-gridnet-q8_0.gguf"),
2086            Some(ModelFamily::TfGridnet)
2087        );
2088        // 唤醒词族。
2089        assert_eq!(
2090            ModelFamily::from_path("micro-wake-word-hey-jarvis-q8_0.gguf"),
2091            Some(ModelFamily::MicroWakeWord)
2092        );
2093        assert_eq!(
2094            ModelFamily::from_path("sherpa-kws-keywords-q8_0.gguf"),
2095            Some(ModelFamily::SherpaKws)
2096        );
2097        // ced 不吞掉对齐模型(for**ced**-aligner),且 audioset 后缀判给 CED。
2098        assert_eq!(
2099            ModelFamily::from_path("ced-base-16k-q8_0.gguf"),
2100            Some(ModelFamily::Ced)
2101        );
2102        assert_eq!(
2103            ModelFamily::from_path("qwen3-forced-aligner-q8_0.gguf"),
2104            Some(ModelFamily::Qwen3ForcedAligner)
2105        );
2106        assert_eq!(
2107            ModelFamily::from_path("ast-audioset-q8_0.gguf"),
2108            Some(ModelFamily::AstAudioset)
2109        );
2110        assert_eq!(
2111            ModelFamily::from_path("tone-color-vc-q8_0.gguf"),
2112            Some(ModelFamily::ToneColorVc)
2113        );
2114        assert_eq!(
2115            ModelFamily::from("htdemucs_6s"),
2116            ModelFamily::Htdemucs6Stems
2117        );
2118        // 流式/新变体不被其前缀家族吞掉(子串匹配顺序)
2119        assert_eq!(
2120            ModelFamily::from_path("vibevoice-asr-streaming-7b-q8_0.gguf"),
2121            Some(ModelFamily::VibevoiceAsrStreaming)
2122        );
2123        assert_eq!(
2124            ModelFamily::from_path("sortformer-diar-v2-q8_0.gguf"),
2125            Some(ModelFamily::SortformerDiarV2)
2126        );
2127        assert_eq!(
2128            ModelFamily::from_path("kokoro-tts-82m-q8_0.gguf"),
2129            Some(ModelFamily::KokoroTts)
2130        );
2131        assert_eq!(
2132            ModelFamily::from_path("moonshine-tiny-q8_0.gguf"),
2133            Some(ModelFamily::MoonshineAsr)
2134        );
2135        assert_eq!(
2136            ModelFamily::from_path("niagara-asr-q8_0.gguf"),
2137            Some(ModelFamily::NiagaraAsr)
2138        );
2139        assert_eq!(
2140            ModelFamily::from_path("yue2-music-q8_0.gguf"),
2141            Some(ModelFamily::Yue2)
2142        );
2143        assert_eq!(
2144            ModelFamily::from_path("sheetsage2-q8_0.gguf"),
2145            Some(ModelFamily::Sheetsage2)
2146        );
2147        // voxtral_tts 不被裸 "voxtral" 关键词吞掉(后者判回 realtime)。
2148        assert_eq!(
2149            ModelFamily::from_path("voxtral-tts-4b-q8_0.gguf"),
2150            Some(ModelFamily::VoxtralTts)
2151        );
2152        assert_eq!(
2153            ModelFamily::from_path("voxtral-realtime-q8_0.gguf"),
2154            Some(ModelFamily::VoxtralRealtime)
2155        );
2156        assert_eq!(
2157            ModelFamily::from_path("pulsevad-2.1k-f32.gguf"),
2158            Some(ModelFamily::Pulsevad)
2159        );
2160        assert_eq!(
2161            ModelFamily::from_path("canary-180m-flash-q8_0.gguf"),
2162            Some(ModelFamily::CanaryAsr)
2163        );
2164        assert_eq!(
2165            ModelFamily::from_path("cohere-transcribe-03-2026-q8_0.gguf"),
2166            Some(ModelFamily::CohereAsr)
2167        );
2168        // moss_transcribe_diarize 不能被裸 "moss" 吞掉
2169        assert_eq!(
2170            ModelFamily::from_path("moss-transcribe-diarize-q8_0.gguf"),
2171            Some(ModelFamily::MossTranscribeDiarize)
2172        );
2173        assert_eq!(
2174            ModelFamily::from_path("apollo-orig.gguf"),
2175            Some(ModelFamily::Apollo)
2176        );
2177        assert_eq!(
2178            ModelFamily::from_path("universr-audio-orig.gguf"),
2179            Some(ModelFamily::Universr)
2180        );
2181        assert_eq!(
2182            ModelFamily::from_path("zipvoice-distill-q8_0.gguf"),
2183            Some(ModelFamily::Zipvoice)
2184        );
2185        assert_eq!(
2186            ModelFamily::from_path("auk-flash-q8_0.gguf"),
2187            Some(ModelFamily::Auk)
2188        );
2189        assert_eq!(
2190            ModelFamily::from_path("liveavatar-nvfp4.gguf"),
2191            Some(ModelFamily::Liveavatar)
2192        );
2193        assert_eq!(
2194            ModelFamily::from_path("piper-en-us-lessac-medium-orig.gguf"),
2195            Some(ModelFamily::PiperTts)
2196        );
2197        assert_eq!(
2198            ModelFamily::from_path("kitten-tts-mini-0.8-orig.gguf"),
2199            Some(ModelFamily::KittenTts)
2200        );
2201        // confucius4_r2t2 不能被裸 "confucius" 吞掉(判成 Confucius4Tts)
2202        assert_eq!(
2203            ModelFamily::from_path("confucius4-r2t2-q8_0.gguf"),
2204            Some(ModelFamily::Confucius4R2t2)
2205        );
2206        assert_eq!(
2207            ModelFamily::from_path("confucius4-tts-q8_0.gguf"),
2208            Some(ModelFamily::Confucius4Tts)
2209        );
2210        // 内置音频工具:model_path 直接传工具 ID 也能映射到族。
2211        assert_eq!(
2212            ModelFamily::from_path("deepfilternet2"),
2213            Some(ModelFamily::BuiltinAudioUtils)
2214        );
2215        // gradium_phonon 与 pocket_tts 互不误判(同 target、不同 loader 族);
2216        // phonon-2 自 d2da530b 起是正式族 Phonon2Asr(不再是 None)。
2217        assert_eq!(
2218            ModelFamily::from_path("phonon-2-mixed-q8.gguf"),
2219            Some(ModelFamily::Phonon2Asr)
2220        );
2221        assert_eq!(
2222            ModelFamily::from_path("Phonon-2-GGUF/phonon-2-mixed-q8.gguf"),
2223            Some(ModelFamily::Phonon2Asr)
2224        );
2225        assert_eq!(
2226            ModelFamily::from_path("phonon-31m-en-q8_0.gguf"),
2227            Some(ModelFamily::GradiumPhonon)
2228        );
2229        assert_eq!(
2230            ModelFamily::from_path("gradium-phonon-31m-en-q8_0.gguf"),
2231            Some(ModelFamily::GradiumPhonon)
2232        );
2233        assert_eq!(
2234            ModelFamily::from_path("pocket-tts-q8_0.gguf"),
2235            Some(ModelFamily::PocketTts)
2236        );
2237        // cc21c9ea→d2da530b 新增 4 族:按上游真实权重文件名断言。
2238        // pocket_tts_small 不被裸 "pocket_tts" 吞掉(含目录名的完整路径)。
2239        assert_eq!(
2240            ModelFamily::from_path(
2241                "PocketTTS-Small-GGUF/english-18m-refl/english-18m-refl-f32.gguf"
2242            ),
2243            Some(ModelFamily::PocketTtsSmall)
2244        );
2245        // realtime_reuse 不被目录 RE-USE-GGUF 的 "re-use"/文件名的 "reuse"
2246        // 抢先吞成 Reuse。
2247        assert_eq!(
2248            ModelFamily::from_path("RE-USE-GGUF/realtime-reuse-f32.gguf"),
2249            Some(ModelFamily::RealtimeReuse)
2250        );
2251        assert_eq!(
2252            ModelFamily::from_path("Haas-ja-v1.0-GGUF/haas-ja-v1.0-q8_0.gguf"),
2253            Some(ModelFamily::HaasAsr)
2254        );
2255        assert_eq!(
2256            ModelFamily::from_path("Underdog-Bark-3.0-GGUF/underdog-bark-3.0-mixed-f16.gguf"),
2257            Some(ModelFamily::UnderdogBark3)
2258        );
2259        assert_eq!(
2260            ModelFamily::from_path("d1-Omni-2B-GGUF/d1-Omni-2B-Q8_0.gguf"),
2261            Some(ModelFamily::LfmD1Omni)
2262        );
2263        assert_eq!(
2264            ModelFamily::from_path("d1-Omni-2B-GGUF/mmproj-d1-Omni-2B-F16.gguf"),
2265            Some(ModelFamily::LfmD1Omni)
2266        );
2267        // ming3_audio 不被裸 "ming3" 吞成 Ming3Tts;权重名两种写法均命中。
2268        assert_eq!(
2269            ModelFamily::from_path("ming-3.0-audio-tiny-bf16.gguf"),
2270            Some(ModelFamily::Ming3Audio)
2271        );
2272        assert_eq!(
2273            ModelFamily::from_path("ming-3.0-tts-0.5b-bf16.gguf"),
2274            Some(ModelFamily::Ming3Tts)
2275        );
2276        assert_eq!(
2277            ModelFamily::from_path("xtts-v2-gpt-q8_0.gguf"),
2278            Some(ModelFamily::XttsV2)
2279        );
2280        assert_eq!(
2281            ModelFamily::from_path("latentsync-1.6-q8_0-f16.gguf"),
2282            Some(ModelFamily::Latentsync)
2283        );
2284        // 大小写不敏感
2285        assert_eq!(
2286            ModelFamily::from_path("Qwen3-ASR.Q8_0.GGUF"),
2287            Some(ModelFamily::Qwen3Asr)
2288        );
2289        // #family= 显式覆盖优先于关键词(大小写不敏感)
2290        assert_eq!(
2291            ModelFamily::from_path("model.gguf#family=citrinet_asr"),
2292            Some(ModelFamily::CitrinetAsr)
2293        );
2294        assert_eq!(
2295            ModelFamily::from_path("model.gguf#family=MiraTTS"),
2296            Some(ModelFamily::MiraTts)
2297        );
2298        assert_eq!(
2299            ModelFamily::from_path("model.gguf#family=qwen3_tts"),
2300            Some(ModelFamily::Qwen3Tts)
2301        );
2302        // ---- cc21c9ea 新增 5 族:按上游真实权重名/目录名校验 ----
2303        assert_eq!(
2304            ModelFamily::from_path("earshot_vad.safetensors"),
2305            Some(ModelFamily::EarshotVad)
2306        );
2307        assert_eq!(
2308            ModelFamily::from_path("nemotron-transcribe-30b-a4b-experts-iq4_nl.gguf"),
2309            Some(ModelFamily::NemotronTranscribe)
2310        );
2311        // nemotron 的 encoder 侧 mmproj 与权重目录名都含 "nemotron-3",
2312        // 必须命中 Transcribe 而非被 nemotron_3_diar 的关键词吞掉。
2313        assert_eq!(
2314            ModelFamily::from_path(
2315                "mmproj-NVIDIA-Nemotron-3.5-Transcribe-30B-A4B-Encoder-F16.gguf"
2316            ),
2317            Some(ModelFamily::NemotronTranscribe)
2318        );
2319        assert_eq!(
2320            ModelFamily::from_path("NVIDIA-Nemotron-3.5-Transcribe-30B-A4B-GGUF"),
2321            Some(ModelFamily::NemotronTranscribe)
2322        );
2323        // 反向回归:真正的 Nemotron 3 Diar 仍判成 diar。
2324        assert_eq!(
2325            ModelFamily::from_path("nemotron-3-diarization-bf16.gguf"),
2326            Some(ModelFamily::Nemotron3Diar)
2327        );
2328        assert_eq!(
2329            ModelFamily::from_path("LFM3-Audio-2B-Q8_0.gguf"),
2330            Some(ModelFamily::Lfm3Audio)
2331        );
2332        assert_eq!(
2333            ModelFamily::from_path("mmproj-LFM3-Audio-2B-F16.gguf"),
2334            Some(ModelFamily::Lfm3Audio)
2335        );
2336        // 反向回归:lfm2 不被新增的 lfm3 关键词影响。
2337        assert_eq!(
2338            ModelFamily::from_path("lfm2.5-audio-1.5b-q8_0.gguf"),
2339            Some(ModelFamily::Lfm2Audio)
2340        );
2341        assert_eq!(
2342            ModelFamily::from_path("magenta-rt2-small-f16.gguf"),
2343            Some(ModelFamily::MagentaRealtime2)
2344        );
2345        assert_eq!(
2346            ModelFamily::from_path("Magenta-RealTime-2-GGUF/magenta-rt2-base-q8_0.gguf"),
2347            Some(ModelFamily::MagentaRealtime2)
2348        );
2349        assert_eq!(
2350            ModelFamily::from_path("SoulX-FlashHead-Lite-Q8_0.gguf"),
2351            Some(ModelFamily::SoulxFlashhead)
2352        );
2353        assert_eq!(
2354            ModelFamily::from_path("SoulX-FlashHead-GGUF"),
2355            Some(ModelFamily::SoulxFlashhead)
2356        );
2357        // 无法识别
2358        assert_eq!(ModelFamily::from_path("my-weights.gguf"), None);
2359        assert_eq!(ModelFamily::from_path(""), None);
2360    }
2361
2362    #[test]
2363    fn task_kind_as_str() {
2364        let cases = [
2365            (TaskKind::Vad, "vad"),
2366            (TaskKind::Asr, "asr"),
2367            (TaskKind::Diar, "diar"),
2368            (TaskKind::SourceSeparation, "sep"),
2369            (TaskKind::AudioGeneration, "gen"),
2370            (TaskKind::Tts, "tts"),
2371            (TaskKind::VoiceCloning, "clon"),
2372            (TaskKind::VoiceConversion, "vc"),
2373            (TaskKind::SpeechToSpeech, "s2s"),
2374            (TaskKind::Alignment, "align"),
2375            (TaskKind::VoiceDesign, "vdes"),
2376            (TaskKind::SpeakerRecognition, "spk"),
2377            (TaskKind::Svc, "svc"),
2378            (TaskKind::Midi, "midi"),
2379            (TaskKind::TurnDetection, "turn"),
2380            (TaskKind::WakeWord, "wake"),
2381            (TaskKind::AudioClassification, "cls"),
2382        ];
2383        for (k, want) in cases {
2384            assert_eq!(k.as_str(), want);
2385        }
2386    }
2387
2388    #[test]
2389    fn task_kind_aliases() {
2390        // 上游别名与规范短名互通:`wake_word`→WakeWord,`classify`→AudioClassification。
2391        assert_eq!(
2392            serde_json::from_str::<TaskKind>("\"wake_word\"").unwrap(),
2393            TaskKind::WakeWord
2394        );
2395        assert_eq!(
2396            serde_json::from_str::<TaskKind>("\"classify\"").unwrap(),
2397            TaskKind::AudioClassification
2398        );
2399    }
2400
2401    #[test]
2402    fn task_kind_serde_roundtrip() {
2403        // serde 表示必须与 as_str(C 边界字符串)一致,而非变体名的
2404        // 小写形式(如 "sep" 而非 "sourceseparation")。
2405        let all = [
2406            TaskKind::Vad,
2407            TaskKind::Asr,
2408            TaskKind::Diar,
2409            TaskKind::SourceSeparation,
2410            TaskKind::AudioGeneration,
2411            TaskKind::Tts,
2412            TaskKind::VoiceCloning,
2413            TaskKind::VoiceConversion,
2414            TaskKind::SpeechToSpeech,
2415            TaskKind::Alignment,
2416            TaskKind::VoiceDesign,
2417            TaskKind::SpeakerRecognition,
2418            TaskKind::Svc,
2419            TaskKind::Midi,
2420            TaskKind::TurnDetection,
2421            TaskKind::WakeWord,
2422            TaskKind::AudioClassification,
2423        ];
2424        for k in all {
2425            let s = serde_json::to_string(&k).unwrap();
2426            assert_eq!(s, format!("\"{}\"", k.as_str()));
2427            assert_eq!(serde_json::from_str::<TaskKind>(&s).unwrap(), k);
2428        }
2429        assert!(serde_json::from_str::<TaskKind>("\"sourceseparation\"").is_err());
2430    }
2431
2432    #[test]
2433    fn run_mode_as_str() {
2434        assert_eq!(RunMode::Offline.as_str(), "offline");
2435        assert_eq!(RunMode::Streaming.as_str(), "streaming");
2436    }
2437
2438    #[test]
2439    fn backend_as_str() {
2440        let cases = [
2441            (Backend::Cpu, "cpu"),
2442            (Backend::Cuda, "cuda"),
2443            (Backend::Hip, "hip"),
2444            (Backend::Vulkan, "vulkan"),
2445            (Backend::Metal, "metal"),
2446            (Backend::Best, "best"),
2447        ];
2448        for (b, want) in cases {
2449            assert_eq!(b.as_str(), want);
2450        }
2451    }
2452
2453    /// 结构化类型应能从 C 侧 `dump_*` 产出的 JSON 反序列化(serde 契约)。
2454    #[test]
2455    fn structured_types_deserialize() {
2456        let result: TaskResult = serde_json::from_str(
2457            r#"{
2458                "speech_segments": [
2459                    {"span": {"start_sample": 0, "end_sample": 1600}, "confidence": 0.95, "text": ""}
2460                ],
2461                "text_output": {"text": "hi", "language": "en"},
2462                "audio_output": {"sample_rate": 24000, "channels": 1, "sample_count": 0, "samples": []},
2463                "named_audio_outputs": [],
2464                "speaker_turns": [
2465                    {"speaker_id": "speaker_0", "span": {"start_sample": 0, "end_sample": 1600}, "confidence": 0.9, "text": "hello"}
2466                ],
2467                "word_timestamps": [
2468                    {"span": {"start_sample": 0, "end_sample": 320}, "word": "hello", "confidence": 0.99}
2469                ],
2470                "artifact_output": {"kind": "speaker_embedding", "id": "spk1", "payload_base64": "", "meta": {}},
2471                "output_artifacts": [
2472                    {"kind": "custom", "id": "a", "payload_base64": "AQID", "meta": {"k": "v"}}
2473                ]
2474            }"#,
2475        )
2476        .unwrap();
2477        assert_eq!(result.speech_segments.len(), 1);
2478        assert_eq!(result.speech_segments[0].span.start_sample, 0);
2479        assert_eq!(result.speech_segments[0].confidence, 0.95);
2480        assert_eq!(result.text_output.as_ref().unwrap().text, "hi");
2481        assert_eq!(result.audio_output.unwrap().sample_rate, 24000);
2482        assert_eq!(result.speaker_turns[0].text, "hello");
2483        assert_eq!(result.word_timestamps[0].word, "hello");
2484        assert_eq!(
2485            result.artifact_output.as_ref().unwrap().kind,
2486            "speaker_embedding"
2487        );
2488        assert_eq!(result.output_artifacts[0].payload_base64, "AQID");
2489
2490        let ev: StreamEvent = serde_json::from_str(
2491            r#"{
2492                "voice_activity": [
2493                    {"kind": "speech_start", "sample": 100, "probability": 0.8, "segment": null}
2494                ],
2495                "partial_text": null,
2496                "partial_text_snapshot": {"text": "hel", "language": "en"},
2497                "audio_output": null,
2498                "named_audio_outputs": [{"id": "chunk_0", "audio": {"sample_rate": 48000, "channels": 2, "sample_count": 320, "samples": []}, "meta": {}}],
2499                "speaker_turns": [],
2500                "word_timestamps": [],
2501                "output_artifacts": [],
2502                "is_final": true
2503            }"#,
2504        )
2505        .unwrap();
2506        assert_eq!(ev.voice_activity[0].kind, "speech_start");
2507        assert_eq!(ev.named_audio_outputs[0].id, "chunk_0");
2508        assert_eq!(ev.partial_text_snapshot.as_ref().unwrap().text, "hel");
2509        assert!(ev.is_final);
2510    }
2511
2512    #[test]
2513    fn model_inspection_deserialize() {
2514        // 验证 inspect 返回的 ModelInspection 结构与上游 dump_inspection 对齐,
2515        // 且缺失字段(无 meta / 无发现资产)能向后兼容。
2516        let info: ModelInspection = serde_json::from_str(
2517            r#"{
2518                "metadata": {
2519                    "family": "qwen3_asr",
2520                    "variant": "q8_0",
2521                    "description": "Qwen3 ASR",
2522                    "config_candidates": ["config.json"],
2523                    "weight_candidates": ["qwen3-asr-q8_0.gguf"]
2524                },
2525                "capabilities": {
2526                    "supported_tasks": [{"task": "asr", "modes": ["offline", "streaming"]}],
2527                    "languages": ["zh", "en"],
2528                    "supports_speaker_reference": false,
2529                    "supports_style_condition": false,
2530                    "supports_timestamps": true
2531                },
2532                "discovered_weights": [
2533                    {"id": "model", "path": "/models/qwen3-asr-q8_0.gguf"}
2534                ],
2535                "cli": {
2536                    "request_options": [
2537                        {"name": "language", "value_name": "CODE", "description": "语言", "required": false, "default_value": "auto"}
2538                    ],
2539                    "session_options": [],
2540                    "load_options": []
2541                }
2542            }"#,
2543        )
2544        .unwrap();
2545        assert_eq!(info.metadata.family, "qwen3_asr");
2546        assert!(
2547            info.capabilities
2548                .supported_tasks
2549                .iter()
2550                .any(|t| t.task == "asr" && t.modes.contains(&"streaming".to_string()))
2551        );
2552        assert_eq!(info.discovered_weights[0].id, "model");
2553        assert_eq!(info.cli.request_options[0].name, "language");
2554        assert_eq!(
2555            info.cli.request_options[0].default_value.as_deref(),
2556            Some("auto")
2557        );
2558    }
2559}