diff --git a/src/index.rs b/src/index.rs index e8a6291..25dedea 100644 --- a/src/index.rs +++ b/src/index.rs @@ -9,7 +9,7 @@ use std::thread; use std::time; use error::*; -use metadata::SongTags; +use metadata; use utils; use vfs::Vfs; @@ -364,7 +364,7 @@ impl Index { self.populate_directory(builder, file_path.as_path()); } else { if let Some(file_path_string) = file_path.to_str() { - if let Ok(tags) = SongTags::read(file_path.as_path()) { + if let Ok(tags) = metadata::read(file_path.as_path()) { if tags.year.is_some() { inconsistent_directory_year |= directory_year.is_some() && directory_year != tags.year; diff --git a/src/metadata.rs b/src/metadata.rs index 5171500..fa46634 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -11,6 +11,7 @@ use error::PError; use utils; use utils::AudioFormat; +#[derive(Debug, PartialEq)] pub struct SongTags { pub disc_number: Option, pub track_number: Option, @@ -21,134 +22,148 @@ pub struct SongTags { pub year: Option, } -impl SongTags { - pub fn read(path: &Path) -> Result { - match utils::get_audio_format(path) { - Some(AudioFormat::FLAC) => SongTags::read_flac(path), - Some(AudioFormat::MP3) => SongTags::read_id3(path), - Some(AudioFormat::MPC) => SongTags::read_ape(path), - Some(AudioFormat::OGG) => SongTags::read_vorbis(path), - _ => Err(PError::UnsupportedMetadataFormat), - } +pub fn read(path: &Path) -> Result { + match utils::get_audio_format(path) { + Some(AudioFormat::FLAC) => read_flac(path), + Some(AudioFormat::MP3) => read_id3(path), + Some(AudioFormat::MPC) => read_ape(path), + Some(AudioFormat::OGG) => read_vorbis(path), + _ => Err(PError::UnsupportedMetadataFormat), } +} - fn read_id3(path: &Path) -> Result { - let tag = try!(id3::Tag::read_from_path(path)); +fn read_id3(path: &Path) -> Result { + let tag = try!(id3::Tag::read_from_path(path)); - let artist = tag.artist().map(|s| s.to_string()); - let album_artist = tag.album_artist().map(|s| s.to_string()); - let album = tag.album().map(|s| s.to_string()); - let title = tag.title().map(|s| s.to_string()); - let disc_number = tag.disc(); - let track_number = tag.track(); - let year = tag.year() - .map(|y| y as i32) - .or(tag.date_released().and_then(|d| d.year)) - .or(tag.date_recorded().and_then(|d| d.year)); + let artist = tag.artist().map(|s| s.to_string()); + let album_artist = tag.album_artist().map(|s| s.to_string()); + let album = tag.album().map(|s| s.to_string()); + let title = tag.title().map(|s| s.to_string()); + let disc_number = tag.disc(); + let track_number = tag.track(); + let year = tag.year() + .map(|y| y as i32) + .or(tag.date_released().and_then(|d| d.year)) + .or(tag.date_recorded().and_then(|d| d.year)); - Ok(SongTags { - artist: artist, - album_artist: album_artist, - album: album, - title: title, - disc_number: disc_number, - track_number: track_number, - year: year, - }) + Ok(SongTags { + artist: artist, + album_artist: album_artist, + album: album, + title: title, + disc_number: disc_number, + track_number: track_number, + year: year, + }) +} + +fn read_ape_string(item: &ape::Item) -> Option { + match item.value { + ape::ItemValue::Text(ref s) => Some(s.clone()), + _ => None, } +} - fn read_ape_string(item: &ape::Item) -> Option { - match item.value { - ape::ItemValue::Text(ref s) => Some(s.clone()), - _ => None, - } +fn read_ape_i32(item: &ape::Item) -> Option { + match item.value { + ape::ItemValue::Text(ref s) => s.parse::().ok(), + _ => None, } +} - fn read_ape_i32(item: &ape::Item) -> Option { - match item.value { - ape::ItemValue::Text(ref s) => s.parse::().ok(), - _ => None, - } - } - - fn read_ape_x_of_y(item: &ape::Item) -> Option { - match item.value { - ape::ItemValue::Text(ref s) => { - let format = Regex::new(r#"^\d+"#).unwrap(); - if let Some((start, end)) = format.find(s) { - s[start..end].parse().ok() - } else { - None - } - } - _ => None, - } - } - - fn read_ape(path: &Path) -> Result { - let tag = try!(ape::read(path)); - let artist = tag.item("Artist").and_then(SongTags::read_ape_string); - let album = tag.item("Album").and_then(SongTags::read_ape_string); - let album_artist = tag.item("Album artist").and_then(SongTags::read_ape_string); - let title = tag.item("Title").and_then(SongTags::read_ape_string); - let year = tag.item("Year").and_then(SongTags::read_ape_i32); - let disc_number = tag.item("Disc").and_then(SongTags::read_ape_x_of_y); - let track_number = tag.item("Track").and_then(SongTags::read_ape_x_of_y); - Ok(SongTags { - artist: artist, - album_artist: album_artist, - album: album, - title: title, - disc_number: disc_number, - track_number: track_number, - year: year, - }) - } - - fn read_vorbis(path: &Path) -> Result { - - let file = try!(fs::File::open(path)); - let source = try!(OggStreamReader::new(PacketReader::new(file))); - - let mut tags = SongTags { - artist: None, - album_artist: None, - album: None, - title: None, - disc_number: None, - track_number: None, - year: None, - }; - - for (key, value) in source.comment_hdr.comment_list { - match key.as_str() { - "TITLE" => tags.title = Some(value), - "ALBUM" => tags.album = Some(value), - "ARTIST" => tags.artist = Some(value), - "ALBUMARTIST" => tags.album_artist = Some(value), - "TRACKNUMBER" => tags.track_number = value.parse::().ok(), - "DISCNUMBER" => tags.disc_number = value.parse::().ok(), - "DATE" => tags.year = value.parse::().ok(), - _ => (), +fn read_ape_x_of_y(item: &ape::Item) -> Option { + match item.value { + ape::ItemValue::Text(ref s) => { + let format = Regex::new(r#"^\d+"#).unwrap(); + if let Some((start, end)) = format.find(s) { + s[start..end].parse().ok() + } else { + None } } + _ => None, + } +} - Ok(tags) +fn read_ape(path: &Path) -> Result { + let tag = try!(ape::read(path)); + let artist = tag.item("Artist").and_then(read_ape_string); + let album = tag.item("Album").and_then(read_ape_string); + let album_artist = tag.item("Album artist").and_then(read_ape_string); + let title = tag.item("Title").and_then(read_ape_string); + let year = tag.item("Year").and_then(read_ape_i32); + let disc_number = tag.item("Disc").and_then(read_ape_x_of_y); + let track_number = tag.item("Track").and_then(read_ape_x_of_y); + Ok(SongTags { + artist: artist, + album_artist: album_artist, + album: album, + title: title, + disc_number: disc_number, + track_number: track_number, + year: year, + }) +} + +fn read_vorbis(path: &Path) -> Result { + + let file = try!(fs::File::open(path)); + let source = try!(OggStreamReader::new(PacketReader::new(file))); + + let mut tags = SongTags { + artist: None, + album_artist: None, + album: None, + title: None, + disc_number: None, + track_number: None, + year: None, + }; + + for (key, value) in source.comment_hdr.comment_list { + match key.as_str() { + "TITLE" => tags.title = Some(value), + "ALBUM" => tags.album = Some(value), + "ARTIST" => tags.artist = Some(value), + "ALBUMARTIST" => tags.album_artist = Some(value), + "TRACKNUMBER" => tags.track_number = value.parse::().ok(), + "DISCNUMBER" => tags.disc_number = value.parse::().ok(), + "DATE" => tags.year = value.parse::().ok(), + _ => (), + } } - fn read_flac(path: &Path) -> Result { - let tag = try!(metaflac::Tag::read_from_path(path)); - let vorbis = try!(tag.vorbis_comments().ok_or(PError::MetadataDecodingError)); - let disc_number = vorbis.get("DISCNUMBER").and_then(|d| d[0].parse::().ok()); - let year = vorbis.get("DATE").and_then(|d| d[0].parse::().ok()); - Ok(SongTags { - artist: vorbis.artist().map(|v| v[0].clone()), - album_artist: vorbis.album_artist().map(|v| v[0].clone()), - album: vorbis.album().map(|v| v[0].clone()), - title: vorbis.title().map(|v| v[0].clone()), - disc_number: disc_number, - track_number: vorbis.track(), - year: year, - }) - } -} \ No newline at end of file + Ok(tags) +} + +fn read_flac(path: &Path) -> Result { + let tag = try!(metaflac::Tag::read_from_path(path)); + let vorbis = try!(tag.vorbis_comments().ok_or(PError::MetadataDecodingError)); + let disc_number = vorbis.get("DISCNUMBER").and_then(|d| d[0].parse::().ok()); + let year = vorbis.get("DATE").and_then(|d| d[0].parse::().ok()); + Ok(SongTags { + artist: vorbis.artist().map(|v| v[0].clone()), + album_artist: vorbis.album_artist().map(|v| v[0].clone()), + album: vorbis.album().map(|v| v[0].clone()), + title: vorbis.title().map(|v| v[0].clone()), + disc_number: disc_number, + track_number: vorbis.track(), + year: year, + }) +} + +#[test] +fn test_read_metadata() { + let sample_tags = SongTags { + disc_number: Some(3), + track_number: Some(1), + title: Some("TEST TITLE".into()), + artist: Some("TEST ARTIST".into()), + album_artist: Some("TEST ALBUM ARTIST".into()), + album: Some("TEST ALBUM".into()), + year: Some(2016), + }; + assert_eq!(read(Path::new("test/sample.mp3")).unwrap(), sample_tags); + assert_eq!(read(Path::new("test/sample.ogg")).unwrap(), sample_tags); + assert_eq!(read(Path::new("test/sample.flac")).unwrap(), sample_tags); +} diff --git a/test/sample.flac b/test/sample.flac new file mode 100644 index 0000000..07d6729 Binary files /dev/null and b/test/sample.flac differ diff --git a/test/sample.mp3 b/test/sample.mp3 new file mode 100644 index 0000000..7dda8b2 Binary files /dev/null and b/test/sample.mp3 differ diff --git a/test/sample.ogg b/test/sample.ogg new file mode 100644 index 0000000..2e31ff3 Binary files /dev/null and b/test/sample.ogg differ