extern crate byteorder;
extern crate hex;
use error::{Error, ErrorKind, Result};
use self::byteorder::{ReadBytesExt, WriteBytesExt, BE};
use std::collections::HashMap;
use std::convert::TryInto;
use std::io::{Read, Write};
use std::iter::repeat;
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BlockType {
StreamInfo,
Padding,
Application,
SeekTable,
VorbisComment,
CueSheet,
Picture,
Unknown(u8),
}
#[allow(missing_docs)]
impl BlockType {
fn to_u8(&self) -> u8 {
match *self {
BlockType::StreamInfo => 0,
BlockType::Padding => 1,
BlockType::Application => 2,
BlockType::SeekTable => 3,
BlockType::VorbisComment => 4,
BlockType::CueSheet => 5,
BlockType::Picture => 6,
BlockType::Unknown(n) => n as u8,
}
}
fn from_u8(n: u8) -> BlockType {
match n {
0 => BlockType::StreamInfo,
1 => BlockType::Padding,
2 => BlockType::Application,
3 => BlockType::SeekTable,
4 => BlockType::VorbisComment,
5 => BlockType::CueSheet,
6 => BlockType::Picture,
n => BlockType::Unknown(n),
}
}
}
#[derive(Clone, Debug)]
pub enum Block {
StreamInfo(StreamInfo),
Application(Application),
CueSheet(CueSheet),
Padding(u32),
Picture(Picture),
SeekTable(SeekTable),
VorbisComment(VorbisComment),
Unknown((u8, Vec<u8>)),
}
impl Block {
pub fn read_from(reader: &mut dyn Read) -> Result<(bool, u32, Block)> {
let byte = reader.read_u8()?;
let is_last = (byte & 0x80) != 0;
let blocktype_byte = byte & 0x7F;
let blocktype = BlockType::from_u8(blocktype_byte);
let length = reader.read_uint::<BE>(3)? as u32;
debug!("Reading block {:?} with {} bytes", blocktype, length);
let mut data = Vec::new();
reader.take(length as u64).read_to_end(&mut data).unwrap();
let block = match blocktype {
BlockType::StreamInfo => Block::StreamInfo(StreamInfo::from_bytes(&data[..])),
BlockType::Padding => Block::Padding(length),
BlockType::Application => Block::Application(Application::from_bytes(&data[..])),
BlockType::SeekTable => Block::SeekTable(SeekTable::from_bytes(&data[..])),
BlockType::VorbisComment => Block::VorbisComment(VorbisComment::from_bytes(&data[..])?),
BlockType::Picture => Block::Picture(Picture::from_bytes(&data[..])?),
BlockType::CueSheet => Block::CueSheet(CueSheet::from_bytes(&data[..])?),
BlockType::Unknown(_) => Block::Unknown((blocktype_byte, data)),
};
debug!("{:?}", block);
Ok((is_last, length + 4, block))
}
pub fn write_to(&self, is_last: bool, writer: &mut dyn Write) -> Result<u32> {
let (content_len, contents) = match *self {
Block::StreamInfo(ref streaminfo) => {
let bytes = streaminfo.to_bytes();
(bytes.len() as u32, Some(bytes))
}
Block::Application(ref application) => {
let bytes = application.to_bytes();
(bytes.len() as u32, Some(bytes))
}
Block::CueSheet(ref cuesheet) => {
let bytes = cuesheet.to_bytes();
(bytes.len() as u32, Some(bytes))
}
Block::Padding(size) => (size, None),
Block::Picture(ref picture) => {
let bytes = picture.to_bytes();
(bytes.len() as u32, Some(bytes))
}
Block::SeekTable(ref seektable) => {
let bytes = seektable.to_bytes();
(bytes.len() as u32, Some(bytes))
}
Block::VorbisComment(ref vorbis) => {
let bytes = vorbis.to_bytes();
(bytes.len() as u32, Some(bytes))
}
Block::Unknown((_, ref bytes)) => (bytes.len() as u32, Some(bytes.clone())),
};
debug!(
"Writing block {:?} with {} bytes",
self.block_type(),
content_len
);
let mut byte: u8 = 0;
if is_last {
byte |= 0x80;
}
byte |= self.block_type().to_u8() & 0x7F;
writer.write_u8(byte)?;
writer.write_all(&content_len.to_be_bytes()[1..])?;
match contents {
Some(bytes) => writer.write_all(&bytes[..])?,
None => {
let zeroes = [0; 1024];
let mut remaining = content_len as usize;
loop {
if remaining <= zeroes.len() {
writer.write_all(&zeroes[..remaining])?;
break;
} else {
writer.write_all(&zeroes[..])?;
remaining -= zeroes.len();
}
}
}
}
Ok(content_len + 4)
}
pub fn block_type(&self) -> BlockType {
match *self {
Block::StreamInfo(_) => BlockType::StreamInfo,
Block::Application(_) => BlockType::Application,
Block::CueSheet(_) => BlockType::CueSheet,
Block::Padding(_) => BlockType::Padding,
Block::Picture(_) => BlockType::Picture,
Block::SeekTable(_) => BlockType::SeekTable,
Block::VorbisComment(_) => BlockType::VorbisComment,
Block::Unknown((blocktype, _)) => BlockType::Unknown(blocktype),
}
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct StreamInfo {
pub min_block_size: u16,
pub max_block_size: u16,
pub min_frame_size: u32,
pub max_frame_size: u32,
pub sample_rate: u32,
pub num_channels: u8,
pub bits_per_sample: u8,
pub total_samples: u64,
pub md5: Vec<u8>,
}
impl ::std::fmt::Debug for StreamInfo {
fn fmt(&self, out: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(out, "StreamInfo {{ min_block_size: {}, max_block_size: {}, min_frame_size: {}, max_frame_size: {}, sample_rate: {}, num_channels: {}, bits_per_sample: {}, total_samples: {}, md5: {} }}", self.min_block_size, self.max_block_size, self.min_frame_size, self.max_frame_size, self.sample_rate, self.num_channels, self.bits_per_sample, self.total_samples, hex::encode(&self.md5[..]))
}
}
impl StreamInfo {
pub fn new() -> StreamInfo {
StreamInfo {
min_block_size: 0,
max_block_size: 0,
min_frame_size: 0,
max_frame_size: 0,
sample_rate: 0,
num_channels: 0,
bits_per_sample: 0,
total_samples: 0,
md5: Vec::new(),
}
}
pub fn from_bytes(bytes: &[u8]) -> StreamInfo {
let mut streaminfo = StreamInfo::new();
let mut i = 0;
streaminfo.min_block_size =
u16::from_be_bytes((&bytes[i..i + 2]).try_into().unwrap()) as u16;
i += 2;
streaminfo.max_block_size =
u16::from_be_bytes((&bytes[i..i + 2]).try_into().unwrap()) as u16;
i += 2;
streaminfo.min_frame_size = (&bytes[i..i + 3]).read_uint::<BE>(3).unwrap() as u32;
i += 3;
streaminfo.max_frame_size = (&bytes[i..i + 3]).read_uint::<BE>(3).unwrap() as u32;
i += 3;
let sample_first = u16::from_be_bytes((&bytes[i..i + 2]).try_into().unwrap()) as u16;
i += 2;
let sample_channel_bps = bytes[i];
i += 1;
streaminfo.sample_rate = (sample_first as u32) << 4 | (sample_channel_bps as u32) >> 4;
streaminfo.num_channels = ((sample_channel_bps >> 1) & 0x7) + 1;
let bps_total = (&bytes[i..i + 5]).read_uint::<BE>(5).unwrap();
i += 5;
streaminfo.bits_per_sample =
((sample_channel_bps & 0x1) << 4 | (bps_total >> 36) as u8) + 1;
streaminfo.total_samples = bps_total & 0xF_FF_FF_FF_FF;
streaminfo.md5 = bytes[i..i + 16].to_vec();
streaminfo
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend(self.min_block_size.to_be_bytes().iter());
bytes.extend(self.max_block_size.to_be_bytes().iter());
bytes.extend(self.min_frame_size.to_be_bytes()[1..].iter());
bytes.extend(self.max_frame_size.to_be_bytes()[1..].iter());
bytes.extend(((self.sample_rate >> 4) as u16).to_be_bytes().iter());
let byte = ((self.sample_rate & 0xF) << 4) as u8
| (((self.num_channels - 1) & 0x7) << 1) as u8
| (((self.bits_per_sample - 1) >> 4) & 0x1) as u8;
bytes.push(byte);
let byte = (((self.bits_per_sample - 1) & 0xF) << 4) as u8
| ((self.total_samples >> 32) & 0xF) as u8;
bytes.push(byte);
bytes.extend(
((self.total_samples & 0xFF_FF_FF_FF) as u32)
.to_be_bytes()
.iter(),
);
bytes.extend(self.md5.iter().cloned());
bytes
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct Application {
pub id: Vec<u8>,
pub data: Vec<u8>,
}
impl ::std::fmt::Debug for Application {
fn fmt(&self, out: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(
out,
"Application {{ id: {}, data: {:?} }}",
hex::encode(&self.id[..]),
self.data
)
}
}
impl Application {
pub fn new() -> Application {
Application {
id: Vec::new(),
data: Vec::new(),
}
}
pub fn from_bytes(bytes: &[u8]) -> Application {
let mut application = Application::new();
let mut i = 0;
application.id = bytes[i..i + 4].to_vec();
i += 4;
application.data = bytes[i..].to_vec();
application
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend(self.id.iter().cloned());
bytes.extend(self.data.iter().cloned());
bytes
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CueSheetTrackIndex {
pub offset: u64,
pub point_num: u8,
}
impl CueSheetTrackIndex {
pub fn new() -> CueSheetTrackIndex {
CueSheetTrackIndex {
offset: 0,
point_num: 0,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CueSheetTrack {
pub offset: u64,
pub number: u8,
pub isrc: String,
pub is_audio: bool,
pub pre_emphasis: bool,
pub indices: Vec<CueSheetTrackIndex>,
}
impl CueSheetTrack {
pub fn new() -> CueSheetTrack {
CueSheetTrack {
offset: 0,
number: 0,
isrc: String::new(),
is_audio: true,
pre_emphasis: false,
indices: Vec::new(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CueSheet {
pub catalog_num: String,
pub num_leadin: u64,
pub is_cd: bool,
pub tracks: Vec<CueSheetTrack>,
}
impl CueSheet {
pub fn new() -> CueSheet {
CueSheet {
catalog_num: String::new(),
num_leadin: 0,
is_cd: true,
tracks: Vec::new(),
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<CueSheet> {
let mut cuesheet = CueSheet::new();
let mut i = 0;
cuesheet.catalog_num = String::from_utf8(bytes[i..i + 128].to_vec())?;
i += 128;
cuesheet.num_leadin = u64::from_be_bytes((&bytes[i..i + 8]).try_into().unwrap());
i += 8;
let flags = bytes[i];
i += 1;
cuesheet.is_cd = (flags & 0x80) != 0;
i += 258;
let num_tracks = bytes[i];
i += 1;
for _ in 0..num_tracks {
let mut track = CueSheetTrack::new();
track.offset = u64::from_be_bytes((&bytes[i..i + 8]).try_into().unwrap());
i += 8;
track.number = bytes[i];
i += 1;
track.isrc = String::from_utf8(bytes[i..i + 12].to_vec())?;
i += 12;
let flags = bytes[i];
i += 1;
track.is_audio = (flags & 0x80) == 0;
track.pre_emphasis = (flags & 0x40) != 0;
i += 13;
let num_indices = bytes[i];
i += 1;
for _ in 0..num_indices {
let mut index = CueSheetTrackIndex::new();
index.offset = u64::from_be_bytes((&bytes[i..i + 8]).try_into().unwrap());
i += 8;
index.point_num = bytes[i];
i += 1;
i += 3;
track.indices.push(index);
}
cuesheet.tracks.push(track);
}
Ok(cuesheet)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
assert!(self.catalog_num.len() <= 128);
bytes.extend(self.catalog_num.clone().into_bytes().into_iter());
bytes.extend(
repeat(0)
.take(128 - self.catalog_num.len())
.collect::<Vec<u8>>()
.into_iter(),
);
bytes.extend(self.num_leadin.to_be_bytes().iter());
let mut flags = 0;
if self.is_cd {
flags |= 0x80;
}
bytes.push(flags);
bytes.extend([0; 258].iter().cloned());
bytes.push(self.tracks.len() as u8);
for track in self.tracks.iter() {
assert!(track.isrc.len() <= 12);
bytes.extend(track.offset.to_be_bytes().iter());
bytes.push(track.number);
bytes.extend(track.isrc.clone().into_bytes().into_iter());
bytes.extend(
repeat(0)
.take(12 - track.isrc.len())
.collect::<Vec<u8>>()
.into_iter(),
);
let mut flags = 0;
if !track.is_audio {
flags |= 0x80;
}
if track.pre_emphasis {
flags |= 0x40;
}
bytes.push(flags);
bytes.extend([0; 13].iter().cloned());
bytes.push(track.indices.len() as u8);
for index in track.indices.iter() {
bytes.extend(index.offset.to_be_bytes().iter());
bytes.push(index.point_num);
bytes.extend([0; 3].iter().cloned());
}
}
bytes
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum PictureType {
Other,
Icon,
OtherIcon,
CoverFront,
CoverBack,
Leaflet,
Media,
LeadArtist,
Artist,
Conductor,
Band,
Composer,
Lyricist,
RecordingLocation,
DuringRecording,
DuringPerformance,
ScreenCapture,
BrightFish,
Illustration,
BandLogo,
PublisherLogo,
}
impl PictureType {
fn from_u32(n: u32) -> Option<PictureType> {
match n {
0 => Some(PictureType::Other),
1 => Some(PictureType::Icon),
2 => Some(PictureType::OtherIcon),
3 => Some(PictureType::CoverFront),
4 => Some(PictureType::CoverBack),
5 => Some(PictureType::Leaflet),
6 => Some(PictureType::Media),
7 => Some(PictureType::LeadArtist),
8 => Some(PictureType::Artist),
9 => Some(PictureType::Conductor),
10 => Some(PictureType::Band),
11 => Some(PictureType::Composer),
12 => Some(PictureType::Lyricist),
13 => Some(PictureType::RecordingLocation),
14 => Some(PictureType::DuringRecording),
15 => Some(PictureType::DuringPerformance),
16 => Some(PictureType::ScreenCapture),
17 => Some(PictureType::BrightFish),
18 => Some(PictureType::Illustration),
19 => Some(PictureType::BandLogo),
20 => Some(PictureType::PublisherLogo),
_ => None,
}
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct Picture {
pub picture_type: PictureType,
pub mime_type: String,
pub description: String,
pub width: u32,
pub height: u32,
pub depth: u32,
pub num_colors: u32,
pub data: Vec<u8>,
}
impl ::std::fmt::Debug for Picture {
fn fmt(&self, out: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(out, "Picture {{ picture_type: {:?}, mime_type: {}, description: {}, width: {}, height: {}, depth: {}, num_colors: {}, data: Vec<u8> ({}) }}", self.picture_type, self.mime_type, self.description, self.width, self.height, self.depth, self.num_colors, self.data.len())
}
}
impl Picture {
pub fn new() -> Picture {
Picture {
picture_type: PictureType::Other,
mime_type: String::new(),
description: String::new(),
width: 0,
height: 0,
depth: 0,
num_colors: 0,
data: Vec::new(),
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<Picture> {
let mut picture = Picture::new();
let mut i = 0;
let picture_type_u32 = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap());
picture.picture_type = match PictureType::from_u32(picture_type_u32) {
Some(picture_type) => picture_type,
None => {
debug!("Encountered invalid picture type: {}", picture_type_u32);
return Err(Error::new(ErrorKind::InvalidInput, "invalid picture type"));
}
};
i += 4;
let mime_length = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
picture.mime_type = String::from_utf8(bytes[i..i + mime_length].to_vec())?;
i += mime_length;
let description_length =
u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
picture.description = String::from_utf8(bytes[i..i + description_length].to_vec())?;
i += description_length;
picture.width = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap());
i += 4;
picture.height = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap());
i += 4;
picture.depth = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap());
i += 4;
picture.num_colors = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap());
i += 4;
let data_length = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
picture.data = bytes[i..i + data_length].to_vec();
Ok(picture)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend((self.picture_type as u32).to_be_bytes().iter());
let mime_type = self.mime_type.clone().into_bytes();
bytes.extend((mime_type.len() as u32).to_be_bytes().iter());
bytes.extend(mime_type.into_iter());
let description = self.description.clone().into_bytes();
bytes.extend((description.len() as u32).to_be_bytes().iter());
bytes.extend(description.into_iter());
bytes.extend(self.width.to_be_bytes().iter());
bytes.extend(self.height.to_be_bytes().iter());
bytes.extend(self.depth.to_be_bytes().iter());
bytes.extend(self.num_colors.to_be_bytes().iter());
let data = self.data.clone();
bytes.extend((data.len() as u32).to_be_bytes().iter());
bytes.extend(data.into_iter());
bytes
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SeekPoint {
sample_number: u64,
offset: u64,
num_samples: u16,
}
impl SeekPoint {
pub fn new() -> SeekPoint {
SeekPoint {
sample_number: 0,
offset: 0,
num_samples: 0,
}
}
pub fn from_bytes(bytes: &[u8]) -> SeekPoint {
let mut seekpoint = SeekPoint::new();
let mut i = 0;
seekpoint.sample_number = u64::from_be_bytes((&bytes[i..i + 8]).try_into().unwrap());
i += 8;
seekpoint.offset = u64::from_be_bytes((&bytes[i..i + 8]).try_into().unwrap());
i += 8;
seekpoint.num_samples = u16::from_be_bytes((&bytes[i..i + 2]).try_into().unwrap());
seekpoint
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend(self.sample_number.to_be_bytes().iter());
bytes.extend(self.offset.to_be_bytes().iter());
bytes.extend(self.num_samples.to_be_bytes().iter());
bytes
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SeekTable {
pub seekpoints: Vec<SeekPoint>,
}
impl SeekTable {
pub fn new() -> SeekTable {
SeekTable {
seekpoints: Vec::new(),
}
}
pub fn from_bytes(bytes: &[u8]) -> SeekTable {
let mut seektable = SeekTable::new();
let num_points = bytes.len() / 18;
let mut i = 0;
for _ in 0..num_points {
let seekpoint = SeekPoint::from_bytes(&bytes[i..i + 18]);
i += 18;
seektable.seekpoints.push(seekpoint);
}
seektable
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
for seekpoint in self.seekpoints.iter() {
bytes.extend(seekpoint.to_bytes().into_iter());
}
bytes
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VorbisComment {
pub vendor_string: String,
pub comments: HashMap<String, Vec<String>>,
}
impl VorbisComment {
pub fn new() -> VorbisComment {
VorbisComment {
vendor_string: String::new(),
comments: HashMap::new(),
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<VorbisComment> {
let mut vorbis = VorbisComment::new();
let mut i = 0;
let vendor_length = u32::from_le_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
vorbis.vendor_string = String::from_utf8(bytes[i..i + vendor_length].to_vec())?;
i += vendor_length;
let num_comments = u32::from_le_bytes((&bytes[i..i + 4]).try_into().unwrap());
i += 4;
for _ in 0..num_comments {
let comment_length =
u32::from_le_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
let comments = String::from_utf8(bytes[i..i + comment_length].to_vec())?;
i += comment_length;
let comments_split: Vec<&str> = comments.splitn(2, '=').collect();
let key = comments_split[0].to_ascii_uppercase();
let value = comments_split[1].to_owned();
if vorbis.comments.contains_key(&key) {
vorbis.comments.get_mut(&key).unwrap().push(value);
} else {
vorbis.comments.insert(key, vec![value]);
}
}
Ok(vorbis)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let vendor_string = self.vendor_string.clone().into_bytes();
bytes.extend((vendor_string.len() as u32).to_le_bytes().iter());
bytes.extend(vendor_string.into_iter());
bytes.extend(
(self
.comments
.values()
.fold(0u32, |acc, l| acc + l.len() as u32))
.to_le_bytes()
.iter(),
);
for (key, list) in self.comments.iter() {
for value in list.iter() {
let comment_string = format!("{}={}", key, value);
debug!("Writing comment: {}", comment_string);
let comment = comment_string.into_bytes();
bytes.extend((comment.len() as u32).to_le_bytes().iter());
bytes.extend(comment.into_iter());
}
}
bytes
}
pub fn get(&self, key: &str) -> Option<&Vec<String>> {
self.comments.get(key)
}
pub fn set<K: Into<String>, V: Into<String>>(&mut self, key: K, values: Vec<V>) {
let key_owned = key.into();
self.remove(&key_owned[..]);
self.comments
.insert(key_owned, values.into_iter().map(|s| s.into()).collect());
}
pub fn remove(&mut self, key: &str) {
self.comments.remove(key);
}
pub fn remove_pair(&mut self, key: &str, value: &str) {
match self.comments.get_mut(key) {
Some(list) => list.retain(|s| &s[..] != value),
None => {}
}
let mut num_values = 0;
if let Some(values) = self.get(key) {
num_values = values.len();
}
if num_values == 0 {
self.remove(key)
}
}
pub fn artist(&self) -> Option<&Vec<String>> {
self.get("ARTIST")
}
pub fn set_artist<T: Into<String>>(&mut self, artists: Vec<T>) {
self.remove("ARTISTSORT");
self.set("ARTIST", artists);
}
pub fn remove_artist(&mut self) {
self.remove("ARTISTSORT");
self.remove("ARTIST");
}
pub fn album(&self) -> Option<&Vec<String>> {
self.get("ALBUM")
}
pub fn set_album<T: Into<String>>(&mut self, albums: Vec<T>) {
self.remove("ALBUMSORT");
self.set("ALBUM", albums);
}
pub fn remove_album(&mut self) {
self.remove("ALBUMSORT");
self.remove("ALBUM");
}
pub fn genre(&self) -> Option<&Vec<String>> {
self.get("GENRE")
}
pub fn set_genre<T: Into<String>>(&mut self, genres: Vec<T>) {
self.set("GENRE", genres);
}
pub fn remove_genre(&mut self) {
self.remove("GENRE");
}
pub fn title(&self) -> Option<&Vec<String>> {
self.get("TITLE")
}
pub fn set_title<T: Into<String>>(&mut self, title: Vec<T>) {
self.remove("TITLESORT");
self.set("TITLE", title);
}
pub fn remove_title(&mut self) {
self.remove("TITLESORT");
self.remove("TITLE");
}
pub fn track(&self) -> Option<u32> {
self.get("TRACKNUMBER").and_then(|s| {
if s.len() > 0 {
s[0].parse::<u32>().ok()
} else {
None
}
})
}
pub fn set_track(&mut self, track: u32) {
self.set("TRACKNUMBER", vec![format!("{}", track)]);
}
pub fn remove_track(&mut self) {
self.remove("TRACKNUMBER");
}
pub fn total_tracks(&self) -> Option<u32> {
self.get("TOTALTRACKS").and_then(|s| {
if s.len() > 0 {
s[0].parse::<u32>().ok()
} else {
None
}
})
}
pub fn set_total_tracks(&mut self, total_tracks: u32) {
self.set("TOTALTRACKS", vec![format!("{}", total_tracks)]);
}
pub fn remove_total_tracks(&mut self) {
self.remove("TOTALTRACKS");
}
pub fn album_artist(&self) -> Option<&Vec<String>> {
self.get("ALBUMARTIST")
}
pub fn set_album_artist<T: Into<String>>(&mut self, album_artists: Vec<T>) {
self.remove("ALBUMARTISTSORT");
self.set("ALBUMARTIST", album_artists);
}
pub fn remove_album_artist(&mut self) {
self.remove("ALBUMARTISTSORT");
self.remove("ALBUMARTIST");
}
pub fn lyrics(&self) -> Option<&Vec<String>> {
self.get("LYRICS")
}
pub fn set_lyrics<T: Into<String>>(&mut self, lyrics: Vec<T>) {
self.set("LYRICS", lyrics);
}
pub fn remove_lyrics(&mut self) {
self.remove("LYRICS");
}
}