1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#[allow(clippy::wildcard_imports)]
use crate::components::tags::*;
use crate::{Album, AnyTag, Picture, Result, TagType};

use std::borrow::Cow;
use std::fs::{File, OpenOptions};

/// Combination of [`AudioTagEdit`], [`AudioTagWrite`], and [`ToAnyTag`]
pub trait AudioTag: AudioTagEdit + AudioTagWrite + ToAnyTag {}

/// Implementors of this trait are able to read and write audio metadata.
///
/// Constructor methods e.g. `from_file` should be implemented separately.
pub trait AudioTagEdit {
	/// Returns the track title
	fn title(&self) -> Option<&str>;
	/// Sets the track title
	fn set_title(&mut self, title: &str);
	/// Removes the track title
	fn remove_title(&mut self);

	/// Returns the artist(s) as a string
	fn artist_str(&self) -> Option<&str>;
	/// Sets the artist string
	fn set_artist(&mut self, artist: &str);

	/// Splits the artist string into a `Vec`
	fn artists_vec(&self) -> Option<Vec<&str>> {
		self.artist_str().map(|a| a.split('/').collect())
	}
	/// Removes the artist string
	fn remove_artist(&mut self);

	/// Returns the track date
	fn date(&self) -> Option<String> {
		self.year().map(|y| y.to_string())
	}
	/// Sets the track date
	fn set_date(&mut self, date: &str) {
		if let Ok(d) = date.parse::<i32>() {
			self.set_year(d)
		}
	}
	/// Removes the track date
	fn remove_date(&mut self) {
		self.remove_year()
	}

	/// Returns the track year
	fn year(&self) -> Option<i32>;
	/// Sets the track year
	fn set_year(&mut self, year: i32);
	/// Removes the track year
	fn remove_year(&mut self);

	/// Returns the track's [`Album`]
	fn album(&self) -> Album<'_> {
		Album {
			title: self.album_title(),
			artists: self.album_artists_vec(),
			covers: self.album_covers(),
		}
	}

	/// Returns the album title
	fn album_title(&self) -> Option<&str>;
	/// Sets the album title
	fn set_album_title(&mut self, v: &str);
	/// Removes the album title
	fn remove_album_title(&mut self);

	/// Returns the album artist string
	fn album_artist_str(&self) -> Option<&str>;
	/// Splits the artist string into a `Vec`
	fn album_artists_vec(&self) -> Option<Vec<&str>> {
		self.album_artist_str().map(|a| a.split('/').collect())
	}
	/// Sets the album artist string
	fn set_album_artist(&mut self, artist: &str);
	/// Removes the album artist string
	fn remove_album_artists(&mut self);

	/// Returns the front and back album covers
	fn album_covers(&self) -> (Option<Picture>, Option<Picture>) {
		(self.front_cover(), self.back_cover())
	}
	/// Removes both album covers
	fn remove_album_covers(&mut self) {
		self.remove_front_cover();
		self.remove_back_cover();
	}

	/// Returns the front cover
	fn front_cover(&self) -> Option<Picture>;
	/// Sets the front cover
	fn set_front_cover(&mut self, cover: Picture);
	/// Removes the front cover
	fn remove_front_cover(&mut self);

	/// Returns the front cover
	fn back_cover(&self) -> Option<Picture>;
	/// Sets the front cover
	fn set_back_cover(&mut self, cover: Picture);
	/// Removes the front cover
	fn remove_back_cover(&mut self);

	/// Returns an `Iterator` over all pictures stored in the track
	fn pictures(&self) -> Option<Cow<'static, [Picture]>>;

	/// Returns the track number and total tracks
	fn track(&self) -> (Option<u32>, Option<u32>) {
		(self.track_number(), self.total_tracks())
	}
	/// Sets the track number
	fn set_track(&mut self, track: u32) {
		self.set_track_number(track);
	}
	/// Removes the track number and total tracks
	fn remove_track(&mut self) {
		self.remove_track_number();
		self.remove_total_tracks();
	}

	/// Returns the track number
	fn track_number(&self) -> Option<u32>;
	/// Sets the track number
	fn set_track_number(&mut self, track_number: u32);
	/// Removes the track number
	fn remove_track_number(&mut self);

	/// Returns the total tracks
	fn total_tracks(&self) -> Option<u32>;
	/// Sets the total tracks
	fn set_total_tracks(&mut self, total_track: u32);
	/// Removes the total tracks
	fn remove_total_tracks(&mut self);

	/// Returns the disc number and total discs
	fn disc(&self) -> (Option<u32>, Option<u32>) {
		(self.disc_number(), self.total_discs())
	}
	/// Removes the disc number and total discs
	fn remove_disc(&mut self) {
		self.remove_disc_number();
		self.remove_total_discs();
	}

	/// Returns the disc number
	fn disc_number(&self) -> Option<u32>;
	/// Sets the disc number
	fn set_disc_number(&mut self, disc_number: u32);
	/// Removes the disc number
	fn remove_disc_number(&mut self);

	/// Returns the total discs
	fn total_discs(&self) -> Option<u32>;
	/// Sets the total discs
	fn set_total_discs(&mut self, total_discs: u32);
	/// Removes the total discs
	fn remove_total_discs(&mut self);
}

/// Functions for writing to a file
pub trait AudioTagWrite {
	/// Write tag to a [`File`][std::fs::File]
	///
	/// # Errors
	///
	/// Will return `Err` if unable to write to the `File`
	fn write_to(&self, file: &mut File) -> Result<()>;
	/// Write tag to a path
	///
	/// # Errors
	///
	/// Will return `Err` if `path` doesn't exist
	fn write_to_path(&self, path: &str) -> Result<()> {
		self.write_to(&mut OpenOptions::new().read(true).write(true).open(path)?)?;

		Ok(())
	}
}

/// Conversions between tag types
pub trait ToAnyTag: ToAny {
	/// Converts the tag to [`AnyTag`]
	fn to_anytag(&self) -> AnyTag<'_>;

	/// Convert the tag type, which can be lossy.
	fn to_dyn_tag(&self, tag_type: TagType) -> Box<dyn AudioTag> {
		// TODO: write a macro or something that implement this method for every tag type so that if the
		// TODO: target type is the same, just return self
		match tag_type {
			#[cfg(feature = "format-ape")]
			TagType::Ape => Box::new(ApeTag::from(self.to_anytag())),
			#[cfg(feature = "format-id3")]
			TagType::Id3v2(_) => Box::new(Id3v2Tag::from(self.to_anytag())),
			#[cfg(feature = "format-mp4")]
			TagType::Mp4 => Box::new(Mp4Tag::from(self.to_anytag())),
			#[cfg(any(
				feature = "format-vorbis",
				feature = "format-flac",
				feature = "format-opus"
			))]
			TagType::Vorbis(_) => Box::new(VorbisTag::from(self.to_anytag())),
			#[cfg(feature = "format-riff")]
			TagType::RiffInfo => Box::new(RiffTag::from(self.to_anytag())),
		}
	}
}

/// Tag conversion to `Any`
pub trait ToAny {
	/// Convert tag to `Any`
	fn to_any(&self) -> &dyn std::any::Any;
	/// Mutably convert tag to `Any`
	#[allow(clippy::wrong_self_convention)]
	fn to_any_mut(&mut self) -> &mut dyn std::any::Any;
}