qobuz_api_rust/metadata/extractor.rs
1use std::collections::{HashMap, HashSet};
2
3use crate::models::{Album, Artist, Track};
4
5/// Extracts comprehensive metadata from Qobuz API response objects into a key-value map.
6///
7/// This function takes track, album, and artist information from the Qobuz API and
8/// extracts relevant metadata fields into a standardized format. The resulting
9/// HashMap contains common audio file metadata tags that can be used for various
10/// purposes such as embedding in audio files, displaying in applications, or
11/// processing in audio workflows.
12///
13/// The function handles multiple sources for composer information, deduplicates
14/// entries, and follows the same logic as the tag embedding function to ensure
15/// consistency across the library.
16///
17/// # Arguments
18///
19/// * `track` - A reference to the [Track] object containing track-specific metadata
20/// * `album` - A reference to the [Album] object containing album-specific metadata
21/// * `artist` - A reference to the [Artist] object containing primary artist information
22///
23/// # Returns
24///
25/// A [HashMap] where keys are standardized metadata field names (uppercase strings)
26/// and values are the corresponding metadata values as strings.
27///
28/// # Example
29///
30/// ```rust
31/// use qobuz_api_rust::{models::{Track, Album, Artist}, metadata::extractor::extract_comprehensive_metadata};
32///
33/// // Assuming you have track, album, and artist data
34/// // let metadata = extract_comprehensive_metadata(&track, &album, &artist);
35/// // let title = metadata.get("TITLE");
36/// ```
37pub fn extract_comprehensive_metadata(
38 track: &Track,
39 album: &Album,
40 artist: &Artist,
41) -> HashMap<String, String> {
42 let mut metadata = HashMap::new();
43
44 // Extract basic track information
45 if let Some(ref title) = track.title {
46 // Track title - the main name of the audio track
47 metadata.insert("TITLE".to_string(), title.clone());
48 }
49
50 if let Some(ref album_title) = album.title {
51 // Album title - the name of the album containing the track
52 metadata.insert("ALBUM".to_string(), album_title.clone());
53 }
54
55 if let Some(ref artist_name) = artist.name {
56 // Artist name - the primary performing artist of the track
57 metadata.insert("ARTIST".to_string(), artist_name.clone());
58 }
59
60 // Extract performer information from track
61 if let Some(ref performers) = track.performers {
62 // Performer information - detailed list of performers and their roles
63 metadata.insert("PERFORMER".to_string(), performers.clone());
64 }
65
66 // Combine multiple composers from different sources while preventing duplicates
67 // This follows the same logic as the tag embedding function for consistency
68 let mut composers = Vec::new();
69 let mut composer_set = HashSet::new(); // Use a set to prevent duplicates
70
71 // Add performer as first composer if they're typically a composer (for cases like "Kendrick Lamar")
72 if let Some(ref performer) = track.performer
73 && let Some(ref performer_name) = performer.name
74 && !composer_set.contains(performer_name)
75 && performer_name != "Various Composers"
76 {
77 composers.push(performer_name.clone());
78 composer_set.insert(performer_name.clone());
79 }
80
81 // Add track composer if exists and different from performer
82 if let Some(ref track_composer) = track.composer
83 && let Some(ref composer_name) = track_composer.name
84 && !composer_set.contains(composer_name)
85 && composer_name != "Various Composers"
86 {
87 composers.push(composer_name.clone());
88 composer_set.insert(composer_name.clone());
89 }
90
91 // Add album composer if different from others
92 if let Some(ref album_composer) = album.composer
93 && let Some(ref composer_name) = album_composer.name
94 && !composer_set.contains(composer_name)
95 && composer_name != "Various Composers"
96 {
97 composers.push(composer_name.clone());
98 composer_set.insert(composer_name.clone());
99 }
100
101 // Combine all composers with "/" separator as in the C# implementation
102 if !composers.is_empty() {
103 let combined_composers = composers.join("/");
104 // Composer information - combined list of composers for the track
105 metadata.insert("COMPOSER".to_string(), combined_composers);
106 }
107
108 // Extract label information from album
109 if let Some(ref album_label) = album.label
110 && let Some(ref label_name) = album_label.name
111 {
112 // Record label - the name of the record label that released the album
113 metadata.insert("LABEL".to_string(), label_name.clone());
114 }
115
116 // Extract genre information from album
117 if let Some(ref genre) = album.genre
118 && let Some(ref genre_name) = genre.name
119 {
120 // Genre - the musical genre of the track/album
121 metadata.insert("GENRE".to_string(), genre_name.clone());
122 }
123
124 // Extract track number information
125 if let Some(track_number) = track.track_number {
126 // Track number - the sequential number of the track on the album
127 metadata.insert("TRACKNUMBER".to_string(), track_number.to_string());
128 }
129
130 // Extract total tracks count from album
131 if let Some(ref album_tracks_count) = album.tracks_count {
132 // Total tracks - the total number of tracks on the album
133 metadata.insert("TRACKTOTAL".to_string(), album_tracks_count.to_string());
134 }
135
136 // Extract disc number information from album
137 if let Some(ref album_media_count) = album.media_count {
138 // Disc number - the disc number for multi-disc albums
139 metadata.insert("DISCNUMBER".to_string(), album_media_count.to_string());
140 }
141
142 // Extract copyright information from track
143 if let Some(ref copyright) = track.copyright {
144 // Copyright - the copyright information for the track
145 metadata.insert("COPYRIGHT".to_string(), copyright.clone());
146 }
147
148 // Extract ISRC (International Standard Recording Code) from track
149 if let Some(ref isrc) = track.isrc {
150 // ISRC - International Standard Recording Code, a unique identifier for sound recordings
151 metadata.insert("ISRC".to_string(), isrc.clone());
152 }
153
154 // Extract and handle various date information
155 if let Some(ref release_date) = track.release_date_original {
156 // Release date - the original release date of the track
157 metadata.insert("DATE".to_string(), release_date.clone());
158 }
159
160 // Extract stream release date from album
161 if let Some(ref release_date_stream) = album.release_date_stream {
162 // Stream release date - the date when the album became available for streaming
163 metadata.insert(
164 "RELEASE_DATE_STREAM".to_string(),
165 release_date_stream.clone(),
166 );
167 }
168
169 // Extract download release date from album
170 if let Some(ref release_date_download) = album.release_date_download {
171 // Download release date - the date when the album became available for download
172 metadata.insert(
173 "RELEASE_DATE_DOWNLOAD".to_string(),
174 release_date_download.clone(),
175 );
176 }
177
178 // Extract additional album-specific metadata
179 if let Some(ref album_subtitle) = album.subtitle {
180 // Album subtitle - additional descriptive text for the album
181 metadata.insert("SUBTITLE".to_string(), album_subtitle.clone());
182 }
183
184 if let Some(ref album_version) = album.version {
185 // Album version - indicates if this is a special version (remaster, live, etc.)
186 metadata.insert("VERSION".to_string(), album_version.clone());
187 }
188
189 // Extract UPC (Universal Product Code) from album
190 if let Some(ref album_upc) = album.upc {
191 // UPC - Universal Product Code, a barcode symbology used for tracking trade items
192 metadata.insert("UPC".to_string(), album_upc.clone());
193 }
194
195 // Extract album description
196 if let Some(ref album_description) = album.description {
197 // Album description - detailed description of the album
198 metadata.insert("DESCRIPTION".to_string(), album_description.clone());
199 }
200
201 // Extract technical specifications from track
202 if let Some(bit_depth) = track.maximum_bit_depth {
203 // Bit depth - the bit depth of the audio file (e.g., 16, 24 bits)
204 metadata.insert("BIT_DEPTH".to_string(), bit_depth.to_string());
205 }
206
207 if let Some(sampling_rate) = track.maximum_sampling_rate {
208 // Sampling rate - the sampling rate of the audio file in kHz (e.g., 44.1, 96 kHz)
209 metadata.insert("SAMPLING_RATE".to_string(), sampling_rate.to_string());
210 }
211
212 if let Some(channel_count) = track.maximum_channel_count {
213 // Channel count - the number of audio channels (e.g., 2 for stereo, 6 for 5.1 surround)
214 metadata.insert("CHANNELS".to_string(), channel_count.to_string());
215 }
216
217 // Extract high-resolution audio flags
218 if let Some(hires) = track.hires {
219 // HiRes flag - indicates if the track is high-resolution audio (true/false)
220 metadata.insert("HIRES".to_string(), hires.to_string());
221 }
222
223 if let Some(hires_streamable) = track.hires_streamable {
224 // HiRes streamable flag - indicates if high-resolution version is streamable (true/false)
225 metadata.insert("HIRES_STREAMABLE".to_string(), hires_streamable.to_string());
226 }
227
228 metadata
229}