Skip to main content

qobuz_api/metadata/
config.rs

1//! Configuration for which metadata fields to embed.
2
3use std::collections::HashSet;
4
5use tracing::info;
6
7/// Configuration controlling which metadata fields to embed in audio files.
8///
9/// Uses a set of `MetadataField` variants. `Default` enables all fields except `Comment`.
10/// Use `is_enabled()` to check whether a field should be embedded.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct MetadataConfig {
13    /// Set of enabled metadata fields.
14    enabled: HashSet<MetadataField>,
15}
16
17impl MetadataConfig {
18    /// Creates a config with all fields enabled.
19    ///
20    /// # Returns
21    ///
22    /// A `MetadataConfig` with every `MetadataField` enabled.
23    #[must_use]
24    pub fn all() -> Self {
25        Self {
26            enabled: HashSet::from([
27                MetadataField::Title,
28                MetadataField::Artist,
29                MetadataField::Album,
30                MetadataField::AlbumArtist,
31                MetadataField::Genre,
32                MetadataField::ReleaseDate,
33                MetadataField::ReleaseYear,
34                MetadataField::Composer,
35                MetadataField::TrackNumber,
36                MetadataField::TrackTotal,
37                MetadataField::DiscNumber,
38                MetadataField::DiscTotal,
39                MetadataField::CoverArt,
40                MetadataField::Isrc,
41                MetadataField::Copyright,
42                MetadataField::Label,
43                MetadataField::MediaType,
44                MetadataField::Comment,
45                MetadataField::Producer,
46                MetadataField::InvolvedPeople,
47                MetadataField::Explicit,
48                MetadataField::Upc,
49                MetadataField::Url,
50            ]),
51        }
52    }
53
54    /// Returns whether a specific field is enabled for embedding.
55    ///
56    /// # Arguments
57    ///
58    /// * `field` - The metadata field to check
59    ///
60    /// # Returns
61    ///
62    /// `true` if the field is enabled.
63    #[must_use]
64    pub fn is_enabled(&self, field: MetadataField) -> bool {
65        self.enabled.contains(&field)
66    }
67
68    /// Enables or disables a specific field.
69    ///
70    /// # Arguments
71    ///
72    /// * `field` - The metadata field to toggle
73    /// * `enabled` - `true` to enable, `false` to disable
74    pub fn set(&mut self, field: MetadataField, enabled: bool) {
75        if enabled {
76            let inserted = self.enabled.insert(field);
77            info!(field = ?field, inserted, "metadata field toggled");
78        } else {
79            let removed = self.enabled.remove(&field);
80            info!(field = ?field, removed, "metadata field toggled");
81        }
82    }
83}
84
85impl Default for MetadataConfig {
86    fn default() -> Self {
87        let mut config = Self::all();
88        let removed = config.enabled.remove(&MetadataField::Comment);
89        info!(removed, "default metadata config excludes comment");
90        config
91    }
92}
93
94/// Metadata fields that can be embedded in audio files.
95#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
96pub enum MetadataField {
97    /// Track title.
98    Title,
99    /// Artist name.
100    Artist,
101    /// Album title.
102    Album,
103    /// Album artist.
104    AlbumArtist,
105    /// Genre.
106    Genre,
107    /// Full release date (YYYY-MM-DD).
108    ReleaseDate,
109    /// Release year only.
110    ReleaseYear,
111    /// Composer.
112    Composer,
113    /// Track number.
114    TrackNumber,
115    /// Total tracks in album.
116    TrackTotal,
117    /// Disc number.
118    DiscNumber,
119    /// Total discs in album.
120    DiscTotal,
121    /// Cover art image.
122    CoverArt,
123    /// ISRC code.
124    Isrc,
125    /// Copyright notice.
126    Copyright,
127    /// Record label.
128    Label,
129    /// Original media type (album, compilation, etc.).
130    MediaType,
131    /// Comment field.
132    Comment,
133    /// Producer.
134    Producer,
135    /// Involved people / musician credits.
136    InvolvedPeople,
137    /// Explicit content flag.
138    Explicit,
139    /// Universal Product Code.
140    Upc,
141    /// Commercial URL.
142    Url,
143}
144
145#[cfg(test)]
146mod tests {
147    use anyhow::{Result, ensure};
148
149    use crate::metadata::config::{
150        MetadataConfig,
151        MetadataField::{Comment, Producer, Title},
152    };
153
154    /// Tests default excludes comment.
155    ///
156    /// # Errors
157    ///
158    /// Returns an error if the test setup or assertion fails.
159    #[test]
160    fn default_excludes_comment() -> Result<()> {
161        let config = MetadataConfig::default();
162        ensure!(config.is_enabled(Title));
163        ensure!(!config.is_enabled(Comment));
164        Ok(())
165    }
166
167    /// Tests all enables every field.
168    ///
169    /// # Errors
170    ///
171    /// Returns an error if the test setup or assertion fails.
172    #[test]
173    fn all_enables_every_field() -> Result<()> {
174        let config = MetadataConfig::all();
175        ensure!(config.is_enabled(Comment));
176        ensure!(config.is_enabled(Producer));
177        Ok(())
178    }
179
180    /// Tests set toggles field.
181    ///
182    /// # Errors
183    ///
184    /// Returns an error if the test setup or assertion fails.
185    #[test]
186    fn set_toggles_field() -> Result<()> {
187        let mut config = MetadataConfig::default();
188        ensure!(!config.is_enabled(Comment));
189        config.set(Comment, true);
190        ensure!(config.is_enabled(Comment));
191        config.set(Comment, false);
192        ensure!(!config.is_enabled(Comment));
193        Ok(())
194    }
195}