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