1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Config {
9 #[serde(default)]
10 pub directories: DirectoryConfig,
11 #[serde(default)]
12 pub performance: PerformanceConfig,
13 #[serde(default)]
14 pub processing: ProcessingConfig,
15 #[serde(default)]
16 pub quality: QualityConfig,
17 #[serde(default)]
18 pub metadata: MetadataConfig,
19 #[serde(default)]
20 pub organization: OrganizationConfig,
21 #[serde(default)]
22 pub logging: LoggingConfig,
23 #[serde(default)]
24 pub advanced: AdvancedConfig,
25}
26
27impl Default for Config {
28 fn default() -> Self {
29 Self {
30 directories: DirectoryConfig::default(),
31 performance: PerformanceConfig::default(),
32 processing: ProcessingConfig::default(),
33 quality: QualityConfig::default(),
34 metadata: MetadataConfig::default(),
35 organization: OrganizationConfig::default(),
36 logging: LoggingConfig::default(),
37 advanced: AdvancedConfig::default(),
38 }
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct DirectoryConfig {
45 pub source: Option<PathBuf>,
47 #[serde(default = "default_output")]
49 pub output: String,
50}
51
52impl Default for DirectoryConfig {
53 fn default() -> Self {
54 Self {
55 source: None,
56 output: "same_as_source".to_string(),
57 }
58 }
59}
60
61fn default_output() -> String {
62 "same_as_source".to_string()
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct PerformanceConfig {
68 #[serde(default = "default_max_concurrent_encodes")]
71 pub max_concurrent_encodes: String,
72 #[serde(default = "default_true")]
74 pub enable_parallel_encoding: bool,
75 #[serde(default = "default_encoding_preset")]
77 pub encoding_preset: String,
78}
79
80impl Default for PerformanceConfig {
81 fn default() -> Self {
82 Self {
83 max_concurrent_encodes: "auto".to_string(),
84 enable_parallel_encoding: true,
85 encoding_preset: "balanced".to_string(),
86 }
87 }
88}
89
90fn default_max_concurrent_encodes() -> String {
91 "auto".to_string()
92}
93
94fn default_encoding_preset() -> String {
95 "balanced".to_string()
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct ProcessingConfig {
101 #[serde(default = "default_parallel_workers")]
103 pub parallel_workers: u8,
104 #[serde(default = "default_true")]
106 pub skip_existing: bool,
107 #[serde(default)]
109 pub force_reprocess: bool,
110 #[serde(default)]
112 pub normalize_existing: bool,
113 #[serde(default)]
115 pub keep_temp_files: bool,
116 #[serde(default = "default_max_retries")]
118 pub max_retries: u8,
119 #[serde(default = "default_retry_delay")]
121 pub retry_delay: u64,
122}
123
124impl Default for ProcessingConfig {
125 fn default() -> Self {
126 Self {
127 parallel_workers: 2,
128 skip_existing: true,
129 force_reprocess: false,
130 normalize_existing: false,
131 keep_temp_files: false,
132 max_retries: 2,
133 retry_delay: 1,
134 }
135 }
136}
137
138fn default_max_retries() -> u8 {
139 2
140}
141
142fn default_retry_delay() -> u64 {
143 1
144}
145
146fn default_parallel_workers() -> u8 {
147 2
148}
149
150fn default_true() -> bool {
151 true
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct QualityConfig {
157 #[serde(default = "default_true")]
159 pub prefer_stereo: bool,
160 #[serde(default = "default_chapter_source")]
162 pub chapter_source: String,
163 #[serde(default = "default_bitrate")]
165 pub default_bitrate: String,
166 #[serde(default = "default_sample_rate")]
168 pub default_sample_rate: String,
169}
170
171impl Default for QualityConfig {
172 fn default() -> Self {
173 Self {
174 prefer_stereo: true,
175 chapter_source: "auto".to_string(),
176 default_bitrate: "auto".to_string(),
177 default_sample_rate: "auto".to_string(),
178 }
179 }
180}
181
182fn default_chapter_source() -> String {
183 "auto".to_string()
184}
185
186fn default_bitrate() -> String {
187 "auto".to_string()
188}
189
190fn default_sample_rate() -> String {
191 "auto".to_string()
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct MetadataConfig {
197 #[serde(default = "default_language")]
199 pub default_language: String,
200 #[serde(default = "default_cover_filenames")]
202 pub cover_filenames: Vec<String>,
203 #[serde(default)]
205 pub audible: AudibleConfig,
206 #[serde(default)]
208 pub match_mode: MatchMode,
209}
210
211impl Default for MetadataConfig {
212 fn default() -> Self {
213 Self {
214 default_language: "es".to_string(),
215 cover_filenames: vec![
216 "cover.jpg".to_string(),
217 "folder.jpg".to_string(),
218 "cover.png".to_string(),
219 "folder.png".to_string(),
220 ],
221 audible: AudibleConfig::default(),
222 match_mode: MatchMode::default(),
223 }
224 }
225}
226
227fn default_language() -> String {
228 "es".to_string()
229}
230
231fn default_cover_filenames() -> Vec<String> {
232 vec![
233 "cover.jpg".to_string(),
234 "folder.jpg".to_string(),
235 "cover.png".to_string(),
236 "folder.png".to_string(),
237 ]
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
242#[serde(rename_all = "lowercase")]
243pub enum MatchMode {
244 Disabled,
246 Auto,
248 Interactive,
250}
251
252impl Default for MatchMode {
253 fn default() -> Self {
254 MatchMode::Disabled
255 }
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct AudibleConfig {
261 #[serde(default)]
263 pub enabled: bool,
264 #[serde(default = "default_audible_region")]
266 pub region: String,
267 #[serde(default)]
269 pub auto_match: bool,
270 #[serde(default = "default_true")]
272 pub download_covers: bool,
273 #[serde(default = "default_cache_duration")]
275 pub cache_duration_hours: u64,
276 #[serde(default = "default_rate_limit")]
278 pub rate_limit_per_minute: u32,
279}
280
281impl Default for AudibleConfig {
282 fn default() -> Self {
283 Self {
284 enabled: false,
285 region: "us".to_string(),
286 auto_match: false,
287 download_covers: true,
288 cache_duration_hours: 168, rate_limit_per_minute: 100,
290 }
291 }
292}
293
294fn default_audible_region() -> String {
295 "us".to_string()
296}
297
298fn default_cache_duration() -> u64 {
299 168 }
301
302fn default_rate_limit() -> u32 {
303 100
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct OrganizationConfig {
309 #[serde(default = "default_m4b_folder")]
311 pub m4b_folder: String,
312 #[serde(default = "default_convert_folder")]
314 pub convert_folder: String,
315}
316
317impl Default for OrganizationConfig {
318 fn default() -> Self {
319 Self {
320 m4b_folder: "M4B".to_string(),
321 convert_folder: "To_Convert".to_string(),
322 }
323 }
324}
325
326fn default_m4b_folder() -> String {
327 "M4B".to_string()
328}
329
330fn default_convert_folder() -> String {
331 "To_Convert".to_string()
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct LoggingConfig {
337 #[serde(default)]
339 pub log_to_file: bool,
340 pub log_file: Option<PathBuf>,
342 #[serde(default = "default_log_level")]
344 pub log_level: String,
345}
346
347impl Default for LoggingConfig {
348 fn default() -> Self {
349 Self {
350 log_to_file: false,
351 log_file: None,
352 log_level: "INFO".to_string(),
353 }
354 }
355}
356
357fn default_log_level() -> String {
358 "INFO".to_string()
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct AdvancedConfig {
364 pub ffmpeg_path: Option<PathBuf>,
366 pub atomic_parsley_path: Option<PathBuf>,
368 pub mp4box_path: Option<PathBuf>,
370 pub temp_directory: Option<PathBuf>,
372 pub use_apple_silicon_encoder: Option<bool>,
374}
375
376impl Default for AdvancedConfig {
377 fn default() -> Self {
378 Self {
379 ffmpeg_path: None,
380 atomic_parsley_path: None,
381 mp4box_path: None,
382 temp_directory: None,
383 use_apple_silicon_encoder: None,
384 }
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn test_default_config() {
394 let config = Config::default();
395 assert_eq!(config.processing.parallel_workers, 2);
396 assert_eq!(config.quality.prefer_stereo, true);
397 assert_eq!(config.metadata.default_language, "es");
398 }
399
400 #[test]
401 fn test_config_serialization() {
402 let config = Config::default();
403 let yaml = serde_yaml::to_string(&config).unwrap();
404 let deserialized: Config = serde_yaml::from_str(&yaml).unwrap();
405 assert_eq!(deserialized.processing.parallel_workers, 2);
406 }
407}