diff --git a/src/audio.rs b/src/audio.rs index f635a53d..4949a6d9 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -133,72 +133,6 @@ impl Audio { ) } - fn flac_info(reader: &FlacReader, path: &Utf8Path) -> Result { - let streaminfo = reader.streaminfo(); - - let samples = streaminfo - .samples - .context(error::FlacSampleCountUnknown { path })?; - - Ok(AudioInfo { - channels: streaminfo.channels.into(), - sample_bits: Some(streaminfo.bits_per_sample.into()), - sample_rate: streaminfo.sample_rate.into(), - samples, - }) - } - - fn flac_number_tag( - reader: &FlacReader, - path: &Utf8Path, - tag: &'static str, - ) -> Result { - let value = Self::flac_tag(reader, path, tag)?; - parse_number(value).context(error::AudioTagInteger { path, tag }) - } - - fn flac_reader(path: &Utf8Path) -> Result<(FlacReader, AudioInfo)> { - let reader = FlacReader::open(path).context(error::FlacDecode { path })?; - - let audio_info = Self::flac_info(&reader, path)?; - - Ok((reader, audio_info)) - } - - fn flac_tag<'a>( - reader: &'a FlacReader, - path: &Utf8Path, - tag: &'static str, - ) -> Result<&'a str> { - let mut values = reader.get_tag(tag); - - let value = values - .next() - .context(error::AudioTagMissing { path, tag })?; - - ensure! { - values.next().is_none(), - error::AudioTagMultiple { path, tag }, - } - - ensure! { - !value.is_empty(), - error::AudioTagEmpty { path, tag }, - } - - Ok(value) - } - - fn flac_text_tag( - reader: &FlacReader, - path: &Utf8Path, - tag: &'static str, - ) -> Result { - Self::flac_tag(reader, path, tag)? - .parse() - .context(error::AudioTagInvalid { path, tag }) - } - pub(crate) fn formats(tracks: &[Audio]) -> Vec { let mut formats = Vec::new(); @@ -211,120 +145,39 @@ impl Audio { formats } - fn id3_pair_tag(tag: &id3::Tag, path: &Utf8Path, id: &'static str) -> Result<(u64, u64)> { - let value = Self::id3_tag(tag, path, id)?; - - let (number, total) = value - .split_once('/') - .context(error::AudioTagPair { path, tag: id })?; - - Ok(( - parse_number(number).context(error::AudioTagInteger { path, tag: id })?, - parse_number(total).context(error::AudioTagInteger { path, tag: id })?, - )) - } - - fn id3_tag<'a>(tag: &'a id3::Tag, path: &Utf8Path, id: &'static str) -> Result<&'a str> { - let mut values = tag - .get(id) - .and_then(|frame| frame.content().text_values()) - .into_iter() - .flatten(); - - let value = values - .next() - .context(error::AudioTagMissing { path, tag: id })?; - - ensure! { - values.next().is_none(), - error::AudioTagMultiple { path, tag: id }, - } - - ensure! { - !value.is_empty(), - error::AudioTagEmpty { path, tag: id }, - } - - Ok(value) - } - - fn id3_text_tag(tag: &id3::Tag, path: &Utf8Path, id: &'static str) -> Result { - Self::id3_tag(tag, path, id)? - .parse() - .context(error::AudioTagInvalid { path, tag: id }) - } - pub(crate) fn populate(&mut self, root: &Utf8Path) -> Result { let path = root.join(self.as_path()); - match self.ty { - AudioType::Flac => self.populate_flac(&path), - AudioType::Mp3 => self.populate_mp3(&path), - } - } - - fn populate_flac(&mut self, path: &Utf8Path) -> Result { - let (reader, audio_info) = Self::flac_reader(path)?; - - let AudioInfo { - channels, - sample_bits, - sample_rate, - samples, - } = audio_info; - - self.channels = channels; - self.sample_bits = sample_bits; - self.sample_rate = sample_rate; - self.samples = samples; - - self.album = Self::flac_text_tag(&reader, path, "album")?; - self.artist = Self::flac_text_tag(&reader, path, "artist")?; - self.disc = Self::flac_number_tag(&reader, path, "discnumber")?; - self.discs = Self::flac_number_tag(&reader, path, "disctotal")?; - self.title = Self::flac_text_tag(&reader, path, "title")?; - self.track = Self::flac_number_tag(&reader, path, "tracknumber")?; - self.tracks = Self::flac_number_tag(&reader, path, "tracktotal")?; - - Ok(()) - } - - fn populate_mp3(&mut self, path: &Utf8Path) -> Result { - let data = filesystem::read(path)?; - - let tag = match id3::Tag::read_from2(io::Cursor::new(&data)) { - Err(err) => { - if let id3::ErrorKind::NoTag = err.kind { - return Err(error::Mp3TagMissing { path }.build()); - } - return Err(error::Mp3Tag { path }.into_error(err)); - } - Ok(tag) => tag, + let metadata = match self.ty { + AudioType::Flac => FlacDecoder::read(&path)?, + AudioType::Mp3 => Mp3Decoder::read(&path)?, }; - self.album = Self::id3_text_tag(&tag, path, "TALB")?; - self.artist = Self::id3_text_tag(&tag, path, "TPE1")?; - (self.disc, self.discs) = Self::id3_pair_tag(&tag, path, "TPOS")?; - self.title = Self::id3_text_tag(&tag, path, "TIT2")?; - (self.track, self.tracks) = Self::id3_pair_tag(&tag, path, "TRCK")?; - - let mut cursor = io::Cursor::new(&data); - - id3::Tag::skip(&mut cursor).context(error::Mp3Tag { path })?; - - let start = usize::try_from(cursor.position()).unwrap(); - - let AudioInfo { + let AudioMetadata { + album, + artist, channels, + disc, + discs, sample_bits, sample_rate, samples, - } = Mp3Decoder::decode(&data[start..]).context(error::Mp3Decode { path })?; + title, + track, + tracks, + } = metadata; + self.album = album; + self.artist = artist; self.channels = channels; + self.disc = disc; + self.discs = discs; self.sample_bits = sample_bits; self.sample_rate = sample_rate; self.samples = samples; + self.title = title; + self.track = track; + self.tracks = tracks; Ok(()) } @@ -338,6 +191,28 @@ impl Audio { sum.saturating_add(audio.duration()) }) } + + pub(crate) fn tag<'a>( + mut values: impl Iterator, + path: &Utf8Path, + tag: &'static str, + ) -> Result<&'a str> { + let value = values + .next() + .context(error::AudioTagMissing { path, tag })?; + + ensure! { + values.next().is_none(), + error::AudioTagMultiple { path, tag }, + } + + ensure! { + !value.is_empty(), + error::AudioTagEmpty { path, tag }, + } + + Ok(value) + } } impl FromStr for Audio { @@ -718,208 +593,7 @@ mod tests { } #[test] - fn populate_flac_err() { - fn err(bytes: &[u8]) -> Error { - let (_tempdir, root) = tempdir(); - - std::fs::write(root.join("foo.flac"), bytes).unwrap(); - - let mut audio = "foo.flac".parse::