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 #[serde(default = "default_max_concurrent_files_per_book")]
80 pub max_concurrent_files_per_book: String,
81}
82
83impl Default for PerformanceConfig {
84 fn default() -> Self {
85 Self {
86 max_concurrent_encodes: "auto".to_string(),
87 enable_parallel_encoding: true,
88 encoding_preset: "balanced".to_string(),
89 max_concurrent_files_per_book: "8".to_string(),
90 }
91 }
92}
93
94fn default_max_concurrent_encodes() -> String {
95 "auto".to_string()
96}
97
98fn default_encoding_preset() -> String {
99 "balanced".to_string()
100}
101
102fn default_max_concurrent_files_per_book() -> String {
103 "8".to_string()
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ProcessingConfig {
109 #[serde(default = "default_parallel_workers")]
111 pub parallel_workers: u8,
112 #[serde(default = "default_true")]
114 pub skip_existing: bool,
115 #[serde(default)]
117 pub force_reprocess: bool,
118 #[serde(default)]
120 pub normalize_existing: bool,
121 #[serde(default)]
123 pub keep_temp_files: bool,
124 #[serde(default = "default_max_retries")]
126 pub max_retries: u8,
127 #[serde(default = "default_retry_delay")]
129 pub retry_delay: u64,
130}
131
132impl Default for ProcessingConfig {
133 fn default() -> Self {
134 Self {
135 parallel_workers: 2,
136 skip_existing: true,
137 force_reprocess: false,
138 normalize_existing: false,
139 keep_temp_files: false,
140 max_retries: 2,
141 retry_delay: 1,
142 }
143 }
144}
145
146fn default_max_retries() -> u8 {
147 2
148}
149
150fn default_retry_delay() -> u64 {
151 1
152}
153
154fn default_parallel_workers() -> u8 {
155 2
156}
157
158fn default_true() -> bool {
159 true
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct QualityConfig {
165 #[serde(default = "default_true")]
167 pub prefer_stereo: bool,
168 #[serde(default = "default_chapter_source")]
170 pub chapter_source: String,
171 #[serde(default = "default_bitrate")]
173 pub default_bitrate: String,
174 #[serde(default = "default_sample_rate")]
176 pub default_sample_rate: String,
177}
178
179impl Default for QualityConfig {
180 fn default() -> Self {
181 Self {
182 prefer_stereo: true,
183 chapter_source: "auto".to_string(),
184 default_bitrate: "auto".to_string(),
185 default_sample_rate: "auto".to_string(),
186 }
187 }
188}
189
190fn default_chapter_source() -> String {
191 "auto".to_string()
192}
193
194fn default_bitrate() -> String {
195 "auto".to_string()
196}
197
198fn default_sample_rate() -> String {
199 "auto".to_string()
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct MetadataConfig {
205 #[serde(default = "default_language")]
207 pub default_language: String,
208 #[serde(default = "default_cover_filenames")]
210 pub cover_filenames: Vec<String>,
211 #[serde(default = "default_auto_extract_cover")]
213 pub auto_extract_cover: bool,
214 #[serde(default)]
216 pub audible: AudibleConfig,
217 #[serde(default)]
219 pub match_mode: MatchMode,
220}
221
222impl Default for MetadataConfig {
223 fn default() -> Self {
224 Self {
225 default_language: "es".to_string(),
226 cover_filenames: vec![
227 "cover.jpg".to_string(),
228 "folder.jpg".to_string(),
229 "cover.png".to_string(),
230 "folder.png".to_string(),
231 ],
232 auto_extract_cover: true,
233 audible: AudibleConfig::default(),
234 match_mode: MatchMode::default(),
235 }
236 }
237}
238
239fn default_language() -> String {
240 "es".to_string()
241}
242
243fn default_cover_filenames() -> Vec<String> {
244 vec![
245 "cover.jpg".to_string(),
246 "folder.jpg".to_string(),
247 "cover.png".to_string(),
248 "folder.png".to_string(),
249 ]
250}
251
252fn default_auto_extract_cover() -> bool {
253 true
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
258#[serde(rename_all = "lowercase")]
259pub enum MatchMode {
260 Disabled,
262 Auto,
264 Interactive,
266}
267
268impl Default for MatchMode {
269 fn default() -> Self {
270 MatchMode::Disabled
271 }
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct AudibleConfig {
277 #[serde(default)]
279 pub enabled: bool,
280 #[serde(default = "default_audible_region")]
282 pub region: String,
283 #[serde(default)]
285 pub auto_match: bool,
286 #[serde(default = "default_true")]
288 pub download_covers: bool,
289 #[serde(default)]
291 pub fetch_chapters: bool,
292 #[serde(default = "default_cache_duration")]
294 pub cache_duration_hours: u64,
295 #[serde(default = "default_rate_limit")]
297 pub rate_limit_per_minute: u32,
298 #[serde(default = "default_api_max_retries")]
300 pub api_max_retries: u8,
301 #[serde(default = "default_api_retry_delay")]
303 pub api_retry_delay_secs: u64,
304 #[serde(default = "default_api_max_retry_delay")]
306 pub api_max_retry_delay_secs: u64,
307}
308
309impl Default for AudibleConfig {
310 fn default() -> Self {
311 Self {
312 enabled: false,
313 region: "us".to_string(),
314 auto_match: false,
315 download_covers: true,
316 fetch_chapters: false,
317 cache_duration_hours: 168, rate_limit_per_minute: 100,
319 api_max_retries: 3,
320 api_retry_delay_secs: 1,
321 api_max_retry_delay_secs: 30,
322 }
323 }
324}
325
326fn default_audible_region() -> String {
327 "us".to_string()
328}
329
330fn default_cache_duration() -> u64 {
331 168 }
333
334fn default_rate_limit() -> u32 {
335 100
336}
337
338fn default_api_max_retries() -> u8 {
339 3
340}
341
342fn default_api_retry_delay() -> u64 {
343 1
344}
345
346fn default_api_max_retry_delay() -> u64 {
347 30
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct OrganizationConfig {
353 #[serde(default = "default_m4b_folder")]
355 pub m4b_folder: String,
356 #[serde(default = "default_convert_folder")]
358 pub convert_folder: String,
359}
360
361impl Default for OrganizationConfig {
362 fn default() -> Self {
363 Self {
364 m4b_folder: "M4B".to_string(),
365 convert_folder: "To_Convert".to_string(),
366 }
367 }
368}
369
370fn default_m4b_folder() -> String {
371 "M4B".to_string()
372}
373
374fn default_convert_folder() -> String {
375 "To_Convert".to_string()
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
380pub struct LoggingConfig {
381 #[serde(default)]
383 pub log_to_file: bool,
384 pub log_file: Option<PathBuf>,
386 #[serde(default = "default_log_level")]
388 pub log_level: String,
389}
390
391impl Default for LoggingConfig {
392 fn default() -> Self {
393 Self {
394 log_to_file: false,
395 log_file: None,
396 log_level: "INFO".to_string(),
397 }
398 }
399}
400
401fn default_log_level() -> String {
402 "INFO".to_string()
403}
404
405fn default_aac_encoder() -> String {
406 "auto".to_string()
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct AdvancedConfig {
412 pub ffmpeg_path: Option<PathBuf>,
414 pub atomic_parsley_path: Option<PathBuf>,
416 pub mp4box_path: Option<PathBuf>,
418 pub temp_directory: Option<PathBuf>,
420 #[serde(skip_serializing_if = "Option::is_none")]
423 pub use_apple_silicon_encoder: Option<bool>,
424 #[serde(default = "default_aac_encoder")]
426 pub aac_encoder: String,
427}
428
429impl Default for AdvancedConfig {
430 fn default() -> Self {
431 Self {
432 ffmpeg_path: None,
433 atomic_parsley_path: None,
434 mp4box_path: None,
435 temp_directory: None,
436 use_apple_silicon_encoder: None,
437 aac_encoder: default_aac_encoder(),
438 }
439 }
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445
446 #[test]
447 fn test_default_config() {
448 let config = Config::default();
449 assert_eq!(config.processing.parallel_workers, 2);
450 assert_eq!(config.quality.prefer_stereo, true);
451 assert_eq!(config.metadata.default_language, "es");
452 }
453
454 #[test]
455 fn test_config_serialization() {
456 let config = Config::default();
457 let yaml = serde_yaml::to_string(&config).unwrap();
458 let deserialized: Config = serde_yaml::from_str(&yaml).unwrap();
459 assert_eq!(deserialized.processing.parallel_workers, 2);
460 }
461}