Added song/album info from ID3v2 tags to results
This commit is contained in:
parent
f62113b83c
commit
3dc30317c8
5 changed files with 121 additions and 37 deletions
|
@ -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 = "*"
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
20
src/error.rs
20
src/error.rs
|
@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Reference in a new issue