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}