@@ -23,22 +23,67 @@ pub struct DatabaseConfig {
2323
2424#[ derive( Clone , Debug , Deserialize , Serialize ) ]
2525pub struct MediaPathConfig {
26+ pub name : Option < String > ,
2627 pub path : String ,
2728 pub original_path : Option < String > ,
2829 pub original_extension : Option < String > ,
2930}
3031
32+ #[ derive( Clone , Debug , Deserialize , Serialize ) ]
33+ pub struct MediaSourceConfig {
34+ pub name : String ,
35+ pub path : String ,
36+ #[ serde( rename = "originalPath" ) ]
37+ pub original_path : Option < String > ,
38+ #[ serde( rename = "originalExtension" ) ]
39+ pub original_extension : Option < String > ,
40+ }
41+
42+ #[ derive( Clone , Debug , Deserialize , Serialize ) ]
43+ pub struct MediaSourcePathsConfig {
44+ pub sources : Option < Vec < MediaSourceConfig > > ,
45+ #[ serde( skip) ]
46+ pub legacy_string : Option < String > ,
47+ }
48+
3149#[ derive( Clone , Debug , Deserialize , Serialize ) ]
3250pub struct MediaConfig {
51+ #[ serde( rename = "mediaSourcePaths" ) ]
52+ pub media_source_paths_config : MediaSourcePathsConfig ,
3353 pub source_paths : Vec < MediaPathConfig > ,
54+ #[ serde( rename = "exportBasePath" ) ]
3455 pub export_base_path : String ,
56+ #[ serde( rename = "thumbnailPath" ) ]
3557 pub thumbnail_path : String ,
3658}
3759
60+ impl MediaConfig {
61+ // Convert MediaSourcePathsConfig to Vec<MediaPathConfig>
62+ pub fn convert_source_paths ( & mut self ) {
63+ if let Some ( sources) = & self . media_source_paths_config . sources {
64+ // Convert each MediaSourceConfig to MediaPathConfig
65+ self . source_paths = sources. iter ( ) . map ( |source| {
66+ MediaPathConfig {
67+ name : Some ( source. name . clone ( ) ) ,
68+ path : source. path . clone ( ) ,
69+ original_path : source. original_path . clone ( ) ,
70+ original_extension : source. original_extension . clone ( ) ,
71+ }
72+ } ) . collect ( ) ;
73+ } else if let Some ( legacy_string) = & self . media_source_paths_config . legacy_string {
74+ // If we have a legacy string, parse it
75+ self . source_paths = parse_comma_separated_paths_from_string ( legacy_string) ;
76+ } else {
77+ // If no sources are defined, use environment variables as fallback
78+ self . source_paths = parse_comma_separated_paths ( "MEDIA_SOURCE_PATHS" ) ;
79+ }
80+ }
81+ }
82+
3883impl Config {
3984 pub fn load ( ) -> Result < Self > {
4085 // Default configuration
41- let config = Config {
86+ let mut config = Config {
4287 server : ServerConfig {
4388 host : env:: var ( "SERVER_HOST" ) . unwrap_or_else ( |_| "127.0.0.1" . to_string ( ) ) ,
4489 port : env:: var ( "SERVER_PORT" )
@@ -54,26 +99,179 @@ impl Config {
5499 . unwrap_or ( 5 ) ,
55100 } ,
56101 media : MediaConfig {
57- source_paths : parse_comma_separated_paths ( "MEDIA_SOURCE_PATHS" ) ,
102+ media_source_paths_config : MediaSourcePathsConfig {
103+ sources : None ,
104+ legacy_string : env:: var ( "MEDIA_SOURCE_PATHS" ) . ok ( ) ,
105+ } ,
106+ source_paths : Vec :: new ( ) , // Will be populated in convert_source_paths
58107 export_base_path : env:: var ( "EXPORT_BASE_PATH" )
59108 . unwrap_or_else ( |_| "./exports" . to_string ( ) ) ,
60109 thumbnail_path : env:: var ( "THUMBNAIL_PATH" )
61110 . unwrap_or_else ( |_| "./thumbnails" . to_string ( ) ) ,
62111 } ,
63112 } ;
64113
114+ // Convert media_source_paths_config to source_paths
115+ config. media . convert_source_paths ( ) ;
116+
65117 Ok ( config)
66118 }
67119}
68120
121+ fn parse_comma_separated_paths_from_string ( paths_str : & str ) -> Vec < MediaPathConfig > {
122+ paths_str
123+ . split ( ',' )
124+ . map ( |s| s. trim ( ) . to_string ( ) )
125+ . map ( |path_config| {
126+ // Check if the path contains a named section (e.g., "bmpcc:")
127+ if let Some ( colon_pos) = path_config. find ( ':' ) {
128+ let name = path_config[ ..colon_pos] . trim ( ) . to_string ( ) ;
129+ let config_str = path_config[ colon_pos + 1 ..] . trim ( ) . to_string ( ) ;
130+
131+ // Split the configuration by semicolons
132+ let parts: Vec < & str > = config_str. split ( ';' ) . collect ( ) ;
133+
134+ if parts. is_empty ( ) {
135+ // Invalid configuration, return a default
136+ return MediaPathConfig {
137+ name : Some ( name) ,
138+ path : "./media" . to_string ( ) ,
139+ original_path : None ,
140+ original_extension : None ,
141+ } ;
142+ }
143+
144+ let path = parts[ 0 ] . to_string ( ) ;
145+
146+ // Parse original_path if provided
147+ let original_path = parts. get ( 1 )
148+ . filter ( |& p| !p. is_empty ( ) )
149+ . map ( |p| p. to_string ( ) ) ;
150+
151+ // Parse original_extension if provided
152+ let original_extension = parts. get ( 2 )
153+ . filter ( |& e| !e. is_empty ( ) )
154+ . map ( |e| e. to_string ( ) ) ;
155+
156+ // If original_path is provided without extension, use the same extension as path
157+ let original_extension = if original_path. is_some ( ) && original_extension. is_none ( ) {
158+ // Extract extension from path
159+ std:: path:: Path :: new ( & path)
160+ . extension ( )
161+ . and_then ( |ext| ext. to_str ( ) )
162+ . map ( |ext| ext. to_string ( ) )
163+ } else {
164+ original_extension
165+ } ;
166+
167+ MediaPathConfig {
168+ name : Some ( name) ,
169+ path,
170+ original_path,
171+ original_extension,
172+ }
173+ }
174+ // Backward compatibility: Check if the path contains configuration options without a name
175+ else if path_config. contains ( ';' ) {
176+ let parts: Vec < & str > = path_config. split ( ';' ) . collect ( ) ;
177+ let path = parts[ 0 ] . to_string ( ) ;
178+
179+ // Parse original_path if provided
180+ let original_path = parts. get ( 1 )
181+ . filter ( |& p| !p. is_empty ( ) )
182+ . map ( |p| p. to_string ( ) ) ;
183+
184+ // Parse original_extension if provided
185+ let original_extension = parts. get ( 2 )
186+ . filter ( |& e| !e. is_empty ( ) )
187+ . map ( |e| e. to_string ( ) ) ;
188+
189+ // If original_path is provided without extension, use the same extension as path
190+ let original_extension = if original_path. is_some ( ) && original_extension. is_none ( ) {
191+ // Extract extension from path
192+ std:: path:: Path :: new ( & path)
193+ . extension ( )
194+ . and_then ( |ext| ext. to_str ( ) )
195+ . map ( |ext| ext. to_string ( ) )
196+ } else {
197+ original_extension
198+ } ;
199+
200+ MediaPathConfig {
201+ name : None ,
202+ path,
203+ original_path,
204+ original_extension,
205+ }
206+ } else {
207+ // Simple path without additional configuration
208+ MediaPathConfig {
209+ name : None ,
210+ path : path_config,
211+ original_path : None ,
212+ original_extension : None ,
213+ }
214+ }
215+ } )
216+ . collect ( )
217+ }
218+
69219fn parse_comma_separated_paths ( env_var : & str ) -> Vec < MediaPathConfig > {
70220 env:: var ( env_var)
71221 . unwrap_or_else ( |_| "./media" . to_string ( ) )
72222 . split ( ',' )
73223 . map ( |s| s. trim ( ) . to_string ( ) )
74224 . map ( |path_config| {
75- // Check if the path contains configuration options
76- if path_config. contains ( ';' ) {
225+ // Check if the path contains a named section (e.g., "bmpcc:")
226+ if let Some ( colon_pos) = path_config. find ( ':' ) {
227+ let name = path_config[ ..colon_pos] . trim ( ) . to_string ( ) ;
228+ let config_str = path_config[ colon_pos + 1 ..] . trim ( ) . to_string ( ) ;
229+
230+ // Split the configuration by semicolons
231+ let parts: Vec < & str > = config_str. split ( ';' ) . collect ( ) ;
232+
233+ if parts. is_empty ( ) {
234+ // Invalid configuration, return a default
235+ return MediaPathConfig {
236+ name : Some ( name) ,
237+ path : "./media" . to_string ( ) ,
238+ original_path : None ,
239+ original_extension : None ,
240+ } ;
241+ }
242+
243+ let path = parts[ 0 ] . to_string ( ) ;
244+
245+ // Parse original_path if provided
246+ let original_path = parts. get ( 1 )
247+ . filter ( |& p| !p. is_empty ( ) )
248+ . map ( |p| p. to_string ( ) ) ;
249+
250+ // Parse original_extension if provided
251+ let original_extension = parts. get ( 2 )
252+ . filter ( |& e| !e. is_empty ( ) )
253+ . map ( |e| e. to_string ( ) ) ;
254+
255+ // If original_path is provided without extension, use the same extension as path
256+ let original_extension = if original_path. is_some ( ) && original_extension. is_none ( ) {
257+ // Extract extension from path
258+ std:: path:: Path :: new ( & path)
259+ . extension ( )
260+ . and_then ( |ext| ext. to_str ( ) )
261+ . map ( |ext| ext. to_string ( ) )
262+ } else {
263+ original_extension
264+ } ;
265+
266+ MediaPathConfig {
267+ name : Some ( name) ,
268+ path,
269+ original_path,
270+ original_extension,
271+ }
272+ }
273+ // Backward compatibility: Check if the path contains configuration options without a name
274+ else if path_config. contains ( ';' ) {
77275 let parts: Vec < & str > = path_config. split ( ';' ) . collect ( ) ;
78276 let path = parts[ 0 ] . to_string ( ) ;
79277
@@ -87,14 +285,27 @@ fn parse_comma_separated_paths(env_var: &str) -> Vec<MediaPathConfig> {
87285 . filter ( |& e| !e. is_empty ( ) )
88286 . map ( |e| e. to_string ( ) ) ;
89287
288+ // If original_path is provided without extension, use the same extension as path
289+ let original_extension = if original_path. is_some ( ) && original_extension. is_none ( ) {
290+ // Extract extension from path
291+ std:: path:: Path :: new ( & path)
292+ . extension ( )
293+ . and_then ( |ext| ext. to_str ( ) )
294+ . map ( |ext| ext. to_string ( ) )
295+ } else {
296+ original_extension
297+ } ;
298+
90299 MediaPathConfig {
300+ name : None ,
91301 path,
92302 original_path,
93303 original_extension,
94304 }
95305 } else {
96306 // Simple path without additional configuration
97307 MediaPathConfig {
308+ name : None ,
98309 path : path_config,
99310 original_path : None ,
100311 original_extension : None ,
0 commit comments