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)]
213 pub audible: AudibleConfig,
214 #[serde(default)]
216 pub match_mode: MatchMode,
217}
218
219impl Default for MetadataConfig {
220 fn default() -> Self {
221 Self {
222 default_language: "es".to_string(),
223 cover_filenames: vec![
224 "cover.jpg".to_string(),
225 "folder.jpg".to_string(),
226 "cover.png".to_string(),
227 "folder.png".to_string(),
228 ],
229 audible: AudibleConfig::default(),
230 match_mode: MatchMode::default(),
231 }
232 }
233}
234
235fn default_language() -> String {
236 "es".to_string()
237}
238
239fn default_cover_filenames() -> Vec<String> {
240 vec![
241 "cover.jpg".to_string(),
242 "folder.jpg".to_string(),
243 "cover.png".to_string(),
244 "folder.png".to_string(),
245 ]
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
250#[serde(rename_all = "lowercase")]
251pub enum MatchMode {
252 Disabled,
254 Auto,
256 Interactive,
258}
259
260impl Default for MatchMode {
261 fn default() -> Self {
262 MatchMode::Disabled
263 }
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct AudibleConfig {
269 #[serde(default)]
271 pub enabled: bool,
272 #[serde(default = "default_audible_region")]
274 pub region: String,
275 #[serde(default)]
277 pub auto_match: bool,
278 #[serde(default = "default_true")]
280 pub download_covers: bool,
281 #[serde(default = "default_cache_duration")]
283 pub cache_duration_hours: u64,
284 #[serde(default = "default_rate_limit")]
286 pub rate_limit_per_minute: u32,
287}
288
289impl Default for AudibleConfig {
290 fn default() -> Self {
291 Self {
292 enabled: false,
293 region: "us".to_string(),
294 auto_match: false,
295 download_covers: true,
296 cache_duration_hours: 168, rate_limit_per_minute: 100,
298 }
299 }
300}
301
302fn default_audible_region() -> String {
303 "us".to_string()
304}
305
306fn default_cache_duration() -> u64 {
307 168 }
309
310fn default_rate_limit() -> u32 {
311 100
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct OrganizationConfig {
317 #[serde(default = "default_m4b_folder")]
319 pub m4b_folder: String,
320 #[serde(default = "default_convert_folder")]
322 pub convert_folder: String,
323}
324
325impl Default for OrganizationConfig {
326 fn default() -> Self {
327 Self {
328 m4b_folder: "M4B".to_string(),
329 convert_folder: "To_Convert".to_string(),
330 }
331 }
332}
333
334fn default_m4b_folder() -> String {
335 "M4B".to_string()
336}
337
338fn default_convert_folder() -> String {
339 "To_Convert".to_string()
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct LoggingConfig {
345 #[serde(default)]
347 pub log_to_file: bool,
348 pub log_file: Option<PathBuf>,
350 #[serde(default = "default_log_level")]
352 pub log_level: String,
353}
354
355impl Default for LoggingConfig {
356 fn default() -> Self {
357 Self {
358 log_to_file: false,
359 log_file: None,
360 log_level: "INFO".to_string(),
361 }
362 }
363}
364
365fn default_log_level() -> String {
366 "INFO".to_string()
367}
368
369fn default_aac_encoder() -> String {
370 "auto".to_string()
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct AdvancedConfig {
376 pub ffmpeg_path: Option<PathBuf>,
378 pub atomic_parsley_path: Option<PathBuf>,
380 pub mp4box_path: Option<PathBuf>,
382 pub temp_directory: Option<PathBuf>,
384 #[serde(skip_serializing_if = "Option::is_none")]
387 pub use_apple_silicon_encoder: Option<bool>,
388 #[serde(default = "default_aac_encoder")]
390 pub aac_encoder: String,
391}
392
393impl Default for AdvancedConfig {
394 fn default() -> Self {
395 Self {
396 ffmpeg_path: None,
397 atomic_parsley_path: None,
398 mp4box_path: None,
399 temp_directory: None,
400 use_apple_silicon_encoder: None,
401 aac_encoder: default_aac_encoder(),
402 }
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn test_default_config() {
412 let config = Config::default();
413 assert_eq!(config.processing.parallel_workers, 2);
414 assert_eq!(config.quality.prefer_stereo, true);
415 assert_eq!(config.metadata.default_language, "es");
416 }
417
418 #[test]
419 fn test_config_serialization() {
420 let config = Config::default();
421 let yaml = serde_yaml::to_string(&config).unwrap();
422 let deserialized: Config = serde_yaml::from_str(&yaml).unwrap();
423 assert_eq!(deserialized.processing.parallel_workers, 2);
424 }
425}