Added song/album info from ID3v2 tags to results

This commit is contained in:
Antoine Gersant 2016-09-11 20:39:42 -07:00
parent f62113b83c
commit 3dc30317c8
5 changed files with 121 additions and 37 deletions

View file

@ -10,6 +10,7 @@ version = "0.4.0"
git = "https://github.com/servo/rust-url" git = "https://github.com/servo/rust-url"
[dependencies] [dependencies]
id3 = { git = "https://github.com/jameshurst/rust-id3" }
mount = "*" mount = "*"
regex = "0.1" regex = "0.1"
router = "*" router = "*"

View file

@ -28,8 +28,11 @@ impl From<PError> for IronError {
PError::ConfigFileReadError => IronError::new(err, status::InternalServerError), PError::ConfigFileReadError => IronError::new(err, status::InternalServerError),
PError::ConfigFileParseError => IronError::new(err, status::InternalServerError), PError::ConfigFileParseError => IronError::new(err, status::InternalServerError),
PError::ConfigMountDirsParseError => 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::AlbumArtSearchError => IronError::new(err, status::InternalServerError),
PError::ID3ParseError => IronError::new(err, status::InternalServerError),
} }
} }
} }

View file

@ -3,6 +3,7 @@ use std::fs;
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use id3::Tag;
use regex::Regex; use regex::Regex;
use toml; use toml;
@ -11,19 +12,35 @@ use error::*;
#[derive(Debug, RustcEncodable)] #[derive(Debug, RustcEncodable)]
pub struct Album { pub struct Album {
name: Option<String>, title: Option<String>,
year: Option<String>, year: Option<i32>,
album_art: Option<String>, album_art: Option<String>,
artist: Option<String>, artist: Option<String>,
} }
impl Album { #[derive(Debug, RustcEncodable)]
fn read(collection: &Collection, path: &Path) -> Result<Option<Album>, PError> { pub struct Song {
let name = None; path: String,
let year = None; album: Album,
let artist = None; track_number: Option<u32>,
title: Option<String>,
artist: Option<String>,
}
let album_art = collection.get_album_art(path).unwrap_or(None); #[derive(Debug)]
pub struct SongTags {
track_number: Option<u32>,
title: Option<String>,
artist: Option<String>,
album_artist: Option<String>,
album: Option<String>,
year: Option<i32>,
}
impl Album {
fn read(collection: &Collection, real_path: &Path) -> Result<Album, PError> {
let album_art = collection.get_album_art(real_path).unwrap_or(None);
let album_art = match album_art { let album_art = match album_art {
Some(p) => Some(try!(collection.vfs.real_to_virtual(p.as_path()))), Some(p) => Some(try!(collection.vfs.real_to_virtual(p.as_path()))),
None => None, None => None,
@ -33,21 +50,62 @@ impl Album {
Some(a) => a.to_str().map(|p| p.to_string()), Some(a) => a.to_str().map(|p| p.to_string()),
}; };
Ok(Some(Album { let mut song_path = None;
name: name, if real_path.is_file() {
year: year, song_path = Some(real_path.to_path_buf());
album_art: album_art, } else {
artist: artist, 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)] impl SongTags {
pub struct Song { fn read(path: &Path) -> Result<SongTags, PError> {
path: String, let tag = try!(Tag::read_from_path(path));
album: Album,
title: Option<String>, let artist = tag.artist().map(|s| s.to_string());
artist: Option<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 { impl Song {
@ -55,19 +113,26 @@ impl Song {
let virtual_path = try!(collection.vfs.real_to_virtual(path)); let virtual_path = try!(collection.vfs.real_to_virtual(path));
let path_string = try!(virtual_path.to_str().ok_or(PError::PathDecoding)); let path_string = try!(virtual_path.to_str().ok_or(PError::PathDecoding));
let name = virtual_path.file_stem().unwrap(); let tags = SongTags::read(path).ok();
let name = name.to_str().unwrap();
let name = name.to_string();
let album = try!(Album::read(collection, path)); let album = try!(Album::read(collection, path));
let album = album.unwrap();
Ok(Song { if let Some(t) = tags {
path: path_string.to_string(), Ok(Song {
album: album, path: path_string.to_string(),
artist: None, album: album,
title: Some(name), 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 { fn is_song(path: &Path) -> bool {
@ -93,7 +158,7 @@ impl Song {
pub struct Directory { pub struct Directory {
path: String, path: String,
name: String, name: String,
album: Option<Album>, album: Album,
} }
impl Directory { impl Directory {
@ -243,7 +308,7 @@ impl Collection {
let file_path = file.path(); let file_path = file.path();
let file_path = file_path.as_path(); let file_path = file_path.as_path();
if file_meta.is_file() { 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)); let song = try!(Song::read(self, file_path));
out.push(CollectionFile::Song(song)); out.push(CollectionFile::Song(song));
} }

View file

@ -1,6 +1,7 @@
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::io; use std::io;
use id3;
#[derive(Debug)] #[derive(Debug)]
pub enum PError { pub enum PError {
@ -15,6 +16,7 @@ pub enum PError {
ConfigMountDirsParseError, ConfigMountDirsParseError,
ConfigAlbumArtPatternParseError, ConfigAlbumArtPatternParseError,
AlbumArtSearchError, AlbumArtSearchError,
ID3ParseError,
} }
impl From<io::Error> for PError { impl From<io::Error> for PError {
@ -23,6 +25,12 @@ impl From<io::Error> for PError {
} }
} }
impl From<id3::Error> for PError {
fn from(_: id3::Error) -> PError {
PError::ID3ParseError
}
}
impl error::Error for PError { impl error::Error for PError {
fn description(&self) -> &str { fn description(&self) -> &str {
match *self { match *self {
@ -37,15 +45,18 @@ impl error::Error for PError {
PError::ConfigFileReadError => "Could not read config file", PError::ConfigFileReadError => "Could not read config file",
PError::ConfigFileParseError => "Could not parse config file", PError::ConfigFileParseError => "Could not parse config file",
PError::ConfigMountDirsParseError => "Could not parse mount directories in 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::AlbumArtSearchError => "Error while looking for album art",
PError::ID3ParseError => "Error while reading ID3 tag",
} }
} }
fn cause(&self) -> Option<&error::Error> { fn cause(&self) -> Option<&error::Error> {
match *self { match *self {
PError::Io(ref err) => Some(err), PError::Io(ref err) => Some(err),
_ => None, _ => None,
} }
} }
} }
@ -64,8 +75,11 @@ impl fmt::Display for PError {
PError::ConfigMountDirsParseError => { PError::ConfigMountDirsParseError => {
write!(f, "Could not parse mount directories in config file") 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::AlbumArtSearchError => write!(f, "Error while looking for album art"),
PError::ID3ParseError => write!(f, "Error while reading ID3 tag"),
} }
} }
} }

View file

@ -2,6 +2,7 @@ extern crate core;
extern crate iron; extern crate iron;
extern crate mount; extern crate mount;
extern crate regex; extern crate regex;
extern crate id3;
extern crate rustc_serialize; extern crate rustc_serialize;
extern crate staticfile; extern crate staticfile;
extern crate toml; extern crate toml;