voicepeak_cli/
config.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::fs;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct EmotionParam {
8    pub name: String,
9    pub value: i32,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct VoicePreset {
14    pub name: String,
15    pub narrator: String,
16    pub emotions: Vec<EmotionParam>,
17    pub pitch: Option<i32>,
18    pub speed: Option<i32>,
19}
20
21impl VoicePreset {
22    pub fn get_emotion_string(&self) -> String {
23        if self.emotions.is_empty() {
24            String::new()
25        } else {
26            self.emotions
27                .iter()
28                .map(|e| format!("{}={}", e.name, e.value))
29                .collect::<Vec<_>>()
30                .join(",")
31        }
32    }
33}
34
35impl EmotionParam {
36    pub fn new(name: &str, value: i32) -> Self {
37        Self {
38            name: name.to_string(),
39            value,
40        }
41    }
42}
43
44#[derive(Debug, Default, Serialize, Deserialize)]
45pub struct Config {
46    pub default_preset: Option<String>,
47    pub presets: Vec<VoicePreset>,
48}
49
50pub fn get_config_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
51    let home_dir = dirs::home_dir().ok_or("Could not find home directory")?;
52    let config_dir = home_dir.join(".config").join("vp");
53
54    if !config_dir.exists() {
55        fs::create_dir_all(&config_dir)?;
56    }
57
58    Ok(config_dir.join("config.toml"))
59}
60
61pub fn load_config() -> Result<Config, Box<dyn std::error::Error>> {
62    let config_path = get_config_path()?;
63
64    if !config_path.exists() {
65        let default_config = Config::default();
66        save_config(&default_config)?;
67        return Ok(default_config);
68    }
69
70    let content = fs::read_to_string(&config_path)?;
71    let config: Config = toml::from_str(&content)?;
72    Ok(config)
73}
74
75pub fn save_config(config: &Config) -> Result<(), Box<dyn std::error::Error>> {
76    let config_path = get_config_path()?;
77    let content = toml::to_string_pretty(config)?;
78    fs::write(&config_path, content)?;
79    Ok(())
80}
81
82pub fn get_presets_map(config: &Config) -> HashMap<String, VoicePreset> {
83    config
84        .presets
85        .iter()
86        .map(|preset| (preset.name.clone(), preset.clone()))
87        .collect()
88}
89
90pub fn list_presets(config: &Config) {
91    println!("Available presets:");
92    for preset in &config.presets {
93        let marker = if Some(&preset.name) == config.default_preset.as_ref() {
94            " (default)"
95        } else {
96            ""
97        };
98        let emotion_display = if preset.emotions.is_empty() {
99            "normal".to_string()
100        } else {
101            preset.get_emotion_string()
102        };
103        let pitch_display = preset
104            .pitch
105            .map(|p| format!(", pitch={}", p))
106            .unwrap_or_default();
107        let speed_display = preset
108            .speed
109            .map(|s| format!(", speed={}", s))
110            .unwrap_or_default();
111        println!(
112            "  {} - {} ({}{}{}){}",
113            preset.name, preset.narrator, emotion_display, pitch_display, speed_display, marker
114        );
115    }
116
117    if let Some(ref default) = config.default_preset {
118        println!("\nDefault preset: {}", default);
119    } else {
120        println!("\nNo default preset set");
121    }
122}