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 +12/−0
- README.md +189/−0
- Sound/HTagLib.hs +11/−15
- Sound/HTagLib/Getter.hs +18/−14
- Sound/HTagLib/Internal.hs +58/−101
- Sound/HTagLib/Setter.hs +24/−20
- Sound/HTagLib/Type.hs +95/−56
- htaglib.cabal +19/−5
- tests/Getter.hs +1/−1
- tests/Setter.hs +2/−0
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++[](http://opensource.org/licenses/BSD-3-Clause)+[](https://hackage.haskell.org/package/htaglib)+[](http://stackage.org/nightly/package/htaglib)+[](https://travis-ci.org/mrkkrp/htaglib)+[](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