use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default)]
pub directories: DirectoryConfig,
#[serde(default)]
pub performance: PerformanceConfig,
#[serde(default)]
pub processing: ProcessingConfig,
#[serde(default)]
pub quality: QualityConfig,
#[serde(default)]
pub metadata: MetadataConfig,
#[serde(default)]
pub organization: OrganizationConfig,
#[serde(default)]
pub logging: LoggingConfig,
#[serde(default)]
pub advanced: AdvancedConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
directories: DirectoryConfig::default(),
performance: PerformanceConfig::default(),
processing: ProcessingConfig::default(),
quality: QualityConfig::default(),
metadata: MetadataConfig::default(),
organization: OrganizationConfig::default(),
logging: LoggingConfig::default(),
advanced: AdvancedConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DirectoryConfig {
pub source: Option<PathBuf>,
#[serde(default = "default_output")]
pub output: String,
}
impl Default for DirectoryConfig {
fn default() -> Self {
Self {
source: None,
output: "same_as_source".to_string(),
}
}
}
fn default_output() -> String {
"same_as_source".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceConfig {
#[serde(default = "default_max_concurrent_encodes")]
pub max_concurrent_encodes: String,
#[serde(default = "default_true")]
pub enable_parallel_encoding: bool,
#[serde(default = "default_encoding_preset")]
pub encoding_preset: String,
#[serde(default = "default_max_concurrent_files_per_book")]
pub max_concurrent_files_per_book: String,
}
impl Default for PerformanceConfig {
fn default() -> Self {
Self {
max_concurrent_encodes: "auto".to_string(),
enable_parallel_encoding: true,
encoding_preset: "balanced".to_string(),
max_concurrent_files_per_book: "8".to_string(),
}
}
}
fn default_max_concurrent_encodes() -> String {
"auto".to_string()
}
fn default_encoding_preset() -> String {
"balanced".to_string()
}
fn default_max_concurrent_files_per_book() -> String {
"8".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessingConfig {
#[serde(default = "default_parallel_workers")]
pub parallel_workers: u8,
#[serde(default = "default_true")]
pub skip_existing: bool,
#[serde(default)]
pub force_reprocess: bool,
#[serde(default)]
pub normalize_existing: bool,
#[serde(default)]
pub keep_temp_files: bool,
#[serde(default = "default_max_retries")]
pub max_retries: u8,
#[serde(default = "default_retry_delay")]
pub retry_delay: u64,
}
impl Default for ProcessingConfig {
fn default() -> Self {
Self {
parallel_workers: 2,
skip_existing: true,
force_reprocess: false,
normalize_existing: false,
keep_temp_files: false,
max_retries: 2,
retry_delay: 1,
}
}
}
fn default_max_retries() -> u8 {
2
}
fn default_retry_delay() -> u64 {
1
}
fn default_parallel_workers() -> u8 {
2
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityConfig {
#[serde(default = "default_true")]
pub prefer_stereo: bool,
#[serde(default = "default_chapter_source")]
pub chapter_source: String,
#[serde(default = "default_bitrate")]
pub default_bitrate: String,
#[serde(default = "default_sample_rate")]
pub default_sample_rate: String,
}
impl Default for QualityConfig {
fn default() -> Self {
Self {
prefer_stereo: true,
chapter_source: "auto".to_string(),
default_bitrate: "auto".to_string(),
default_sample_rate: "auto".to_string(),
}
}
}
fn default_chapter_source() -> String {
"auto".to_string()
}
fn default_bitrate() -> String {
"auto".to_string()
}
fn default_sample_rate() -> String {
"auto".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetadataConfig {
#[serde(default = "default_language")]
pub default_language: String,
#[serde(default = "default_cover_filenames")]
pub cover_filenames: Vec<String>,
#[serde(default = "default_auto_extract_cover")]
pub auto_extract_cover: bool,
#[serde(default)]
pub audible: AudibleConfig,
#[serde(default)]
pub match_mode: MatchMode,
}
impl Default for MetadataConfig {
fn default() -> Self {
Self {
default_language: "es".to_string(),
cover_filenames: vec![
"cover.jpg".to_string(),
"folder.jpg".to_string(),
"cover.png".to_string(),
"folder.png".to_string(),
],
auto_extract_cover: true,
audible: AudibleConfig::default(),
match_mode: MatchMode::default(),
}
}
}
fn default_language() -> String {
"es".to_string()
}
fn default_cover_filenames() -> Vec<String> {
vec![
"cover.jpg".to_string(),
"folder.jpg".to_string(),
"cover.png".to_string(),
"folder.png".to_string(),
]
}
fn default_auto_extract_cover() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum MatchMode {
Disabled,
Auto,
Interactive,
}
impl Default for MatchMode {
fn default() -> Self {
MatchMode::Disabled
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudibleConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_audible_region")]
pub region: String,
#[serde(default)]
pub auto_match: bool,
#[serde(default = "default_true")]
pub download_covers: bool,
#[serde(default)]
pub fetch_chapters: bool,
#[serde(default = "default_cache_duration")]
pub cache_duration_hours: u64,
#[serde(default = "default_rate_limit")]
pub rate_limit_per_minute: u32,
#[serde(default = "default_api_max_retries")]
pub api_max_retries: u8,
#[serde(default = "default_api_retry_delay")]
pub api_retry_delay_secs: u64,
#[serde(default = "default_api_max_retry_delay")]
pub api_max_retry_delay_secs: u64,
}
impl Default for AudibleConfig {
fn default() -> Self {
Self {
enabled: false,
region: "us".to_string(),
auto_match: false,
download_covers: true,
fetch_chapters: false,
cache_duration_hours: 168, rate_limit_per_minute: 100,
api_max_retries: 3,
api_retry_delay_secs: 1,
api_max_retry_delay_secs: 30,
}
}
}
fn default_audible_region() -> String {
"us".to_string()
}
fn default_cache_duration() -> u64 {
168 }
fn default_rate_limit() -> u32 {
100
}
fn default_api_max_retries() -> u8 {
3
}
fn default_api_retry_delay() -> u64 {
1
}
fn default_api_max_retry_delay() -> u64 {
30
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrganizationConfig {
#[serde(default = "default_m4b_folder")]
pub m4b_folder: String,
#[serde(default = "default_convert_folder")]
pub convert_folder: String,
}
impl Default for OrganizationConfig {
fn default() -> Self {
Self {
m4b_folder: "M4B".to_string(),
convert_folder: "To_Convert".to_string(),
}
}
}
fn default_m4b_folder() -> String {
"M4B".to_string()
}
fn default_convert_folder() -> String {
"To_Convert".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
#[serde(default)]
pub log_to_file: bool,
pub log_file: Option<PathBuf>,
#[serde(default = "default_log_level")]
pub log_level: String,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
log_to_file: false,
log_file: None,
log_level: "INFO".to_string(),
}
}
}
fn default_log_level() -> String {
"INFO".to_string()
}
fn default_aac_encoder() -> String {
"auto".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdvancedConfig {
pub ffmpeg_path: Option<PathBuf>,
pub atomic_parsley_path: Option<PathBuf>,
pub mp4box_path: Option<PathBuf>,
pub temp_directory: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub use_apple_silicon_encoder: Option<bool>,
#[serde(default = "default_aac_encoder")]
pub aac_encoder: String,
}
impl Default for AdvancedConfig {
fn default() -> Self {
Self {
ffmpeg_path: None,
atomic_parsley_path: None,
mp4box_path: None,
temp_directory: None,
use_apple_silicon_encoder: None,
aac_encoder: default_aac_encoder(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.processing.parallel_workers, 2);
assert_eq!(config.quality.prefer_stereo, true);
assert_eq!(config.metadata.default_language, "es");
}
#[test]
fn test_config_serialization() {
let config = Config::default();
let yaml = serde_yaml::to_string(&config).unwrap();
let deserialized: Config = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(deserialized.processing.parallel_workers, 2);
}
}