#![crate_name = "taglib"]
#![crate_type = "lib"]
extern crate libc;
extern crate taglib_sys as sys;
use libc::c_char;
use std::ffi::{CString, CStr};
use std::path::Path;
use sys as ll;
fn c_str_to_str(c_str: *const c_char) -> Option<String> {
if c_str.is_null() {
None
} else {
let bytes = unsafe { CStr::from_ptr(c_str).to_bytes() };
if bytes.is_empty() { None } else { Some(String::from_utf8_lossy(bytes).to_string()) }
}
}
fn u32_to_option(n: u32) -> Option<u32> {
if n == 0 { None } else { Some(n) }
}
pub struct File {
raw: *mut ll::TagLib_File,
}
#[allow(dead_code)]
pub struct Tag<'a> {
raw: *mut ll::TagLib_Tag,
file: &'a File,
}
#[allow(dead_code)]
pub struct AudioProperties<'a> {
raw: *const ll::TagLib_AudioProperties,
file: &'a File,
}
impl<'a> Tag<'a> {
pub fn title(&self) -> Option<String> {
let res = unsafe { ll::taglib_tag_title(self.raw) };
c_str_to_str(res)
}
pub fn set_title(&mut self, title: &str) {
let cs = CString::new(title).unwrap();
let s = cs.as_ptr();
unsafe {
ll::taglib_tag_set_title(self.raw, s);
}
}
pub fn artist(&self) -> Option<String> {
let res = unsafe { ll::taglib_tag_artist(self.raw) };
c_str_to_str(res)
}
pub fn set_artist(&mut self, artist: &str) {
let cs = CString::new(artist).unwrap();
let s = cs.as_ptr();
unsafe {
ll::taglib_tag_set_artist(self.raw, s);
}
}
pub fn album(&self) -> Option<String> {
let res = unsafe { ll::taglib_tag_album(self.raw) };
c_str_to_str(res)
}
pub fn set_album(&mut self, album: &str) {
let cs = CString::new(album).unwrap();
let s = cs.as_ptr();
unsafe {
ll::taglib_tag_set_album(self.raw, s);
}
}
pub fn comment(&self) -> Option<String> {
let res = unsafe { ll::taglib_tag_comment(self.raw) };
c_str_to_str(res)
}
pub fn set_comment(&mut self, comment: &str) {
let cs = CString::new(comment).unwrap();
let s = cs.as_ptr();
unsafe {
ll::taglib_tag_set_comment(self.raw, s);
}
}
pub fn genre(&self) -> Option<String> {
let res = unsafe { ll::taglib_tag_genre(self.raw) };
c_str_to_str(res)
}
pub fn set_genre(&mut self, genre: &str) {
let cs = CString::new(genre).unwrap();
let s = cs.as_ptr();
unsafe {
ll::taglib_tag_set_genre(self.raw, s);
}
}
pub fn year(&self) -> Option<u32> {
u32_to_option(unsafe { ll::taglib_tag_year(self.raw) as u32 })
}
pub fn set_year(&mut self, year: u32) {
unsafe {
ll::taglib_tag_set_year(self.raw, year);
}
}
pub fn track(&self) -> Option<u32> {
u32_to_option(unsafe { ll::taglib_tag_track(self.raw) as u32 })
}
pub fn set_track(&mut self, track: u32) {
unsafe {
ll::taglib_tag_set_track(self.raw, track);
}
}
}
impl<'a> AudioProperties<'a> {
pub fn length(&self) -> u32 {
unsafe { ll::taglib_audioproperties_length(self.raw) as u32 }
}
pub fn bitrate(&self) -> u32 {
unsafe { ll::taglib_audioproperties_bitrate(self.raw) as u32 }
}
pub fn samplerate(&self) -> u32 {
unsafe { ll::taglib_audioproperties_samplerate(self.raw) as u32 }
}
pub fn channels(&self) -> u32 {
unsafe { ll::taglib_audioproperties_channels(self.raw) as u32 }
}
}
#[derive(Copy, Clone, PartialEq)]
pub enum FileType {
MPEG = ll::TAGLIB_FILE_MPEG as isize,
OggVorbis = ll::TAGLIB_FILE_OGG_VORBIS as isize,
FLAC = ll::TAGLIB_FILE_FLAC as isize,
MPC = ll::TAGLIB_FILE_MPC as isize,
OggFlac = ll::TAGLIB_FILE_OGG_FLAC as isize,
WavPack = ll::TAGLIB_FILE_WAV_PACK as isize,
Speex = ll::TAGLIB_FILE_SPEEX as isize,
TrueAudio = ll::TAGLIB_FILE_TRUE_AUDIO as isize,
MP4 = ll::TAGLIB_FILE_MP4 as isize,
ASF = ll::TAGLIB_FILE_ASF as isize,
}
#[derive(Debug)]
pub enum FileError {
InvalidFile,
InvalidFileName,
NoAvailableTag,
NoAvailableAudioProperties,
}
impl Drop for File {
fn drop(&mut self) {
unsafe {
ll::taglib_file_free(self.raw);
}
}
}
impl File {
pub fn new<P: AsRef<Path>>(path: P) -> Result<File, FileError> {
let filename = path.as_ref().to_str().ok_or(FileError::InvalidFileName)?;
let filename_c = CString::new(filename).ok().ok_or(FileError::InvalidFileName)?;
let filename_c_ptr = filename_c.as_ptr();
let f = unsafe { ll::taglib_file_new(filename_c_ptr) };
if f.is_null() {
return Err(FileError::InvalidFile);
}
Ok(File { raw: f })
}
pub fn new_type(filename: &str, filetype: FileType) -> Result<File, FileError> {
let filename_c = match CString::new(filename) {
Ok(s) => s,
_ => return Err(FileError::InvalidFileName),
};
let filename_c_ptr = filename_c.as_ptr();
let f = unsafe { ll::taglib_file_new_type(filename_c_ptr, filetype as u32) };
if f.is_null() {
return Err(FileError::InvalidFile);
}
Ok(File { raw: f })
}
pub fn tag(&self) -> Result<Tag, FileError> {
let res = unsafe { ll::taglib_file_tag(self.raw) };
if res.is_null() {
Err(FileError::NoAvailableTag)
} else {
Ok(Tag {
raw: res,
file: self,
})
}
}
pub fn is_valid(&self) -> bool {
unsafe { ll::taglib_file_is_valid(self.raw) != 0 }
}
pub fn audioproperties(&self) -> Result<AudioProperties, FileError> {
let res = unsafe { ll::taglib_file_audioproperties(self.raw) };
if res.is_null() {
Err(FileError::NoAvailableAudioProperties)
} else {
Ok(AudioProperties {
raw: res,
file: self,
})
}
}
pub fn save(&self) -> bool {
unsafe { ll::taglib_file_save(self.raw) != 0 }
}
}
#[cfg(test)]
mod test {
const TEST_MP3: &'static str = "fixtures/test.mp3";
use super::*;
use std::fs;
use std::path::PathBuf;
#[test]
fn test_get_tag() {
let file = File::new(TEST_MP3).unwrap();
let tag = file.tag().unwrap();
assert_eq!(tag.artist().unwrap(), "Artist");
}
#[test]
fn test_get_pathbuf() {
let file = File::new(PathBuf::from(TEST_MP3)).unwrap();
let tag = file.tag().unwrap();
assert_eq!(tag.artist().unwrap(), "Artist");
}
#[test]
fn test_get_no_tag() {
let file = File::new(TEST_MP3).unwrap();
let tag = file.tag().unwrap();
assert_eq!(tag.album(), None);
}
#[test]
fn test_get_tag_new_type() {
let file = File::new_type(TEST_MP3, FileType::MPEG).unwrap();
let tag = file.tag().unwrap();
assert_eq!(tag.artist().unwrap(), "Artist");
}
#[test]
fn test_get_audioproperties() {
let file = File::new(TEST_MP3).unwrap();
let ap = file.audioproperties().unwrap();
assert_eq!(ap.length(), 0);
}
#[test]
fn test_set_tag() {
let temp_fn = "fixtures/temp.mp3";
fs::copy(TEST_MP3, temp_fn).unwrap();
let file = File::new(temp_fn).unwrap();
let mut tag = file.tag().unwrap();
tag.set_artist("Not Artist");
assert_eq!(tag.artist().unwrap(), "Not Artist");
file.save();
let file = File::new(temp_fn).unwrap();
let tag = file.tag().unwrap();
assert_eq!(tag.artist().unwrap(), "Not Artist");
fs::remove_file(temp_fn).unwrap();
}
}