diff --git a/Cargo.toml b/Cargo.toml index eb98df4..b510c46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ version = "0.4.0" git = "https://github.com/servo/rust-url" [dependencies] +id3 = { git = "https://github.com/jameshurst/rust-id3" } mount = "*" regex = "0.1" router = "*" diff --git a/src/api.rs b/src/api.rs index c67a0a6..20ce676 100644 --- a/src/api.rs +++ b/src/api.rs @@ -28,8 +28,11 @@ impl From for IronError { PError::ConfigFileReadError => IronError::new(err, status::InternalServerError), PError::ConfigFileParseError => IronError::new(err, status::InternalServerError), PError::ConfigMountDirsParseError => IronError::new(err, status::InternalServerError), - PError::ConfigAlbumArtPatternParseError => IronError::new(err, status::InternalServerError), + PError::ConfigAlbumArtPatternParseError => { + IronError::new(err, status::InternalServerError) + } PError::AlbumArtSearchError => IronError::new(err, status::InternalServerError), + PError::ID3ParseError => IronError::new(err, status::InternalServerError), } } } diff --git a/src/collection.rs b/src/collection.rs index 3283831..1ba40b5 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -3,6 +3,7 @@ use std::fs; use std::fs::File; use std::path::Path; use std::path::PathBuf; +use id3::Tag; use regex::Regex; use toml; @@ -11,19 +12,35 @@ use error::*; #[derive(Debug, RustcEncodable)] pub struct Album { - name: Option, - year: Option, + title: Option, + year: Option, album_art: Option, artist: Option, } -impl Album { - fn read(collection: &Collection, path: &Path) -> Result, PError> { - let name = None; - let year = None; - let artist = None; +#[derive(Debug, RustcEncodable)] +pub struct Song { + path: String, + album: Album, + track_number: Option, + title: Option, + artist: Option, +} - let album_art = collection.get_album_art(path).unwrap_or(None); +#[derive(Debug)] +pub struct SongTags { + track_number: Option, + title: Option, + artist: Option, + album_artist: Option, + album: Option, + year: Option, +} + +impl Album { + fn read(collection: &Collection, real_path: &Path) -> Result { + + let album_art = collection.get_album_art(real_path).unwrap_or(None); let album_art = match album_art { Some(p) => Some(try!(collection.vfs.real_to_virtual(p.as_path()))), None => None, @@ -33,21 +50,62 @@ impl Album { Some(a) => a.to_str().map(|p| p.to_string()), }; - Ok(Some(Album { - name: name, - year: year, - album_art: album_art, - artist: artist, - })) + let mut song_path = None; + if real_path.is_file() { + song_path = Some(real_path.to_path_buf()); + } else { + let find_song = try!(fs::read_dir(real_path)).find(|f| { + match *f { + Ok(ref dir_entry) => Song::is_song(dir_entry.path().as_path()), + _ => false, + } + }); + if let Some(dir_entry) = find_song { + song_path = Some(try!(dir_entry).path()); + } + }; + + let song_tags = song_path.map(|p| SongTags::read(p.as_path())); + if let Some(Ok(t)) = song_tags { + Ok(Album { + album_art: album_art, + title: t.album, + year: t.year, + artist: t.album_artist, + }) + } else { + Ok(Album { + album_art: album_art, + title: None, + year: None, + artist: None, + }) + } } } -#[derive(Debug, RustcEncodable)] -pub struct Song { - path: String, - album: Album, - title: Option, - artist: Option, +impl SongTags { + fn read(path: &Path) -> Result { + let tag = try!(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 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, + track_number: track_number, + year: year, + }) + } } impl Song { @@ -55,19 +113,26 @@ impl Song { let virtual_path = try!(collection.vfs.real_to_virtual(path)); let path_string = try!(virtual_path.to_str().ok_or(PError::PathDecoding)); - let name = virtual_path.file_stem().unwrap(); - let name = name.to_str().unwrap(); - let name = name.to_string(); - + let tags = SongTags::read(path).ok(); let album = try!(Album::read(collection, path)); - let album = album.unwrap(); - Ok(Song { - path: path_string.to_string(), - album: album, - artist: None, - title: Some(name), - }) + if let Some(t) = tags { + Ok(Song { + path: path_string.to_string(), + album: album, + artist: t.artist, + title: t.title, + track_number: t.track_number, + }) + } else { + Ok(Song { + path: path_string.to_string(), + album: album, + artist: None, + title: None, + track_number: None, + }) + } } fn is_song(path: &Path) -> bool { @@ -93,7 +158,7 @@ impl Song { pub struct Directory { path: String, name: String, - album: Option, + album: Album, } impl Directory { @@ -243,7 +308,7 @@ impl Collection { let file_path = file.path(); let file_path = file_path.as_path(); if file_meta.is_file() { - if Song::is_song( file_path ) { + if Song::is_song(file_path) { let song = try!(Song::read(self, file_path)); out.push(CollectionFile::Song(song)); } diff --git a/src/error.rs b/src/error.rs index 6868c52..3bf320b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,7 @@ use std::error; use std::fmt; use std::io; +use id3; #[derive(Debug)] pub enum PError { @@ -15,6 +16,7 @@ pub enum PError { ConfigMountDirsParseError, ConfigAlbumArtPatternParseError, AlbumArtSearchError, + ID3ParseError, } impl From for PError { @@ -23,6 +25,12 @@ impl From for PError { } } +impl From for PError { + fn from(_: id3::Error) -> PError { + PError::ID3ParseError + } +} + impl error::Error for PError { fn description(&self) -> &str { match *self { @@ -37,15 +45,18 @@ impl error::Error for PError { PError::ConfigFileReadError => "Could not read config file", PError::ConfigFileParseError => "Could not parse config file", PError::ConfigMountDirsParseError => "Could not parse mount directories in config file", - PError::ConfigAlbumArtPatternParseError => "Could not parse album art pattern in config file", + PError::ConfigAlbumArtPatternParseError => { + "Could not parse album art pattern in config file" + } PError::AlbumArtSearchError => "Error while looking for album art", + PError::ID3ParseError => "Error while reading ID3 tag", } } fn cause(&self) -> Option<&error::Error> { match *self { PError::Io(ref err) => Some(err), - _ => None, + _ => None, } } } @@ -64,8 +75,11 @@ impl fmt::Display for PError { PError::ConfigMountDirsParseError => { write!(f, "Could not parse mount directories in config file") } - PError::ConfigAlbumArtPatternParseError => write!(f, "Could not album art pattern in config file"), + PError::ConfigAlbumArtPatternParseError => { + write!(f, "Could not album art pattern in config file") + } PError::AlbumArtSearchError => write!(f, "Error while looking for album art"), + PError::ID3ParseError => write!(f, "Error while reading ID3 tag"), } } } diff --git a/src/main.rs b/src/main.rs index 9b6a1cd..85b644f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ extern crate core; extern crate iron; extern crate mount; extern crate regex; +extern crate id3; extern crate rustc_serialize; extern crate staticfile; extern crate toml;