Added metadata decoding tests
This commit is contained in:
parent
a16595a2e1
commit
1099c0dcf1
5 changed files with 135 additions and 120 deletions
|
@ -9,7 +9,7 @@ use std::thread;
|
||||||
use std::time;
|
use std::time;
|
||||||
|
|
||||||
use error::*;
|
use error::*;
|
||||||
use metadata::SongTags;
|
use metadata;
|
||||||
use utils;
|
use utils;
|
||||||
use vfs::Vfs;
|
use vfs::Vfs;
|
||||||
|
|
||||||
|
@ -364,7 +364,7 @@ impl Index {
|
||||||
self.populate_directory(builder, file_path.as_path());
|
self.populate_directory(builder, file_path.as_path());
|
||||||
} else {
|
} else {
|
||||||
if let Some(file_path_string) = file_path.to_str() {
|
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() {
|
if tags.year.is_some() {
|
||||||
inconsistent_directory_year |= directory_year.is_some() &&
|
inconsistent_directory_year |= directory_year.is_some() &&
|
||||||
directory_year != tags.year;
|
directory_year != tags.year;
|
||||||
|
|
273
src/metadata.rs
273
src/metadata.rs
|
@ -11,6 +11,7 @@ use error::PError;
|
||||||
use utils;
|
use utils;
|
||||||
use utils::AudioFormat;
|
use utils::AudioFormat;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct SongTags {
|
pub struct SongTags {
|
||||||
pub disc_number: Option<u32>,
|
pub disc_number: Option<u32>,
|
||||||
pub track_number: Option<u32>,
|
pub track_number: Option<u32>,
|
||||||
|
@ -21,134 +22,148 @@ pub struct SongTags {
|
||||||
pub year: Option<i32>,
|
pub year: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SongTags {
|
pub fn read(path: &Path) -> Result<SongTags, PError> {
|
||||||
pub fn read(path: &Path) -> Result<SongTags, PError> {
|
match utils::get_audio_format(path) {
|
||||||
match utils::get_audio_format(path) {
|
Some(AudioFormat::FLAC) => read_flac(path),
|
||||||
Some(AudioFormat::FLAC) => SongTags::read_flac(path),
|
Some(AudioFormat::MP3) => read_id3(path),
|
||||||
Some(AudioFormat::MP3) => SongTags::read_id3(path),
|
Some(AudioFormat::MPC) => read_ape(path),
|
||||||
Some(AudioFormat::MPC) => SongTags::read_ape(path),
|
Some(AudioFormat::OGG) => read_vorbis(path),
|
||||||
Some(AudioFormat::OGG) => SongTags::read_vorbis(path),
|
_ => Err(PError::UnsupportedMetadataFormat),
|
||||||
_ => Err(PError::UnsupportedMetadataFormat),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn read_id3(path: &Path) -> Result<SongTags, PError> {
|
|
||||||
let tag = try!(id3::Tag::read_from_path(path));
|
fn read_id3(path: &Path) -> Result<SongTags, PError> {
|
||||||
|
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 artist = tag.artist().map(|s| s.to_string());
|
||||||
let album = tag.album().map(|s| s.to_string());
|
let album_artist = tag.album_artist().map(|s| s.to_string());
|
||||||
let title = tag.title().map(|s| s.to_string());
|
let album = tag.album().map(|s| s.to_string());
|
||||||
let disc_number = tag.disc();
|
let title = tag.title().map(|s| s.to_string());
|
||||||
let track_number = tag.track();
|
let disc_number = tag.disc();
|
||||||
let year = tag.year()
|
let track_number = tag.track();
|
||||||
.map(|y| y as i32)
|
let year = tag.year()
|
||||||
.or(tag.date_released().and_then(|d| d.year))
|
.map(|y| y as i32)
|
||||||
.or(tag.date_recorded().and_then(|d| d.year));
|
.or(tag.date_released().and_then(|d| d.year))
|
||||||
|
.or(tag.date_recorded().and_then(|d| d.year));
|
||||||
Ok(SongTags {
|
|
||||||
artist: artist,
|
Ok(SongTags {
|
||||||
album_artist: album_artist,
|
artist: artist,
|
||||||
album: album,
|
album_artist: album_artist,
|
||||||
title: title,
|
album: album,
|
||||||
disc_number: disc_number,
|
title: title,
|
||||||
track_number: track_number,
|
disc_number: disc_number,
|
||||||
year: year,
|
track_number: track_number,
|
||||||
})
|
year: year,
|
||||||
}
|
})
|
||||||
|
}
|
||||||
fn read_ape_string(item: &ape::Item) -> Option<String> {
|
|
||||||
match item.value {
|
fn read_ape_string(item: &ape::Item) -> Option<String> {
|
||||||
ape::ItemValue::Text(ref s) => Some(s.clone()),
|
match item.value {
|
||||||
_ => None,
|
ape::ItemValue::Text(ref s) => Some(s.clone()),
|
||||||
}
|
_ => None,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn read_ape_i32(item: &ape::Item) -> Option<i32> {
|
|
||||||
match item.value {
|
fn read_ape_i32(item: &ape::Item) -> Option<i32> {
|
||||||
ape::ItemValue::Text(ref s) => s.parse::<i32>().ok(),
|
match item.value {
|
||||||
_ => None,
|
ape::ItemValue::Text(ref s) => s.parse::<i32>().ok(),
|
||||||
}
|
_ => None,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn read_ape_x_of_y(item: &ape::Item) -> Option<u32> {
|
|
||||||
match item.value {
|
fn read_ape_x_of_y(item: &ape::Item) -> Option<u32> {
|
||||||
ape::ItemValue::Text(ref s) => {
|
match item.value {
|
||||||
let format = Regex::new(r#"^\d+"#).unwrap();
|
ape::ItemValue::Text(ref s) => {
|
||||||
if let Some((start, end)) = format.find(s) {
|
let format = Regex::new(r#"^\d+"#).unwrap();
|
||||||
s[start..end].parse().ok()
|
if let Some((start, end)) = format.find(s) {
|
||||||
} else {
|
s[start..end].parse().ok()
|
||||||
None
|
} else {
|
||||||
}
|
None
|
||||||
}
|
}
|
||||||
_ => None,
|
}
|
||||||
}
|
_ => None,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn read_ape(path: &Path) -> Result<SongTags, PError> {
|
|
||||||
let tag = try!(ape::read(path));
|
fn read_ape(path: &Path) -> Result<SongTags, PError> {
|
||||||
let artist = tag.item("Artist").and_then(SongTags::read_ape_string);
|
let tag = try!(ape::read(path));
|
||||||
let album = tag.item("Album").and_then(SongTags::read_ape_string);
|
let artist = tag.item("Artist").and_then(read_ape_string);
|
||||||
let album_artist = tag.item("Album artist").and_then(SongTags::read_ape_string);
|
let album = tag.item("Album").and_then(read_ape_string);
|
||||||
let title = tag.item("Title").and_then(SongTags::read_ape_string);
|
let album_artist = tag.item("Album artist").and_then(read_ape_string);
|
||||||
let year = tag.item("Year").and_then(SongTags::read_ape_i32);
|
let title = tag.item("Title").and_then(read_ape_string);
|
||||||
let disc_number = tag.item("Disc").and_then(SongTags::read_ape_x_of_y);
|
let year = tag.item("Year").and_then(read_ape_i32);
|
||||||
let track_number = tag.item("Track").and_then(SongTags::read_ape_x_of_y);
|
let disc_number = tag.item("Disc").and_then(read_ape_x_of_y);
|
||||||
Ok(SongTags {
|
let track_number = tag.item("Track").and_then(read_ape_x_of_y);
|
||||||
artist: artist,
|
Ok(SongTags {
|
||||||
album_artist: album_artist,
|
artist: artist,
|
||||||
album: album,
|
album_artist: album_artist,
|
||||||
title: title,
|
album: album,
|
||||||
disc_number: disc_number,
|
title: title,
|
||||||
track_number: track_number,
|
disc_number: disc_number,
|
||||||
year: year,
|
track_number: track_number,
|
||||||
})
|
year: year,
|
||||||
}
|
})
|
||||||
|
}
|
||||||
fn read_vorbis(path: &Path) -> Result<SongTags, PError> {
|
|
||||||
|
fn read_vorbis(path: &Path) -> Result<SongTags, PError> {
|
||||||
let file = try!(fs::File::open(path));
|
|
||||||
let source = try!(OggStreamReader::new(PacketReader::new(file)));
|
let file = try!(fs::File::open(path));
|
||||||
|
let source = try!(OggStreamReader::new(PacketReader::new(file)));
|
||||||
let mut tags = SongTags {
|
|
||||||
artist: None,
|
let mut tags = SongTags {
|
||||||
album_artist: None,
|
artist: None,
|
||||||
album: None,
|
album_artist: None,
|
||||||
title: None,
|
album: None,
|
||||||
disc_number: None,
|
title: None,
|
||||||
track_number: None,
|
disc_number: None,
|
||||||
year: None,
|
track_number: None,
|
||||||
};
|
year: None,
|
||||||
|
};
|
||||||
for (key, value) in source.comment_hdr.comment_list {
|
|
||||||
match key.as_str() {
|
for (key, value) in source.comment_hdr.comment_list {
|
||||||
"TITLE" => tags.title = Some(value),
|
match key.as_str() {
|
||||||
"ALBUM" => tags.album = Some(value),
|
"TITLE" => tags.title = Some(value),
|
||||||
"ARTIST" => tags.artist = Some(value),
|
"ALBUM" => tags.album = Some(value),
|
||||||
"ALBUMARTIST" => tags.album_artist = Some(value),
|
"ARTIST" => tags.artist = Some(value),
|
||||||
"TRACKNUMBER" => tags.track_number = value.parse::<u32>().ok(),
|
"ALBUMARTIST" => tags.album_artist = Some(value),
|
||||||
"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
|
"TRACKNUMBER" => tags.track_number = value.parse::<u32>().ok(),
|
||||||
"DATE" => tags.year = value.parse::<i32>().ok(),
|
"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
|
||||||
_ => (),
|
"DATE" => tags.year = value.parse::<i32>().ok(),
|
||||||
}
|
_ => (),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(tags)
|
|
||||||
}
|
Ok(tags)
|
||||||
|
}
|
||||||
fn read_flac(path: &Path) -> Result<SongTags, PError> {
|
|
||||||
let tag = try!(metaflac::Tag::read_from_path(path));
|
fn read_flac(path: &Path) -> Result<SongTags, PError> {
|
||||||
let vorbis = try!(tag.vorbis_comments().ok_or(PError::MetadataDecodingError));
|
let tag = try!(metaflac::Tag::read_from_path(path));
|
||||||
let disc_number = vorbis.get("DISCNUMBER").and_then(|d| d[0].parse::<u32>().ok());
|
let vorbis = try!(tag.vorbis_comments().ok_or(PError::MetadataDecodingError));
|
||||||
let year = vorbis.get("DATE").and_then(|d| d[0].parse::<i32>().ok());
|
let disc_number = vorbis.get("DISCNUMBER").and_then(|d| d[0].parse::<u32>().ok());
|
||||||
Ok(SongTags {
|
let year = vorbis.get("DATE").and_then(|d| d[0].parse::<i32>().ok());
|
||||||
artist: vorbis.artist().map(|v| v[0].clone()),
|
Ok(SongTags {
|
||||||
album_artist: vorbis.album_artist().map(|v| v[0].clone()),
|
artist: vorbis.artist().map(|v| v[0].clone()),
|
||||||
album: vorbis.album().map(|v| v[0].clone()),
|
album_artist: vorbis.album_artist().map(|v| v[0].clone()),
|
||||||
title: vorbis.title().map(|v| v[0].clone()),
|
album: vorbis.album().map(|v| v[0].clone()),
|
||||||
disc_number: disc_number,
|
title: vorbis.title().map(|v| v[0].clone()),
|
||||||
track_number: vorbis.track(),
|
disc_number: disc_number,
|
||||||
year: year,
|
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);
|
||||||
}
|
}
|
BIN
test/sample.flac
Normal file
BIN
test/sample.flac
Normal file
Binary file not shown.
BIN
test/sample.mp3
Normal file
BIN
test/sample.mp3
Normal file
Binary file not shown.
BIN
test/sample.ogg
Normal file
BIN
test/sample.ogg
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue