packages feed

htaglib 0.1.1 → 1.0.0

raw patch · 10 files changed

+429/−212 lines, 10 filesdep +bytestringdep +text

Dependencies added: bytestring, text

Files

CHANGELOG.md view
@@ -1,3 +1,15 @@+## HTagLib 1.0.0++* Make the module `Sound.HTagLib.Internal` hidden for end users.++* Make `Text` underlying type for wrappers around textual data (breaking+  change, previously it was `String`).++* Rename functions like `getTitle` to `unTitle` (breaking change).++* Fix bug when wrong data is read from files when current locale specifies+  encoding other than UTF-8.+ ## HTagLib 0.1.1  * Missing audio samples used for testing are included in distribution.
+ README.md view
@@ -0,0 +1,189 @@+# HTagLib++[![License BSD3](https://img.shields.io/badge/license-BSD3-brightgreen.svg)](http://opensource.org/licenses/BSD-3-Clause)+[![Hackage](https://img.shields.io/hackage/v/htaglib.svg?style=flat)](https://hackage.haskell.org/package/htaglib)+[![Stackage Nightly](http://stackage.org/package/htaglib/badge/nightly)](http://stackage.org/nightly/package/htaglib)+[![Build Status](https://travis-ci.org/mrkkrp/htaglib.svg?branch=master)](https://travis-ci.org/mrkkrp/htaglib)+[![Coverage Status](https://coveralls.io/repos/mrkkrp/htaglib/badge.svg?branch=master&service=github)](https://coveralls.io/github/mrkkrp/htaglib?branch=master)++* [Alternatives](#alternatives)+* [Quick start](#quick-start)+    * [Reading meta data](#reading-meta-data)+    * [Writing meta data](#writing-meta-data)+    * [Conclusion](#conclusion)+* [License](#license)++This is Haskell bindings to [TagLib](https://taglib.github.io/), library for+reading and editing meta-data of several popular audio formats. This library+is easy to use and type-safe.++It works with the following formats:++* MP3+* FLAC+* MPC+* Speex+* WavPack TrueAudio+* WAV+* AIFF+* MP4+* ASF++This happens in abstract, uniform way, so you don't need to handle any+low-level details. As a consequence, it's currently not possible to work+with format-specific functionality.++## Alternatives++There is at least two Haskell bindings doing “the same” thing:++* [`libtagc`](https://hackage.haskell.org/package/libtagc)+* [`taglib`](https://hackage.haskell.org/package/taglib)++Both are very low level, without any protection or higher-level+abstractions, not really type-safe. I personally don't want to use them, so+I wrote this.++## Quick start++First, since this is bindings to C-interface of the library, you'll need to+install the library itself. If you're on Unix-like system, chances are+you'll have it in official repositories of your distro. Users of other+systems should also be able to install it without particular pain.++After installation of the library, install `htaglib` package using Cabal or+Stack (recommended):++```+$ stack install htaglib+```++### Reading meta data++Now to the hacking. It's recommended that you define a record representing+meta-data of audio track in your program, like this:++```haskell+module Main (main) where++import Data.Monoid+import Sound.HTagLib+import System.Environment (getArgs)++data AudioTrack = AudioTrack+  { atTitle   :: Title+  , atArtist  :: Artist+  , atAlbum   :: Album+  , atComment :: Comment+  , atGenre   :: Genre+  , atYear    :: Maybe Year+  , atTrack   :: Maybe TrackNumber }+  deriving Show+```++A couple of notes here. We use unique types for every component of meta+data, so it's more difficult to use track title in lieu of track artist, for+example. Meta data that is represented by strings also has smart+constructors, they replace zero bytes with spaces, this is necessary to+avoid troubles when your Haskell strings go to C-level (well, zero-bytes in+strings is rather edge case, but it should be mentioned). Of course,+`Title`, `Artist`, `Album`, `Comment`, and `Genre` all are instances of+`IsString`, so just turn on `OverloadedStrings` and you can use normal+string literals to create data of these types.++`Year` and `TrackNumber` may be not set or missing, in this case you get+`Nothing`. This is possible with string-based fields too, but in that case+you just get empty strings. `Year` and `TrackNumber` have smart constructors+that make sure that the values are positive (i.e. zero is not allowed).++OK, it's time to read some info. There is `TagGetter` type which is an+applicative functor. You first construct `TagGetter` which will retrieve+entire `AudioTrack` for you using applicative style:++```haskell+audioTrackGetter :: TagGetter AudioTrack+audioTrackGetter = AudioTrack+  <$> titleGetter+  <*> artistGetter+  <*> albumGetter+  <*> commentGetter+  <*> genreGetter+  <*> yearGetter+  <*> trackNumberGetter+```++Perfect, now use `getTags` to read entire record:++```haskell+main :: IO ()+main = do+  path  <- head <$> getArgs+  track <- getTags path audioTrackGetter+  print track+```++For example (alignment is added):++```+$ ./example "/home/mark/music/David Bowie/1977, Low/01 Speed of Life.flac"+AudioTrack+  { atTitle   = Title   {unTitle   = "Speed of Life"}+  , atArtist  = Artist  {unArtist  = "David Bowie"}+  , atAlbum   = Album   {unAlbum   = "Low"}+  , atComment = Comment {unComment = ""}+  , atGenre   = Genre   {unGenre   = ""}+  , atYear    = Just    (Year {unYear = 1977})+  , atTrack   = Just    (TrackNumber {unTrackNumber = 1})+  }+```++Success! It's also possible to extract audio properties like sample rate,+etc. but it's not shown here for simplicity, consult Haddocks for more+information.++N.B. If you need to extract duration of tracks, TagLib only returns number+of seconds as an integer. This means that if you want to calculate total+duration, you'll have slightly incorrect result. Proper solution is to+extract duration as floating-point number, for that we recommend bindings to+`libsndfile` — [`hsndfile`](https://hackage.haskell.org/package/hsndfile).++### Writing meta data++We cannot use applicative interface to set tags. There are several reasons:++* Applicative interface in general is better for extracting or parsing (or+  rather assembling complex parsers from more basic ones).++* Some fields like sample rate or length can only be read, not set.++* We may wish to set one or two fields selectively, not everything.++Solution: use monoids. `TagSetter` is an instance of `Monoid`. This means+that we can set title and artist of audio track like this:++```haskell+main :: IO ()+main = do+  (path : title : artist : _) <- getArgs+  setTags path Nothing $+    titleSetter (mkTitle title) <>+    artistSetter (mkArtist artist)+  track <- getTags path audioTrackGetter+  print track+```++This code loads file and changes “title” and “artist” meta data+fields.++## Conclusion++With the interface provided by `getTags` and `setTags` it's not possible to+forget to close file or free some resource. You can read all meta data at+once directly into your data structure in type-safe manner. Writing meta+data should be trivial too. Have fun!++## License++Copyright © 2015 Mark Karpov++Distributed under BSD 3 clause license.
Sound/HTagLib.hs view
@@ -14,37 +14,37 @@   ( -- * Data types     Title   , mkTitle-  , getTitle+  , unTitle   , Artist   , mkArtist-  , getArtist+  , unArtist   , Album   , mkAlbum-  , getAlbum+  , unAlbum   , Comment   , mkComment-  , getComment+  , unComment   , Genre   , mkGenre-  , getGenre+  , unGenre   , Year   , mkYear-  , getYear+  , unYear   , TrackNumber   , mkTrackNumber-  , getTrackNumber+  , unTrackNumber   , Duration   , mkDuration-  , getDuration+  , unDuration   , BitRate   , mkBitRate-  , getBitRate+  , unBitRate   , SampleRate   , mkSampleRate-  , getSampleRate+  , unSampleRate   , Channels   , mkChannels-  , getChannels+  , unChannels   , FileType (..)   , ID3v2Encoding (..)   , HTagLibException (..)@@ -77,9 +77,5 @@ where  import Sound.HTagLib.Getter-import Sound.HTagLib.Internal-  ( FileType (..)-  , ID3v2Encoding (..)-  , HTagLibException (..) ) import Sound.HTagLib.Setter import Sound.HTagLib.Type
Sound/HTagLib/Getter.hs view
@@ -14,7 +14,8 @@ {-# LANGUAGE CPP #-}  module Sound.HTagLib.Getter-  ( TagGetter+  ( -- * High-level API+    TagGetter   , getTags   , getTags'     -- * Built-in getters@@ -49,8 +50,8 @@ instance Applicative TagGetter where   pure    = TagGetter . const . return   x <*> y = TagGetter $ \fid ->-              do f <- runGetter x fid-                 f <$> runGetter y fid+    do f <- runGetter x fid+       f <$> runGetter y fid  -- | @getTags path g@ will try to read file located at @path@ and read meta -- data of the file using getter @g@. Type of file will be guessed from its@@ -59,27 +60,30 @@ -- -- In case of trouble 'I.HTagLibException' will be thrown. -getTags :: FilePath    -- ^ Path to audio file-        -> TagGetter a -- ^ Getter-        -> IO a        -- ^ Extracted data+getTags+  :: FilePath          -- ^ Path to audio file+  -> TagGetter a       -- ^ Getter+  -> IO a              -- ^ Extracted data getTags path = execGetter path Nothing  -- | This is essentially the same as 'getTags', but allows to explicitly -- choose file type (see 'I.FileType'). -getTags' :: FilePath    -- ^ Path to audio file-         -> I.FileType  -- ^ Type of audio file-         -> TagGetter a -- ^ Getter-         -> IO a        -- ^ Extracted data+getTags'+  :: FilePath          -- ^ Path to audio file+  -> FileType          -- ^ Type of audio file+  -> TagGetter a       -- ^ Getter+  -> IO a              -- ^ Extracted data getTags' path t = execGetter path (Just t)  -- | This is the most general way to read meta data from file. 'getTags' and -- 'getTags'' are just wrappers around the function. -execGetter :: FilePath         -- ^ Path to audio file-           -> Maybe I.FileType -- ^ Type of audio file (if known)-           -> TagGetter a      -- ^ Getter-           -> IO a             -- ^ Extracted data+execGetter+  :: FilePath         -- ^ Path to audio file+  -> Maybe FileType   -- ^ Type of audio file (if known)+  -> TagGetter a      -- ^ Getter+  -> IO a             -- ^ Extracted data execGetter path t = I.withFile path t . runGetter  -- | Getter to retrieve track title.
Sound/HTagLib/Internal.hs view
@@ -11,16 +11,12 @@ -- see "Sound.HTagLib" instead.  {-# LANGUAGE CPP                      #-}-{-# LANGUAGE DeriveDataTypeable       #-} {-# LANGUAGE EmptyDataDecls           #-} {-# LANGUAGE ForeignFunctionInterface #-}  module Sound.HTagLib.Internal   ( -- * Data types     FileId-  , FileType (..)-  , ID3v2Encoding (..)-  , HTagLibException (..)     -- * File API   , withFile   , saveFile@@ -48,14 +44,15 @@   , id3v2SetEncoding ) where -import Control.Exception+import Control.Exception (throw, bracket) import Control.Monad (when, unless)+import Data.ByteString (packCString, useAsCString) import Data.Maybe (fromJust)-import Data.Typeable (Typeable)+import Data.Text (Text)+import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Foreign import Foreign.C.String import Foreign.C.Types- import qualified Sound.HTagLib.Type as T  #if !MIN_VERSION_base(4,8,0)@@ -71,49 +68,6 @@  newtype FileId = FileId (Ptr TagLibFile) --- | Types of files TagLib can work with. This may be used to explicitly--- specify type of file rather than relying on TagLib ability to guess type--- of file from its extension.--data FileType-  = MPEG-  | OggVorbis-  | FLAC-  | MPC-  | OggFlac-  | WavPack-  | Speex-  | TrueAudio-  | MP4-  | ASF-    deriving (Show, Eq, Enum)---- | Encoding for ID3v2 frames that are written to tags.--data ID3v2Encoding-  = ID3v2Latin1-  | ID3v2UTF16-  | ID3v2UTF16BE-  | ID3v2UTF8-    deriving (Show, Eq, Enum)---- | The data type represents exceptions specific to the library. The--- following constructors are defined:------     * @OpeningFailed@ means that attempt to open audio file to read its---     tags failed.---     * @InvalidFile@ means that file can be opened, but it doesn't contain---     any information that can be interpreted by the library.---     * @SavingFailed@ is thrown when well… saving failed.--data HTagLibException-  = OpeningFailed FilePath-  | InvalidFile   FilePath-  | SavingFailed  FilePath-    deriving (Eq, Show, Typeable)--instance Exception HTagLibException- -- Misc  foreign import ccall unsafe "taglib/tag_c.h taglib_set_string_management_enabled"@@ -213,19 +167,22 @@ -- | Open audio file and return its ID (abstraction that rest of library can -- pass around). In case of trouble 'IOException' is thrown. -newFile :: FilePath       -- ^ Path to audio file-        -> Maybe FileType -- ^ Type of file (or it will be guessed)-        -> IO FileId      -- ^ Id to pass around+newFile+  :: FilePath          -- ^ Path to audio file+  -> Maybe T.FileType  -- ^ Type of file (or it will be guessed)+  -> IO FileId         -- ^ Id to pass around newFile path ftype = do   c_taglib_set_string_management_enabled $ fromBool False   ptr <- withCString path $ \cstr ->-           case ftype of-             Nothing -> c_taglib_file_new cstr-             Just t  -> c_taglib_file_new_type cstr (enumToCInt t)-  when (ptr == nullPtr) $ throw (OpeningFailed path)+    case ftype of+      Nothing -> c_taglib_file_new cstr+      Just t  -> c_taglib_file_new_type cstr (enumToCInt t)+  when (ptr == nullPtr) $+    throw (T.OpeningFailed path)   valid <- toBool <$> c_taglib_file_is_valid ptr-  unless valid $ throw (InvalidFile path)-  return $ FileId ptr+  unless valid $+    throw (T.InvalidFile path)+  return (FileId ptr)  -- | Free file given its ID. Every time you open a file, free it. @@ -235,22 +192,25 @@ -- | Open audio file located at specified path, execute some actions given -- its 'FileId' and then free the file. -withFile :: FilePath         -- ^ Path to audio file-         -> Maybe FileType   -- ^ Type of file (or it will be guessed)-         -> (FileId -> IO a) -- ^ Computation depending of 'FileId'-         -> IO a             -- ^ Result value+withFile+  :: FilePath          -- ^ Path to audio file+  -> Maybe T.FileType  -- ^ Type of file (or it will be guessed)+  -> (FileId -> IO a)  -- ^ Computation depending of 'FileId'+  -> IO a              -- ^ Result value withFile path t = bracket (newFile path t) freeFile  -- | Save file given its ID. Given 'FilePath' just tells what to put into -- exception if the action fails, it doesn't specify where to save the file -- (it's determined by 'FileId'). -saveFile :: FilePath -- ^ File name to use in exceptions-         -> FileId   -- ^ File identifier-         -> IO ()+saveFile+  :: FilePath          -- ^ File name to use in exceptions+  -> FileId            -- ^ File identifier+  -> IO () saveFile path (FileId ptr) = do   success <- toBool <$> c_taglib_file_save ptr-  unless success $ throw (SavingFailed path)+  unless success $+    throw (T.SavingFailed path)  -- Tag API @@ -292,37 +252,37 @@ -- | Set title of track associated with file.  setTitle :: T.Title -> FileId -> IO ()-setTitle v = setStrValue c_taglib_tag_set_title (T.getTitle v)+setTitle v = setStrValue c_taglib_tag_set_title (T.unTitle v)  -- | Set artist of track associated with file.  setArtist :: T.Artist -> FileId -> IO ()-setArtist v = setStrValue c_taglib_tag_set_artist (T.getArtist v)+setArtist v = setStrValue c_taglib_tag_set_artist (T.unArtist v)  -- | Set album of track associated with file.  setAlbum :: T.Album -> FileId -> IO ()-setAlbum v = setStrValue c_taglib_tag_set_album (T.getAlbum v)+setAlbum v = setStrValue c_taglib_tag_set_album (T.unAlbum v)  -- | Set comment of track associated with file.  setComment :: T.Comment -> FileId -> IO ()-setComment v = setStrValue c_taglib_tag_set_comment (T.getComment v)+setComment v = setStrValue c_taglib_tag_set_comment (T.unComment v)  -- | Set genre of track associated with file.  setGenre :: T.Genre -> FileId -> IO ()-setGenre v = setStrValue c_taglib_tag_set_genre (T.getGenre v)+setGenre v = setStrValue c_taglib_tag_set_genre (T.unGenre v)  -- | Set year of track associated with file.  setYear :: Maybe T.Year -> FileId -> IO ()-setYear v = setIntValue c_taglib_tag_set_year (T.getYear <$> v)+setYear v = setIntValue c_taglib_tag_set_year (T.unYear <$> v)  -- | Set track number of track associated with file.  setTrackNumber :: Maybe T.TrackNumber -> FileId -> IO ()-setTrackNumber v = setIntValue c_taglib_tag_set_track (T.getTrackNumber <$> v)+setTrackNumber v = setIntValue c_taglib_tag_set_track (T.unTrackNumber <$> v)  -- Audio properties API @@ -330,85 +290,82 @@  getDuration :: FileId -> IO T.Duration getDuration = fmap (fromJust . T.mkDuration)-            . getIntProperty c_taglib_properties_length+  . getIntProperty c_taglib_properties_length  -- | Get bit rate of track associated with file.  getBitRate :: FileId -> IO T.BitRate getBitRate = fmap (fromJust . T.mkBitRate)-           . getIntProperty c_taglib_properties_bitrate+  . getIntProperty c_taglib_properties_bitrate  -- | Get sample rate of track associated with file.  getSampleRate :: FileId -> IO T.SampleRate getSampleRate = fmap (fromJust . T.mkSampleRate)-              . getIntProperty c_taglib_properties_samplerate+  . getIntProperty c_taglib_properties_samplerate  -- | Get number of channels in track associated with file.  getChannels :: FileId -> IO T.Channels getChannels = fmap (fromJust . T.mkChannels)-            . getIntProperty c_taglib_properties_channels+  . getIntProperty c_taglib_properties_channels  -- Special convenience ID3v2 functions  -- | Set the default encoding for ID3v2 frames that are written to tags. -id3v2SetEncoding :: ID3v2Encoding -> IO ()+id3v2SetEncoding :: T.ID3v2Encoding -> IO () id3v2SetEncoding = c_taglib_id3v2_set_default_text_encoding . enumToCInt  -- Helpers  getStrValue   :: (Ptr TagLibTag -> IO CString) -- ^ How to get string from the resource-  -> FileId                        -- ^ File ID-  -> IO String                     -- ^ String result+  -> FileId            -- ^ File ID+  -> IO Text           -- ^ String result getStrValue getStr (FileId ptr) = do   tag    <- c_taglib_file_tag ptr   cstr   <- getStr tag-  result <- peekCString cstr+  result <- packCString cstr   free cstr-  return result+  return (decodeUtf8 result) -getIntValue-  :: Integral a+getIntValue :: Integral a   => (Ptr TagLibTag -> IO a) -- ^ How to get value from the resource-  -> FileId                  -- ^ File ID-  -> IO Int                  -- ^ Result value+  -> FileId            -- ^ File ID+  -> IO Int            -- ^ Result value getIntValue getInt (FileId ptr) = do   tag  <- c_taglib_file_tag ptr   cint <- getInt tag-  return $ fromIntegral cint+  return (fromIntegral cint)  setStrValue   :: (Ptr TagLibTag -> CString -> IO ()) -- ^ Setting routine-  -> String                              -- ^ New string value-  -> FileId                              -- ^ File ID+  -> Text              -- ^ New string value+  -> FileId            -- ^ File ID   -> IO () setStrValue setStr str (FileId ptr) = do   tag <- c_taglib_file_tag ptr-  withCString str $ \cstr ->+  useAsCString (encodeUtf8 str) $ \cstr ->     setStr tag cstr -setIntValue-  :: Integral a+setIntValue :: Integral a   => (Ptr TagLibTag -> a -> IO ()) -- ^ Setting routine-  -> Maybe Int                     -- ^ New value-  -> FileId                        -- ^ File ID+  -> Maybe Int         -- ^ New value+  -> FileId            -- ^ File ID   -> IO () setIntValue setUInt int (FileId ptr) = do   tag <- c_taglib_file_tag ptr-  setUInt tag $ maybe 0 fromIntegral int+  setUInt tag (maybe 0 fromIntegral int) -getIntProperty-  :: Integral a+getIntProperty :: Integral a   => (Ptr TagLibProperties -> IO a) -- ^ How to get value from the resource-  -> FileId                         -- ^ File ID-  -> IO Int                         -- ^ Result+  -> FileId            -- ^ File ID+  -> IO Int            -- ^ Result getIntProperty getInt (FileId ptr) = do   properties <- c_taglib_file_properties ptr   value      <- getInt properties-  return $ fromIntegral value+  return (fromIntegral value)  -- | Convert Haskell enumeration to C enumeration (an integer). 
Sound/HTagLib/Setter.hs view
@@ -13,9 +13,11 @@ {-# LANGUAGE CPP #-}  module Sound.HTagLib.Setter-  ( TagSetter+  ( -- * High-level API+    TagSetter   , setTags   , setTags'+    -- * Built-in setters   , titleSetter   , artistSetter   , albumSetter@@ -25,6 +27,7 @@   , trackNumberSetter ) where +import Data.Foldable (forM_) import Sound.HTagLib.Type import qualified Sound.HTagLib.Internal as I @@ -40,41 +43,42 @@ instance Monoid TagSetter where   mempty  = TagSetter $ const (return ())   x `mappend` y = TagSetter $ \fid ->-                    do runSetter x fid-                       runSetter y fid+    do runSetter x fid+       runSetter y fid  -- | Set tags in specified file using given setter. -- -- In case of trouble 'I.HTagLibException' will be thrown. -setTags :: FilePath              -- ^ Path to audio file-        -> Maybe I.ID3v2Encoding -- ^ Encoding for ID3v2 frames-        -> TagSetter             -- ^ Setter-        -> IO ()+setTags+  :: FilePath          -- ^ Path to audio file+  -> Maybe ID3v2Encoding -- ^ Encoding for ID3v2 frames+  -> TagSetter         -- ^ Setter+  -> IO () setTags path enc = execSetter path enc Nothing  -- | Similar to 'setTags', but you can also specify type of audio file -- explicitly (otherwise it's guessed from file extension). -setTags' :: FilePath              -- ^ Path to audio file-         -> Maybe I.ID3v2Encoding -- ^ Encoding for ID3v2 frames-         -> I.FileType            -- ^ Type of audio file-         -> TagSetter             -- ^ Setter-         -> IO ()+setTags'+  :: FilePath          -- ^ Path to audio file+  -> Maybe ID3v2Encoding -- ^ Encoding for ID3v2 frames+  -> FileType          -- ^ Type of audio file+  -> TagSetter         -- ^ Setter+  -> IO () setTags' path enc t = execSetter path enc (Just t)  -- | The most general way to set meta data. 'setTags' and 'setTags'' are -- just wrappers around this function. -execSetter :: FilePath               -- ^ Path to audio file-           -> Maybe I.ID3v2Encoding  -- ^ Encoding for ID3v2 frames-           -> Maybe I.FileType       -- ^ Type of audio file (if known)-           -> TagSetter              -- ^ Setter-           -> IO ()+execSetter+  :: FilePath          -- ^ Path to audio file+  -> Maybe ID3v2Encoding -- ^ Encoding for ID3v2 frames+  -> Maybe FileType    -- ^ Type of audio file (if known)+  -> TagSetter         -- ^ Setter+  -> IO () execSetter path enc t s = I.withFile path t $ \fid -> do-  case enc of-    Nothing -> return ()-    Just e  -> I.id3v2SetEncoding e+  forM_ enc I.id3v2SetEncoding   runSetter s fid   I.saveFile path fid 
Sound/HTagLib/Type.hs view
@@ -9,125 +9,129 @@ -- -- Definitions of types used to represent various tags and audio properties. +{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings  #-}+ module Sound.HTagLib.Type   ( Title   , mkTitle-  , getTitle+  , unTitle   , Artist   , mkArtist-  , getArtist+  , unArtist   , Album   , mkAlbum-  , getAlbum+  , unAlbum   , Comment   , mkComment-  , getComment+  , unComment   , Genre   , mkGenre-  , getGenre+  , unGenre   , Year   , mkYear-  , getYear+  , unYear   , TrackNumber   , mkTrackNumber-  , getTrackNumber+  , unTrackNumber   , Duration   , mkDuration-  , getDuration+  , unDuration   , BitRate   , mkBitRate-  , getBitRate+  , unBitRate   , SampleRate   , mkSampleRate-  , getSampleRate+  , unSampleRate   , Channels   , mkChannels-  , getChannels )+  , unChannels+  , FileType (..)+  , ID3v2Encoding (..)+  , HTagLibException (..) ) where +import Control.Exception (Exception) import Data.String+import Data.Text (Text)+import Data.Typeable (Typeable)+import qualified Data.Text as T  -- | Title tag.  newtype Title = Title-  { -- | Convert 'Title' to 'String'.-    getTitle :: String }-  deriving (Show, Eq, Ord)+  { unTitle :: Text    -- ^ Convert 'Title' to 'Text'.+  } deriving (Show, Eq, Ord)  instance IsString Title where-  fromString = mkTitle+  fromString = mkTitle . fromString  -- | Construction of 'Title' type, null bytes are converted to spaces. -mkTitle :: String -> Title+mkTitle :: Text -> Title mkTitle = Title . avoidNulls  -- | Artist tag.  newtype Artist = Artist-  { -- | Convert 'Artist' to 'String'.-    getArtist :: String }-  deriving (Show, Eq, Ord)+  { unArtist :: Text   -- ^ Convert 'Artist' to 'Text'.+  } deriving (Show, Eq, Ord)  instance IsString Artist where-  fromString = mkArtist+  fromString = mkArtist . fromString  -- | Construction of 'Artist' type, null bytes are converted to spaces. -mkArtist :: String -> Artist+mkArtist :: Text -> Artist mkArtist = Artist . avoidNulls  -- | Album tag.  newtype Album = Album-  { -- | Convert 'Album' to 'String'.-    getAlbum :: String }-  deriving (Show, Eq, Ord)+  { unAlbum :: Text    -- ^ Convert 'Album' to 'Text'.+  } deriving (Show, Eq, Ord)  instance IsString Album where-  fromString = mkAlbum+  fromString = mkAlbum . fromString  -- | Construction of 'Album' type, null bytes are converted to spaces. -mkAlbum :: String -> Album+mkAlbum :: Text -> Album mkAlbum = Album . avoidNulls  -- | Comment tag.  newtype Comment = Comment-  { -- | Convert 'Comment' to 'String'.-    getComment :: String }-  deriving (Show, Eq, Ord)+  { unComment :: Text  -- ^ Convert 'Comment' to 'Text'.+  } deriving (Show, Eq, Ord)  instance IsString Comment where-  fromString = mkComment+  fromString = mkComment . fromString  -- | Construction of 'Comment' type, null bytes are converted to spaces. -mkComment :: String -> Comment+mkComment :: Text -> Comment mkComment = Comment . avoidNulls  -- | Genre tag.  newtype Genre = Genre-  { -- | Convert 'Genre' to 'String'.-    getGenre :: String }-  deriving (Show, Eq, Ord)+  { unGenre :: Text    -- ^ Convert 'Genre' to 'Text'.+  } deriving (Show, Eq, Ord)  instance IsString Genre where-  fromString = mkGenre+  fromString = mkGenre . fromString  -- | Construction of 'Genre' type, null bytes are converted to spaces. -mkGenre :: String -> Genre+mkGenre :: Text -> Genre mkGenre = Genre . avoidNulls  -- | Year tag.  newtype Year = Year-  { -- | Convert 'Year' to 'Int'.-    getYear :: Int }-  deriving (Show, Eq, Ord)+  { unYear :: Int      -- ^ Convert 'Year' to 'Int'.+  } deriving (Show, Eq, Ord)  -- | Construction of 'Year' type, non-positive values result in 'Nothing'. @@ -137,9 +141,8 @@ -- | Track number tag.  newtype TrackNumber = TrackNumber-  { -- | Convert 'TrackNumber' to 'Int'.-    getTrackNumber :: Int }-  deriving (Show, Eq, Ord)+  { unTrackNumber :: Int -- ^ Convert 'TrackNumber' to 'Int'.+  } deriving (Show, Eq, Ord)  -- | Construction of 'TrackNumber' type, non-positive values result in -- 'Nothing'.@@ -150,9 +153,8 @@ -- | Duration in seconds.  newtype Duration = Duration-  { -- | Convert 'Duration' to 'Int'.-    getDuration :: Int }-  deriving (Show, Eq, Ord)+  { unDuration :: Int  -- ^ Convert 'Duration' to 'Int'.+  } deriving (Show, Eq, Ord)  -- | Construction of 'Duration' values, negative values result in 'Nothing'. @@ -162,9 +164,8 @@ -- | Bit rate in kb/s.  newtype BitRate = BitRate-  { -- | Convert 'BitRate' to 'Int'.-    getBitRate :: Int }-  deriving (Show, Eq, Ord)+  { unBitRate :: Int   -- ^ Convert 'BitRate' to 'Int'.+  } deriving (Show, Eq, Ord)  -- | Construction of 'BitRate' values, negative values result in -- 'Nothing'.@@ -175,9 +176,8 @@ -- | Sample rate in Hz.  newtype SampleRate = SampleRate-  { -- | Convert 'SampleRate' to 'Int'.-    getSampleRate :: Int }-  deriving (Show, Eq, Ord)+  { unSampleRate :: Int -- ^ Convert 'SampleRate' to 'Int'.+  } deriving (Show, Eq, Ord)  -- | Construction of 'SampleRate' values, non-positive values result in -- 'Nothing'.@@ -188,9 +188,8 @@ -- | Number of channels in the audio stream.  newtype Channels = Channels-  { -- | Convert 'Channels' to 'Int'.-    getChannels :: Int }-  deriving (Show, Eq, Ord)+  { unChannels :: Int  -- ^ Convert 'Channels' to 'Int'.+  } deriving (Show, Eq, Ord)  -- | Construction of 'Channels' values, non-positive values result in -- 'Nothing'.@@ -200,11 +199,51 @@  -- | Replace null bytes with spaces. -avoidNulls :: String -> String-avoidNulls = let f x = if x == '\0' then ' ' else x in fmap f+avoidNulls :: Text -> Text+avoidNulls = T.replace "\0" " "  -- | @atLeast a b@ returns @Just b@ is @b@ is greater or equal to @a@, -- otherwise result is @Nothing@.  atLeast :: Int -> Int -> Maybe Int atLeast a b = if b >= a then Just b else Nothing++-- | Types of files TagLib can work with. This may be used to explicitly+-- specify type of file rather than relying on TagLib ability to guess type+-- of file from its extension.++data FileType+  = MPEG               -- ^ MPEG+  | OggVorbis          -- ^ Ogg vorbis+  | FLAC               -- ^ FLAC+  | MPC                -- ^ MPC+  | OggFlac            -- ^ Ogg FLAC+  | WavPack            -- ^ Wav pack+  | Speex              -- ^ Speex+  | TrueAudio          -- ^ True audio+  | MP4                -- ^ MP4+  | ASF                -- ^ ASF+    deriving (Show, Eq, Enum)++-- | Encoding for ID3v2 frames that are written to tags.++data ID3v2Encoding+  = ID3v2Latin1        -- ^ Latin1+  | ID3v2UTF16         -- ^ UTF-16+  | ID3v2UTF16BE       -- ^ UTF-16 big endian+  | ID3v2UTF8          -- ^ UTF-8+    deriving (Show, Eq, Enum)++-- | The data type represents exceptions specific to the library.++data HTagLibException+  = OpeningFailed FilePath+    -- ^ Attempt to open audio file to read its tags failed+  | InvalidFile FilePath+    -- ^ File can be opened, but it doesn't contain any information that can+    -- be interpreted by the library+  | SavingFailed FilePath+    -- ^ Saving failed+    deriving (Eq, Show, Typeable)++instance Exception HTagLibException
htaglib.cabal view
@@ -32,7 +32,7 @@ -- POSSIBILITY OF SUCH DAMAGE.  name:                htaglib-version:             0.1.1+version:             1.0.0 cabal-version:       >= 1.10 license:             BSD3 license-file:        LICENSE.md@@ -45,26 +45,40 @@ build-type:          Simple description:         Bindings to TagLib, audio meta-data library. extra-source-files:  CHANGELOG.md+                   , README.md                    , audio-samples/README.md                    , audio-samples/sample.flac                    , audio-samples/sample.mp3 +flag dev+  description:       Turn development settings.+  manual:            True+  default:           False+ library-  build-depends:     base >= 4.6 && < 5+  build-depends:     base       >= 4.6 && < 5+                   , bytestring >= 0.9+                   , text       >= 1.0   extra-libraries:   tag_c   exposed-modules:   Sound.HTagLib                    , Sound.HTagLib.Type                    , Sound.HTagLib.Getter                    , Sound.HTagLib.Setter-                   , Sound.HTagLib.Internal-  ghc-options:       -O2 -Wall+  other-modules:     Sound.HTagLib.Internal+  if flag(dev)+    ghc-options:     -O0 -Wall -Werror+  else+    ghc-options:     -O2 -Wall   default-language:  Haskell2010  test-suite tests   main-is:           Main.hs   hs-source-dirs:    tests   type:              exitcode-stdio-1.0-  ghc-options:       -O2 -Wall+  if flag(dev)+    ghc-options:     -O0 -Wall -Werror+  else+    ghc-options:     -O2 -Wall   other-modules:     Getter                    , Setter                    , Util
tests/Getter.hs view
@@ -40,7 +40,7 @@ import Util  #if !MIN_VERSION_base(4,8,0)-import Control.Applicative (Applicative, (<$>))+import Control.Applicative ((<$>)) #endif  tests :: Test
tests/Setter.hs view
@@ -31,6 +31,8 @@ -- ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -- POSSIBILITY OF SUCH DAMAGE. +{-# LANGUAGE OverloadedStrings #-}+ module Setter (tests) where  import Data.Monoid