Skip to main content

audio_cpp/
options.rs

1//! 常用选项键常量与会话选项构造器。
2//!
3//! 上游选项是无类型的字符串键值(`options: { "key": "value" }`),配错键名
4//! 不会报错、只会被静默忽略(教训:`vad_threshold` 在上游从未存在,
5//! silero/marblenet 的阈值键都是 [`request::THRESHOLD`])。本模块把源码级
6//! 核实过的键收敛为常量,拼写错误在编译期暴露;族名前缀别名(如
7//! `sortformer_diar.session_len_sec`)与裸键等价,shim 照单全收。
8//!
9//! 键分两类,传参位置不同不可混用:
10//! - [`request`]:随请求走([`crate::Request::option`]),如 `language`;
11//! - [`session`]:随会话走([`SessionOptions`] →
12//!   [`crate::Model::create_task_session`] 的 `session_options`),如
13//!   `session_len_sec`。
14//!
15//! # 示例
16//!
17//! ```
18//! use audio_cpp::options::{SessionOptions, request, session};
19//! use audio_cpp::Request;
20//!
21//! // 请求选项:直接用常量当键
22//! let req = Request::asr("./speech.wav").option(request::LANGUAGE, "zh");
23//! // 会话选项:builder 拼出 create_task_session 要的 JSON 字符串
24//! let opts = SessionOptions::new()
25//!     .session_len_sec(100.0)
26//!     .graph_capacity_mode(session::graph_capacity::GROW);
27//! assert_eq!(
28//!     opts.to_json(),
29//!     r#"{"graph_capacity_mode":"grow","session_len_sec":100.0}"#
30//! );
31//! ```
32
33use std::collections::BTreeMap;
34
35/// 请求选项键(随 [`crate::Request::option`] 走)。
36pub mod request {
37    /// 转写/合成语言(如 `"zh"` / `"en"`)。
38    ///
39    /// 注意:部分族只读顶层 `language`(对齐器要求 `text_input.language`
40    /// 非空),部分只读本选项(如 Qwen3 TTS 的声音克隆参考);
41    /// [`crate::TtsRequest::language`] 与 [`crate::Request::align`] 写的是
42    /// 顶层 `language`。parakeet_tdt 会严格校验请求选项——它未声明的键
43    /// (含本键)会直接拒绝,此时只用顶层 `language`。
44    pub const LANGUAGE: &str = "language";
45    /// Qwen3 ASR 是否回填词级时间戳(`"true"` 开启)。
46    ///
47    /// 开启后还需会话选项配好对齐模型(见
48    /// [`super::session::QWEN3_ASR_FORCED_ALIGNER_MODEL_PATH`]),否则上游
49    /// 抛错;流式模式不支持本选项(直接抛错)。无词戳输出时先检查这两处,
50    /// 而非假设上游未填充。
51    pub const RETURN_TIMESTAMPS: &str = "return_timestamps";
52    /// 词级时间戳钳制到音频范围内(`"true"` 开启)。
53    ///
54    /// Qwen3 ASR 与 Qwen3 对齐器均读取本键(后者在
55    /// `model_specs/qwen3_forced_aligner.json` 声明,默认 `false`)。
56    pub const CLAMP_TIMESTAMPS_TO_AUDIO: &str = "clamp_timestamps_to_audio";
57    /// 音频分块时长(秒,浮点),如 Qwen3 ASR 流式的窗口/离线分块。
58    ///
59    /// 别名(等价):`audio_chunk_duration_sec` / `audio_chunk_duration_seconds`
60    /// / `audio_chunk_duration`。
61    pub const AUDIO_CHUNK_SECONDS: &str = "audio_chunk_seconds";
62    /// 音频分块模式:`auto` / `fixed` / `quiet_energy` / `vad` / `none`
63    /// (见 [`super::session::audio_chunk_mode`])。
64    pub const AUDIO_CHUNK_MODE: &str = "audio_chunk_mode";
65    /// VAD 阈值(silero_vad 与 marblenet_vad 均用本键)。
66    ///
67    /// 注意:`vad_threshold` 在上游从未存在,传它会被静默忽略。
68    pub const THRESHOLD: &str = "threshold";
69    /// Qwen3 TTS 声音克隆:参考音频的文本转写(越贴合内容音色越准)。
70    ///
71    /// [`crate::TtsRequest::reference_text`] 已封装本键,无需手写。
72    pub const REFERENCE_TEXT: &str = "reference_text";
73    /// 流式 TTS 坏样本重试开关(`"false"` 关闭;VoxCPM2 流式硬性要求关闭)。
74    pub const RETRY_BADCASE: &str = "retry_badcase";
75    /// YuE2 歌曲风格/标签(必填,非空)。
76    pub const STYLE: &str = "style";
77    /// YuE2 歌词(必填;与请求顶层 `text` 等价,见 `Request::tts`)。
78    pub const LYRICS: &str = "lyrics";
79    /// 采样种子(YuE2 等生成族)。
80    pub const SEED: &str = "seed";
81    /// 解码最大长度(如 SheetSage2、Moonshine)。
82    pub const MAX_TOKENS: &str = "max_tokens";
83    /// MMS 对齐器文本归一化:`latin`(默认,31 符号拉丁 CTC)/
84    /// `pre_romanized`(调用方自备 ASCII 罗马化文本)。
85    pub const TEXT_NORMALIZATION: &str = "text_normalization";
86}
87
88/// 会话选项键(随 [`SessionOptions`] 走,建会话时一次传入)。
89pub mod session {
90    /// 长音频会话图长度(秒),如 SortFormer 说话人分离默认仅 ~20s/90s,
91    /// 超长音频需调大(如 `100.0`)配合 [`GRAPH_CAPACITY_MODE`] 使用。
92    ///
93    /// 族名前缀别名(等价):`sortformer_diar.session_len_sec`。
94    pub const SESSION_LEN_SEC: &str = "session_len_sec";
95    /// 会话图容量模式(见 [`graph_capacity`] 取值)。
96    pub const GRAPH_CAPACITY_MODE: &str = "graph_capacity_mode";
97    /// Qwen3 ASR 词戳回填所需的对齐模型路径(Qwen3-ForcedAligner 权重)。
98    ///
99    /// 不配而请求开 [`super::request::RETURN_TIMESTAMPS`] 时上游抛错。
100    /// 别名(等价):`qwen3_asr.aligner_model_path`。
101    pub const QWEN3_ASR_FORCED_ALIGNER_MODEL_PATH: &str = "qwen3_asr.forced_aligner_model_path";
102
103    /// [`GRAPH_CAPACITY_MODE`](self::GRAPH_CAPACITY_MODE) 的合法取值。
104    pub mod graph_capacity {
105        /// 固定容量,超限即报 `request exceeds fixed graph capacity`。
106        pub const FIXED: &str = "fixed";
107        /// 分档容量。
108        pub const TIERED: &str = "tiered";
109        /// 按需增长(长音频 diar 常用)。
110        pub const GROW: &str = "grow";
111        /// 翻倍增长。
112        pub const DOUBLE: &str = "double";
113    }
114
115    /// [`super::request::AUDIO_CHUNK_MODE`] 的合法取值。
116    pub mod audio_chunk_mode {
117        /// 自动选择(默认)。
118        pub const AUTO: &str = "auto";
119        /// 固定窗口切分。
120        pub const FIXED: &str = "fixed";
121        /// 静音能量切分。
122        pub const QUIET_ENERGY: &str = "quiet_energy";
123        /// VAD 切分。
124        pub const VAD: &str = "vad";
125        /// 不切分。
126        pub const NONE: &str = "none";
127    }
128}
129
130/// 会话选项构造器:拼出 [`crate::Model::create_task_session`] 的
131/// `session_options` 参数要的 JSON 字符串。
132///
133/// JSON 形如 `{"session_len_sec":100.0,"graph_capacity_mode":"grow"}`;
134/// 空选项序列化为 `{}`。未知键用 [`SessionOptions::option`] 自由扩展
135/// (shim 对未知键同样静默忽略,键名请核对 [`session`] 常量)。
136///
137/// # 示例
138///
139/// ```no_run
140/// use audio_cpp::{Backend, Registry, RunMode, TaskKind, options::SessionOptions};
141///
142/// let registry = Registry::new()?;
143/// let model = registry.load("./sortformer-diar-4spk-q8_0.gguf", None, None)?;
144/// let opts = SessionOptions::new()
145///     .session_len_sec(100.0)
146///     .graph_capacity_mode(audio_cpp::options::session::graph_capacity::GROW);
147/// let opts_json = opts.to_json();
148/// let session = model.create_task_session(
149///     TaskKind::Diar, RunMode::Offline, Backend::Cpu, 0, 4, Some(&opts_json),
150/// )?;
151/// # Ok::<(), audio_cpp::Error>(())
152/// ```
153#[derive(Debug, Clone, Default)]
154pub struct SessionOptions {
155    options: BTreeMap<String, String>,
156}
157
158impl SessionOptions {
159    /// 空选项。
160    pub fn new() -> Self {
161        Self::default()
162    }
163
164    /// 设置一个选项键值(值为任意可转字符串类型)。
165    pub fn option(mut self, key: impl Into<String>, value: impl ToString) -> Self {
166        self.options.insert(key.into(), value.to_string());
167        self
168    }
169
170    /// 会话图长度(秒),见 [`session::SESSION_LEN_SEC`]。
171    pub fn session_len_sec(mut self, seconds: f64) -> Self {
172        self.options
173            .insert(session::SESSION_LEN_SEC.to_owned(), format!("{seconds:?}"));
174        self
175    }
176
177    /// 会话图容量模式(取值见 [`session::graph_capacity`]),见
178    /// [`session::GRAPH_CAPACITY_MODE`]。
179    pub fn graph_capacity_mode(mut self, mode: impl Into<String>) -> Self {
180        self.options
181            .insert(session::GRAPH_CAPACITY_MODE.to_owned(), mode.into());
182        self
183    }
184
185    /// Qwen3 ASR 词戳回填的对齐模型路径,见
186    /// [`session::QWEN3_ASR_FORCED_ALIGNER_MODEL_PATH`]。
187    pub fn qwen3_aligner_model_path(mut self, path: impl Into<String>) -> Self {
188        self.options.insert(
189            session::QWEN3_ASR_FORCED_ALIGNER_MODEL_PATH.to_owned(),
190            path.into(),
191        );
192        self
193    }
194
195    /// 序列化为 JSON 字符串(传给 `create_task_session`)。
196    ///
197    /// 数值型值([`SessionOptions::session_len_sec`] 写入的)按 JSON 数字
198    /// 还原,其余按字符串;空选项为 `{}`。
199    pub fn to_json(&self) -> String {
200        let mut obj = serde_json::Map::new();
201        for (k, v) in &self.options {
202            // session_len_sec 写入的是 Rust float Debug 格式,还原为数字。
203            let value = if k == session::SESSION_LEN_SEC {
204                v.parse::<f64>().map_or_else(
205                    |_| serde_json::Value::String(v.clone()),
206                    serde_json::Value::from,
207                )
208            } else {
209                serde_json::Value::String(v.clone())
210            };
211            obj.insert(k.clone(), value);
212        }
213        serde_json::Value::Object(obj).to_string()
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    // 测试断言中的 unwrap/expect 是惯用法:失败即测试失败,展开错误链无意义。
220    #![allow(clippy::unwrap_used, clippy::expect_used)]
221    use super::*;
222
223    #[test]
224    fn session_options_json_shape() {
225        let opts = SessionOptions::new()
226            .session_len_sec(100.0)
227            .graph_capacity_mode(session::graph_capacity::GROW);
228        let v: serde_json::Value = serde_json::from_str(&opts.to_json()).unwrap();
229        assert_eq!(
230            v,
231            serde_json::json!({
232                "session_len_sec": 100.0,
233                "graph_capacity_mode": "grow",
234            })
235        );
236    }
237
238    #[test]
239    fn session_options_empty_and_custom() {
240        assert_eq!(SessionOptions::new().to_json(), "{}");
241        let opts = SessionOptions::new()
242            .qwen3_aligner_model_path("./align.gguf")
243            .option("custom_key", "v");
244        let v: serde_json::Value = serde_json::from_str(&opts.to_json()).unwrap();
245        assert_eq!(
246            v,
247            serde_json::json!({
248                "qwen3_asr.forced_aligner_model_path": "./align.gguf",
249                "custom_key": "v",
250            })
251        );
252    }
253}