microclaw_engine/config/
media.rs1use super::*;
2
3#[derive(Clone, Debug, Serialize, Deserialize, Default)]
12pub struct MediaConfig {
13 #[serde(default)]
16 pub api_key: Option<String>,
17 #[serde(default)]
20 pub base_url: Option<String>,
21 #[serde(default)]
25 pub allowed_read_dirs: Vec<String>,
26 #[serde(default)]
27 pub image_gen: ImageGenConfig,
28 #[serde(default)]
29 pub vision: VisionConfig,
30 #[serde(default)]
31 pub tts: TtsConfig,
32 #[serde(default)]
33 pub stt: SttConfig,
34 #[serde(default)]
35 pub book: BookConfig,
36 #[serde(default)]
37 pub podcast: PodcastConfig,
38}
39
40#[derive(Clone, Debug, Serialize, Deserialize)]
41pub struct ImageGenConfig {
42 #[serde(default)]
43 pub enabled: bool,
44 #[serde(default = "default_image_model")]
45 pub model: String,
46 #[serde(default = "default_image_size")]
47 pub default_size: String,
48}
49
50impl Default for ImageGenConfig {
51 fn default() -> Self {
52 Self {
53 enabled: false,
54 model: default_image_model(),
55 default_size: default_image_size(),
56 }
57 }
58}
59
60#[derive(Clone, Debug, Serialize, Deserialize)]
61pub struct VisionConfig {
62 #[serde(default)]
63 pub enabled: bool,
64 #[serde(default = "default_vision_model")]
65 pub model: String,
66 #[serde(default = "default_vision_max_tokens")]
67 pub max_tokens: u32,
68}
69
70impl Default for VisionConfig {
71 fn default() -> Self {
72 Self {
73 enabled: false,
74 model: default_vision_model(),
75 max_tokens: default_vision_max_tokens(),
76 }
77 }
78}
79
80#[derive(Clone, Debug, Serialize, Deserialize)]
81pub struct TtsConfig {
82 #[serde(default)]
83 pub enabled: bool,
84 #[serde(default = "default_tts_model")]
85 pub model: String,
86 #[serde(default = "default_tts_voice")]
87 pub default_voice: String,
88 #[serde(default = "default_tts_format")]
89 pub default_format: String,
90 #[serde(default)]
93 pub base_url: Option<String>,
94 #[serde(default)]
97 pub api_key: Option<String>,
98}
99
100impl Default for TtsConfig {
101 fn default() -> Self {
102 Self {
103 enabled: false,
104 model: default_tts_model(),
105 default_voice: default_tts_voice(),
106 default_format: default_tts_format(),
107 base_url: None,
108 api_key: None,
109 }
110 }
111}
112
113#[derive(Clone, Debug, Serialize, Deserialize)]
114pub struct SttConfig {
115 #[serde(default)]
116 pub enabled: bool,
117 #[serde(default = "default_stt_model")]
118 pub model: String,
119 #[serde(default)]
120 pub language: Option<String>,
121}
122
123impl Default for SttConfig {
124 fn default() -> Self {
125 Self {
126 enabled: false,
127 model: default_stt_model(),
128 language: None,
129 }
130 }
131}
132
133#[derive(Clone, Debug, Serialize, Deserialize)]
139pub struct BookConfig {
140 #[serde(default)]
141 pub enabled: bool,
142 #[serde(default = "default_book_page_size")]
144 pub page_size: String,
145 #[serde(default)]
150 pub font_path: Option<String>,
151}
152
153impl Default for BookConfig {
154 fn default() -> Self {
155 Self {
156 enabled: false,
157 page_size: default_book_page_size(),
158 font_path: None,
159 }
160 }
161}
162
163#[derive(Clone, Debug, Serialize, Deserialize)]
171pub struct PodcastConfig {
172 #[serde(default)]
173 pub enabled: bool,
174 #[serde(default = "default_ffmpeg_path")]
176 pub ffmpeg_path: String,
177 #[serde(default = "default_tts_voice")]
179 pub default_voice: String,
180 #[serde(default = "default_segment_pause_ms")]
182 pub segment_pause_ms: u32,
183 #[serde(default)]
186 pub base_url: Option<String>,
187 #[serde(default)]
190 pub api_key: Option<String>,
191}
192
193impl Default for PodcastConfig {
194 fn default() -> Self {
195 Self {
196 enabled: false,
197 ffmpeg_path: default_ffmpeg_path(),
198 default_voice: default_tts_voice(),
199 segment_pause_ms: default_segment_pause_ms(),
200 base_url: None,
201 api_key: None,
202 }
203 }
204}
205
206pub(crate) fn default_image_model() -> String {
207 "gpt-image-1".into()
208}
209
210pub(crate) fn default_image_size() -> String {
211 "1024x1024".into()
212}
213
214pub(crate) fn default_vision_model() -> String {
215 "gpt-4o-mini".into()
216}
217
218pub(crate) fn default_vision_max_tokens() -> u32 {
219 1024
220}
221
222pub(crate) fn default_tts_model() -> String {
223 "tts-1".into()
224}
225
226pub(crate) fn default_tts_voice() -> String {
227 "alloy".into()
228}
229
230pub(crate) fn default_tts_format() -> String {
231 "mp3".into()
232}
233
234pub(crate) fn default_stt_model() -> String {
235 "whisper-1".into()
236}
237
238pub(crate) fn default_book_page_size() -> String {
239 "A4".into()
240}
241
242pub(crate) fn default_ffmpeg_path() -> String {
243 "ffmpeg".into()
244}
245
246pub(crate) fn default_segment_pause_ms() -> u32 {
247 500
248}
249
250impl MediaConfig {
251 pub fn resolve_api_key(&self, fallback_openai_key: Option<&str>) -> Option<String> {
254 if let Some(k) = self.api_key.as_deref().filter(|s| !s.trim().is_empty()) {
255 return Some(k.to_string());
256 }
257 if let Ok(k) = std::env::var("MICROCLAW_OPENAI_API_KEY") {
258 if !k.trim().is_empty() {
259 return Some(k);
260 }
261 }
262 if let Ok(k) = std::env::var("OPENAI_API_KEY") {
263 if !k.trim().is_empty() {
264 return Some(k);
265 }
266 }
267 fallback_openai_key
268 .filter(|s| !s.trim().is_empty())
269 .map(|s| s.to_string())
270 }
271
272 pub fn resolve_base_url(&self, fallback: Option<&str>) -> String {
275 if let Some(u) = self.base_url.as_deref().filter(|s| !s.trim().is_empty()) {
276 return u.trim_end_matches('/').to_string();
277 }
278 if let Some(u) = fallback.filter(|s| !s.trim().is_empty()) {
279 return u.trim_end_matches('/').to_string();
280 }
281 "https://api.openai.com/v1".to_string()
282 }
283}