Moved metadata decoding to a separate module

This commit is contained in:
Antoine Gersant 2016-11-16 16:51:09 -08:00
parent b5d9bca981
commit c3e5e27f23
5 changed files with 145 additions and 146 deletions

View file

@ -39,9 +39,7 @@ impl From<PError> for IronError {
PError::AlbumArtSearchError => IronError::new(err, status::InternalServerError),
PError::ImageProcessingError => IronError::new(err, status::InternalServerError),
PError::UnsupportedMetadataFormat => IronError::new(err, status::InternalServerError),
PError::APEParseError => IronError::new(err, status::InternalServerError),
PError::ID3ParseError => IronError::new(err, status::InternalServerError),
PError::VorbisParseError => IronError::new(err, status::InternalServerError),
PError::MetadataDecodingError => IronError::new(err, status::InternalServerError),
PError::Unauthorized => IronError::new(err, status::Unauthorized),
PError::IncorrectCredentials => IronError::new(err, status::BadRequest),
}

View file

@ -23,16 +23,14 @@ pub enum PError {
AlbumArtSearchError,
ImageProcessingError,
UnsupportedMetadataFormat,
APEParseError,
ID3ParseError,
VorbisParseError,
MetadataDecodingError,
Unauthorized,
IncorrectCredentials,
}
impl From<ape::Error> for PError {
fn from(_: ape::Error) -> PError {
PError::APEParseError
PError::MetadataDecodingError
}
}
@ -44,7 +42,7 @@ impl From<io::Error> for PError {
impl From<id3::Error> for PError {
fn from(_: id3::Error) -> PError {
PError::ID3ParseError
PError::MetadataDecodingError
}
}
@ -56,7 +54,7 @@ impl From<image::ImageError> for PError {
impl From<lewton::VorbisError> for PError {
fn from(_: lewton::VorbisError) -> PError {
PError::VorbisParseError
PError::MetadataDecodingError
}
}
@ -80,9 +78,7 @@ impl error::Error for PError {
PError::AlbumArtSearchError => "Error while looking for album art",
PError::ImageProcessingError => "Error while processing image",
PError::UnsupportedMetadataFormat => "Unsupported metadata format",
PError::APEParseError => "Error while reading APE tag",
PError::ID3ParseError => "Error while reading ID3 tag",
PError::VorbisParseError => "Error while reading Vorbis tag",
PError::MetadataDecodingError => "Error while reading song metadata",
PError::Unauthorized => "Authentication required",
PError::IncorrectCredentials => "Incorrect username/password combination",
}
@ -118,9 +114,7 @@ impl fmt::Display for PError {
PError::AlbumArtSearchError => write!(f, "Error while looking for album art"),
PError::ImageProcessingError => write!(f, "Error while processing image"),
PError::UnsupportedMetadataFormat => write!(f, "Unsupported metadata format"),
PError::APEParseError => write!(f, "Error while reading APE tag"),
PError::ID3ParseError => write!(f, "Error while reading ID3 tag"),
PError::VorbisParseError => write!(f, "Error while reading Vorbis tag"),
PError::MetadataDecodingError => write!(f, "Error while reading song metadata"),
PError::Unauthorized => write!(f, "Authentication required"),
PError::IncorrectCredentials => write!(f, "Incorrect username/password combination"),
}

View file

@ -1,8 +1,4 @@
use core::ops::Deref;
use ape;
use id3::Tag;
use lewton::inside_ogg::OggStreamReader;
use ogg::PacketReader;
use regex::Regex;
use sqlite;
use sqlite::{Connection, State, Statement, Value};
@ -13,8 +9,7 @@ use std::thread;
use std::time;
use error::*;
use utils;
use utils::AudioFormat;
use metadata::SongTags;
use vfs::Vfs;
const INDEX_BUILDING_INSERT_BUFFER_SIZE: usize = 250; // Insertions in each transaction
@ -41,131 +36,6 @@ pub struct Index {
sleep_duration: u64,
}
struct SongTags {
disc_number: Option<u32>,
track_number: Option<u32>,
title: Option<String>,
artist: Option<String>,
album_artist: Option<String>,
album: Option<String>,
year: Option<i32>,
}
impl SongTags {
fn read(path: &Path) -> Result<SongTags, PError> {
match utils::get_audio_format(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),
}
}
fn read_id3(path: &Path) -> Result<SongTags, PError> {
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 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,
})
}
fn read_ape_string(item: &ape::Item) -> Option<String> {
match item.value {
ape::ItemValue::Text(ref s) => Some(s.clone()),
_ => None,
}
}
fn read_ape_i32(item: &ape::Item) -> Option<i32> {
match item.value {
ape::ItemValue::Text(ref s) => s.parse::<i32>().ok(),
_ => None,
}
}
fn read_ape_x_of_y(item: &ape::Item) -> Option<u32> {
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<SongTags, PError> {
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<SongTags, PError> {
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::<u32>().ok(),
"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
"DATE" => tags.year = value.parse::<i32>().ok(),
_ => (),
}
}
Ok(tags)
}
}
#[derive(Debug, RustcEncodable)]
pub struct Song {
path: String,

View file

@ -41,6 +41,7 @@ mod config;
mod ddns;
mod error;
mod index;
mod metadata;
mod ui;
mod utils;
mod thumbnails;

136
src/metadata.rs Normal file
View file

@ -0,0 +1,136 @@
use ape;
use id3::Tag;
use lewton::inside_ogg::OggStreamReader;
use ogg::PacketReader;
use regex::Regex;
use std::fs;
use std::path::Path;
use error::PError;
use utils;
use utils::AudioFormat;
pub struct SongTags {
pub disc_number: Option<u32>,
pub track_number: Option<u32>,
pub title: Option<String>,
pub artist: Option<String>,
pub album_artist: Option<String>,
pub album: Option<String>,
pub year: Option<i32>,
}
impl SongTags {
pub fn read(path: &Path) -> Result<SongTags, PError> {
match utils::get_audio_format(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),
}
}
fn read_id3(path: &Path) -> Result<SongTags, PError> {
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 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,
})
}
fn read_ape_string(item: &ape::Item) -> Option<String> {
match item.value {
ape::ItemValue::Text(ref s) => Some(s.clone()),
_ => None,
}
}
fn read_ape_i32(item: &ape::Item) -> Option<i32> {
match item.value {
ape::ItemValue::Text(ref s) => s.parse::<i32>().ok(),
_ => None,
}
}
fn read_ape_x_of_y(item: &ape::Item) -> Option<u32> {
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<SongTags, PError> {
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<SongTags, PError> {
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::<u32>().ok(),
"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
"DATE" => tags.year = value.parse::<i32>().ok(),
_ => (),
}
}
Ok(tags)
}
}