1use crate::config::OverflowStrategy;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Default, Serialize, Deserialize)]
8#[serde(default)]
9pub struct PartialConfig {
10 pub ai: PartialAIConfig,
11 pub formats: PartialFormatsConfig,
12 pub sync: PartialSyncConfig,
13 pub general: PartialGeneralConfig,
14 pub parallel: PartialParallelConfig,
15}
16
17#[cfg(test)]
18mod tests {
19 use super::*;
20 use crate::config::Config;
21
22 #[test]
23 fn test_partial_ai_config_merge_and_to_complete_base_url() {
24 let mut base = PartialConfig::default();
26 let mut override_cfg = PartialConfig::default();
28 override_cfg.ai.base_url = Some("https://override.example.com/v1".to_string());
29
30 base.merge(override_cfg).unwrap();
31 let complete = base.to_complete_config().unwrap();
32 assert_eq!(complete.ai.base_url, "https://override.example.com/v1");
33 }
34
35 #[test]
36 fn test_partial_ai_config_to_complete_default_base_url() {
37 let base = PartialConfig::default();
38 let complete = base.to_complete_config().unwrap();
39 assert_eq!(complete.ai.base_url, Config::default().ai.base_url);
40 }
41}
42
43#[derive(Debug, Default, Serialize, Deserialize)]
45pub struct PartialAIConfig {
46 pub provider: Option<String>,
47 pub api_key: Option<String>,
48 pub model: Option<String>,
49 pub base_url: Option<String>,
50 pub max_sample_length: Option<usize>,
51 pub temperature: Option<f32>,
52 pub retry_attempts: Option<u32>,
53 pub retry_delay_ms: Option<u64>,
54}
55
56#[derive(Debug, Default, Serialize, Deserialize)]
58pub struct PartialFormatsConfig {
59 pub default_output: Option<String>,
60 pub preserve_styling: Option<bool>,
61 pub default_encoding: Option<String>,
62 pub encoding_detection_confidence: Option<f32>,
64}
65
66#[derive(Debug, Default, Serialize, Deserialize)]
68pub struct PartialSyncConfig {
69 pub max_offset_seconds: Option<f32>,
70 pub audio_sample_rate: Option<u32>,
71 pub correlation_threshold: Option<f32>,
72 pub dialogue_detection_threshold: Option<f32>,
73 pub min_dialogue_duration_ms: Option<u64>,
74 pub dialogue_merge_gap_ms: Option<u64>,
76 pub enable_dialogue_detection: Option<bool>,
78 pub auto_detect_sample_rate: Option<bool>,
80}
81
82#[derive(Debug, Default, Serialize, Deserialize)]
84pub struct PartialGeneralConfig {
85 pub backup_enabled: Option<bool>,
86 pub max_concurrent_jobs: Option<usize>,
87 pub task_timeout_seconds: Option<u64>,
88 pub enable_progress_bar: Option<bool>,
89 pub worker_idle_timeout_seconds: Option<u64>,
90}
91
92#[derive(Debug, Default, Serialize, Deserialize)]
94pub struct PartialParallelConfig {
95 pub task_queue_size: Option<usize>,
96 pub enable_task_priorities: Option<bool>,
97 pub auto_balance_workers: Option<bool>,
98 pub queue_overflow_strategy: Option<OverflowStrategy>,
100}
101
102impl PartialConfig {
103 pub fn merge(
105 &mut self,
106 other: PartialConfig,
107 ) -> Result<(), crate::config::manager::ConfigError> {
108 if let Some(v) = other.ai.provider {
109 self.ai.provider = Some(v);
110 }
111 if let Some(v) = other.ai.api_key {
112 self.ai.api_key = Some(v);
113 }
114 if let Some(v) = other.ai.model {
115 self.ai.model = Some(v);
116 }
117 if let Some(v) = other.ai.base_url {
118 self.ai.base_url = Some(v);
119 }
120 if let Some(v) = other.ai.max_sample_length {
121 self.ai.max_sample_length = Some(v);
122 }
123 if let Some(v) = other.ai.temperature {
124 self.ai.temperature = Some(v);
125 }
126 if let Some(v) = other.ai.retry_attempts {
127 self.ai.retry_attempts = Some(v);
128 }
129 if let Some(v) = other.ai.retry_delay_ms {
130 self.ai.retry_delay_ms = Some(v);
131 }
132 if let Some(v) = other.formats.default_output {
133 self.formats.default_output = Some(v);
134 }
135 if let Some(v) = other.formats.preserve_styling {
136 self.formats.preserve_styling = Some(v);
137 }
138 if let Some(v) = other.formats.default_encoding {
139 self.formats.default_encoding = Some(v);
140 }
141 if let Some(v) = other.sync.max_offset_seconds {
142 self.sync.max_offset_seconds = Some(v);
143 }
144 if let Some(v) = other.sync.audio_sample_rate {
145 self.sync.audio_sample_rate = Some(v);
146 }
147 if let Some(v) = other.sync.correlation_threshold {
148 self.sync.correlation_threshold = Some(v);
149 }
150 if let Some(v) = other.sync.dialogue_detection_threshold {
151 self.sync.dialogue_detection_threshold = Some(v);
152 }
153 if let Some(v) = other.sync.min_dialogue_duration_ms {
154 self.sync.min_dialogue_duration_ms = Some(v);
155 }
156 if let Some(v) = other.sync.auto_detect_sample_rate {
157 self.sync.auto_detect_sample_rate = Some(v);
158 }
159 if let Some(v) = other.general.backup_enabled {
160 self.general.backup_enabled = Some(v);
161 }
162 if let Some(v) = other.general.max_concurrent_jobs {
163 self.general.max_concurrent_jobs = Some(v);
164 }
165 if let Some(v) = other.general.task_timeout_seconds {
166 self.general.task_timeout_seconds = Some(v);
167 }
168 if let Some(v) = other.general.enable_progress_bar {
169 self.general.enable_progress_bar = Some(v);
170 }
171 if let Some(v) = other.general.worker_idle_timeout_seconds {
172 self.general.worker_idle_timeout_seconds = Some(v);
173 }
174 if let Some(v) = other.parallel.task_queue_size {
175 self.parallel.task_queue_size = Some(v);
176 }
177 if let Some(v) = other.parallel.enable_task_priorities {
178 self.parallel.enable_task_priorities = Some(v);
179 }
180 if let Some(v) = other.parallel.auto_balance_workers {
181 self.parallel.auto_balance_workers = Some(v);
182 }
183 if let Some(v) = other.parallel.queue_overflow_strategy {
184 self.parallel.queue_overflow_strategy = Some(v);
185 }
186 Ok(())
187 }
188}
189
190impl PartialConfig {
191 pub fn to_complete_config(
193 &self,
194 ) -> Result<crate::config::Config, crate::config::manager::ConfigError> {
195 use crate::config::{
196 AIConfig, Config, FormatsConfig, GeneralConfig, ParallelConfig, SyncConfig,
197 };
198 let default = Config::default();
199
200 let ai = AIConfig {
201 provider: self.ai.provider.clone().unwrap_or(default.ai.provider),
202 api_key: self.ai.api_key.clone().or(default.ai.api_key),
203 model: self.ai.model.clone().unwrap_or(default.ai.model),
204 base_url: self.ai.base_url.clone().unwrap_or(default.ai.base_url),
205 max_sample_length: self
206 .ai
207 .max_sample_length
208 .unwrap_or(default.ai.max_sample_length),
209 temperature: self.ai.temperature.unwrap_or(default.ai.temperature),
210 retry_attempts: self.ai.retry_attempts.unwrap_or(default.ai.retry_attempts),
211 retry_delay_ms: self.ai.retry_delay_ms.unwrap_or(default.ai.retry_delay_ms),
212 };
213
214 let formats = FormatsConfig {
215 default_output: self
216 .formats
217 .default_output
218 .clone()
219 .unwrap_or(default.formats.default_output),
220 preserve_styling: self
221 .formats
222 .preserve_styling
223 .unwrap_or(default.formats.preserve_styling),
224 default_encoding: self
225 .formats
226 .default_encoding
227 .clone()
228 .unwrap_or(default.formats.default_encoding.clone()),
229 encoding_detection_confidence: self
230 .formats
231 .encoding_detection_confidence
232 .unwrap_or(default.formats.encoding_detection_confidence),
233 };
234
235 let sync = SyncConfig {
236 max_offset_seconds: self
237 .sync
238 .max_offset_seconds
239 .unwrap_or(default.sync.max_offset_seconds),
240 audio_sample_rate: self
241 .sync
242 .audio_sample_rate
243 .unwrap_or(default.sync.audio_sample_rate),
244 correlation_threshold: self
245 .sync
246 .correlation_threshold
247 .unwrap_or(default.sync.correlation_threshold),
248 dialogue_detection_threshold: self
249 .sync
250 .dialogue_detection_threshold
251 .unwrap_or(default.sync.dialogue_detection_threshold),
252 min_dialogue_duration_ms: self
253 .sync
254 .min_dialogue_duration_ms
255 .unwrap_or(default.sync.min_dialogue_duration_ms),
256 dialogue_merge_gap_ms: self
257 .sync
258 .dialogue_merge_gap_ms
259 .unwrap_or(default.sync.dialogue_merge_gap_ms),
260 enable_dialogue_detection: self
261 .sync
262 .enable_dialogue_detection
263 .unwrap_or(default.sync.enable_dialogue_detection),
264 auto_detect_sample_rate: self
265 .sync
266 .auto_detect_sample_rate
267 .unwrap_or(default.sync.auto_detect_sample_rate),
268 };
269
270 let general = GeneralConfig {
271 backup_enabled: self
272 .general
273 .backup_enabled
274 .unwrap_or(default.general.backup_enabled),
275 max_concurrent_jobs: self
276 .general
277 .max_concurrent_jobs
278 .unwrap_or(default.general.max_concurrent_jobs),
279 task_timeout_seconds: self
280 .general
281 .task_timeout_seconds
282 .unwrap_or(default.general.task_timeout_seconds),
283 enable_progress_bar: self
284 .general
285 .enable_progress_bar
286 .unwrap_or(default.general.enable_progress_bar),
287 worker_idle_timeout_seconds: self
288 .general
289 .worker_idle_timeout_seconds
290 .unwrap_or(default.general.worker_idle_timeout_seconds),
291 };
292
293 let parallel = ParallelConfig {
294 task_queue_size: self
295 .parallel
296 .task_queue_size
297 .unwrap_or(default.parallel.task_queue_size),
298 enable_task_priorities: self
299 .parallel
300 .enable_task_priorities
301 .unwrap_or(default.parallel.enable_task_priorities),
302 auto_balance_workers: self
303 .parallel
304 .auto_balance_workers
305 .unwrap_or(default.parallel.auto_balance_workers),
306 queue_overflow_strategy: self
307 .parallel
308 .queue_overflow_strategy
309 .unwrap_or(default.parallel.queue_overflow_strategy),
310 };
311 Ok(Config {
312 ai,
313 formats,
314 sync,
315 general,
316 parallel,
317 loaded_from: None,
318 })
319 }
320}