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}