use super::*;
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct MediaConfig {
#[serde(default)]
pub api_key: Option<String>,
#[serde(default)]
pub base_url: Option<String>,
#[serde(default)]
pub allowed_read_dirs: Vec<String>,
#[serde(default)]
pub image_gen: ImageGenConfig,
#[serde(default)]
pub vision: VisionConfig,
#[serde(default)]
pub tts: TtsConfig,
#[serde(default)]
pub stt: SttConfig,
#[serde(default)]
pub book: BookConfig,
#[serde(default)]
pub podcast: PodcastConfig,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ImageGenConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_image_model")]
pub model: String,
#[serde(default = "default_image_size")]
pub default_size: String,
}
impl Default for ImageGenConfig {
fn default() -> Self {
Self {
enabled: false,
model: default_image_model(),
default_size: default_image_size(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VisionConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_vision_model")]
pub model: String,
#[serde(default = "default_vision_max_tokens")]
pub max_tokens: u32,
}
impl Default for VisionConfig {
fn default() -> Self {
Self {
enabled: false,
model: default_vision_model(),
max_tokens: default_vision_max_tokens(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TtsConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_tts_model")]
pub model: String,
#[serde(default = "default_tts_voice")]
pub default_voice: String,
#[serde(default = "default_tts_format")]
pub default_format: String,
#[serde(default)]
pub base_url: Option<String>,
#[serde(default)]
pub api_key: Option<String>,
}
impl Default for TtsConfig {
fn default() -> Self {
Self {
enabled: false,
model: default_tts_model(),
default_voice: default_tts_voice(),
default_format: default_tts_format(),
base_url: None,
api_key: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SttConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_stt_model")]
pub model: String,
#[serde(default)]
pub language: Option<String>,
}
impl Default for SttConfig {
fn default() -> Self {
Self {
enabled: false,
model: default_stt_model(),
language: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BookConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_book_page_size")]
pub page_size: String,
#[serde(default)]
pub font_path: Option<String>,
}
impl Default for BookConfig {
fn default() -> Self {
Self {
enabled: false,
page_size: default_book_page_size(),
font_path: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PodcastConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_ffmpeg_path")]
pub ffmpeg_path: String,
#[serde(default = "default_tts_voice")]
pub default_voice: String,
#[serde(default = "default_segment_pause_ms")]
pub segment_pause_ms: u32,
#[serde(default)]
pub base_url: Option<String>,
#[serde(default)]
pub api_key: Option<String>,
}
impl Default for PodcastConfig {
fn default() -> Self {
Self {
enabled: false,
ffmpeg_path: default_ffmpeg_path(),
default_voice: default_tts_voice(),
segment_pause_ms: default_segment_pause_ms(),
base_url: None,
api_key: None,
}
}
}
pub(crate) fn default_image_model() -> String {
"gpt-image-1".into()
}
pub(crate) fn default_image_size() -> String {
"1024x1024".into()
}
pub(crate) fn default_vision_model() -> String {
"gpt-4o-mini".into()
}
pub(crate) fn default_vision_max_tokens() -> u32 {
1024
}
pub(crate) fn default_tts_model() -> String {
"tts-1".into()
}
pub(crate) fn default_tts_voice() -> String {
"alloy".into()
}
pub(crate) fn default_tts_format() -> String {
"mp3".into()
}
pub(crate) fn default_stt_model() -> String {
"whisper-1".into()
}
pub(crate) fn default_book_page_size() -> String {
"A4".into()
}
pub(crate) fn default_ffmpeg_path() -> String {
"ffmpeg".into()
}
pub(crate) fn default_segment_pause_ms() -> u32 {
500
}
impl MediaConfig {
pub fn resolve_api_key(&self, fallback_openai_key: Option<&str>) -> Option<String> {
if let Some(k) = self.api_key.as_deref().filter(|s| !s.trim().is_empty()) {
return Some(k.to_string());
}
if let Ok(k) = std::env::var("MICROCLAW_OPENAI_API_KEY") {
if !k.trim().is_empty() {
return Some(k);
}
}
if let Ok(k) = std::env::var("OPENAI_API_KEY") {
if !k.trim().is_empty() {
return Some(k);
}
}
fallback_openai_key
.filter(|s| !s.trim().is_empty())
.map(|s| s.to_string())
}
pub fn resolve_base_url(&self, fallback: Option<&str>) -> String {
if let Some(u) = self.base_url.as_deref().filter(|s| !s.trim().is_empty()) {
return u.trim_end_matches('/').to_string();
}
if let Some(u) = fallback.filter(|s| !s.trim().is_empty()) {
return u.trim_end_matches('/').to_string();
}
"https://api.openai.com/v1".to_string()
}
}