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}