suno_core/config/
effective.rs1use std::collections::BTreeMap;
4
5use crate::naming::CharacterSet;
6use crate::vocab::{AudioFormat, StemFormat, VideoCoverRetention, WebpEncodeSettings};
7
8use super::shape::{AreasConfig, Settings};
9
10#[derive(Debug, Default)]
17pub struct FlagOverrides {
18 pub token: Option<String>,
19 pub settings: Settings,
20}
21
22#[derive(Debug, Clone, PartialEq)]
24pub struct EffectiveSettings {
25 pub token: Option<String>,
27 pub stored_token: Option<String>,
29 pub token_command: Option<String>,
31 pub account_id: Option<String>,
33 pub format: AudioFormat,
34 pub concurrency: u32,
35 pub retries: u32,
36 pub min_newest: u32,
37 pub animated_covers: bool,
38 pub raw_animated_cover: bool,
41 pub video_cover_retention: VideoCoverRetention,
42 pub animated_cover_webp: WebpEncodeSettings,
43 pub details_sidecar: bool,
44 pub lyrics_sidecar: bool,
45 pub lrc_sidecar: bool,
46 pub video_mp4: bool,
47 pub download_stems: bool,
48 pub stem_format: StemFormat,
49 pub naming_template: String,
50 pub character_set: CharacterSet,
51 pub areas: Option<AreasConfig>,
53 pub album_overrides: BTreeMap<String, String>,
57 pub lead_tracks: Vec<String>,
62 pub number_singletons: bool,
65}
66
67impl EffectiveSettings {
68 pub fn requires_ffmpeg(&self) -> bool {
74 matches!(self.format, AudioFormat::Flac | AudioFormat::Alac) || self.animated_covers
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::config::Config;
82 use crate::config::fixtures::{no_env, no_flags};
83
84 #[test]
85 fn audio_format_display_roundtrip() {
86 for fmt in [
87 AudioFormat::Mp3,
88 AudioFormat::Flac,
89 AudioFormat::Wav,
90 AudioFormat::Alac,
91 ] {
92 let s = fmt.to_string();
93 assert_eq!(s.parse::<AudioFormat>().unwrap(), fmt);
94 }
95 }
96
97 #[test]
98 fn audio_format_ext() {
99 assert_eq!(AudioFormat::Mp3.ext(), "mp3");
100 assert_eq!(AudioFormat::Flac.ext(), "flac");
101 assert_eq!(AudioFormat::Wav.ext(), "wav");
102 assert_eq!(AudioFormat::Alac.ext(), "m4a");
103 }
104
105 fn base_settings(format: AudioFormat) -> EffectiveSettings {
106 let toml = "[accounts.a]\n";
107 let cfg = Config::from_toml(toml).unwrap();
108 let mut eff = cfg.resolve("a", None, &no_env(), &no_flags()).unwrap();
109 eff.format = format;
110 eff
111 }
112
113 #[test]
114 fn requires_ffmpeg_flac_always_needs_it() {
115 let mut eff = base_settings(AudioFormat::Flac);
116 eff.animated_covers = false;
117 assert!(eff.requires_ffmpeg());
118 eff.animated_covers = true;
119 assert!(eff.requires_ffmpeg());
120 }
121
122 #[test]
123 fn requires_ffmpeg_alac_always_needs_it() {
124 let mut eff = base_settings(AudioFormat::Alac);
125 eff.animated_covers = false;
126 assert!(eff.requires_ffmpeg(), "alac transcodes, so needs ffmpeg");
127 }
128
129 #[test]
130 fn requires_ffmpeg_mp3_needs_it_only_for_animated_webp() {
131 let mut eff = base_settings(AudioFormat::Mp3);
132 assert!(!eff.requires_ffmpeg(), "mp3 + no covers = no ffmpeg");
133 eff.animated_covers = true;
134 assert!(eff.requires_ffmpeg(), "mp3 + animated webp = needs ffmpeg");
135 eff.raw_animated_cover = true;
136 assert!(
137 eff.requires_ffmpeg(),
138 "mp3 + both (webp + raw mp4) = needs ffmpeg"
139 );
140 eff.animated_covers = false;
141 assert!(!eff.requires_ffmpeg(), "mp3 + raw mp4 only = no ffmpeg");
142 }
143}