lame 0.2.0 → 0.2.1
raw patch · 9 files changed
+481/−482 lines, 9 filesdep ~basedep ~textdep ~transformers
Dependency ranges changed: base, text, transformers
Files
- CHANGELOG.md +5/−0
- Codec/Audio/LAME.hs +171/−166
- Codec/Audio/LAME/Internal.hs +161/−179
- LICENSE.md +1/−1
- README.md +11/−14
- cbits/helpers.c +2/−2
- cbits/helpers.h +2/−2
- lame.cabal +71/−65
- tests/Codec/Audio/LAMESpec.hs +57/−53
CHANGELOG.md view
@@ -1,3 +1,8 @@+## LAME 0.2.1++* Works with `text-2.0` and older.+* Dropped support for GHC 8.x.+ ## LAME 0.2.0 * Got rid of `data-default-class`. Instead of making `EncoderSettings` an
Codec/Audio/LAME.hs view
@@ -1,29 +1,29 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+ -- | -- Module : Codec.Audio.LAME--- Copyright : © 2017–2019 Mark Karpov+-- Copyright : © 2017–present Mark Karpov -- License : BSD 3 clause -- -- Maintainer : Mark Karpov <markkarpov92@gmail.com> -- Stability : experimental -- Portability : portable ----- The module provides an interface to LAME MP3 encoder. All you need to do--- to encode a WAVE (or RF64) file is to call 'encodeMp3', which see.--{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-+-- The module provides an interface to the LAME encoder. module Codec.Audio.LAME- ( encodeMp3- , EncoderSettings (..)- , defaultEncoderSettings- , Compression (..)- , VbrMode (..)- , MetadataPlacement (..)- , FilterSetup (..)- , I.LameException (..) )+ ( encodeMp3,+ EncoderSettings (..),+ defaultEncoderSettings,+ Compression (..),+ VbrMode (..),+ MetadataPlacement (..),+ FilterSetup (..),+ I.LameException (..),+ ) where +import qualified Codec.Audio.LAME.Internal as I import Codec.Audio.Wave import Control.Monad import Control.Monad.Catch@@ -31,26 +31,23 @@ import Data.ByteString (ByteString) import Data.Maybe (fromMaybe) import Data.Text (Text)+import qualified Data.Text as T import Data.Word import System.Directory import System.FilePath import System.IO-import qualified Codec.Audio.LAME.Internal as I-import qualified Data.Text as T -- | LAME encoder settings. -- -- Currently some noise-shaping and psycho-acoustic parameters are omitted, -- but can be added on request.- data EncoderSettings = EncoderSettings- { encoderCompression :: !Compression- -- ^ How to determine amount of compression to apply, see 'Compression'.+ { -- | How to determine amount of compression to apply, see 'Compression'. -- Default value: @'CompressionRatio' 11@ (corresponds to 128 kbps).- , encoderQuality :: !(Maybe Word)- -- ^ Select algorithm. This variable will effect quality by selecting- -- expensive or cheap algorithms. 0 gives the best quality (very slow). 9 is- -- very fast, but gives worst quality.+ encoderCompression :: !Compression,+ -- | Select the algorithm. This variable will affect the quality by+ -- selecting expensive or cheap algorithms. 0 gives the best quality+ -- (very slow). 9 is very fast, but gives the worst quality. -- -- * 2—near-best quality, not too slow. -- * 5—good quality, fast.@@ -58,107 +55,108 @@ -- -- Default value: 'Nothing', meaning that LAME will select the best -- value itself.- , encoderNoGap :: !(Maybe (Word32, Word32))- -- ^ Enable “no gap” encoding. The first value in the tuple specifies+ encoderQuality :: !(Maybe Word),+ -- | Enable “no gap” encoding. The first value in the tuple specifies -- current index and the second value specifies total number of tracks. -- Default value: 'Nothing'—disabled.- , encoderErrorProtection :: !Bool- -- ^ Whether to enable error protection. Error protection means that 2- -- bytes from each frame for CRC checksum. Default value: 'False'.- , encoderStrictISO :: !Bool- -- ^ Whether to enforce strict ISO compliance. Default value: 'False'.- , encoderSampleRate :: !(Maybe Word32)- -- ^ Sample rate of output file. Default value: 'Nothing', which means- -- use sample rate of input file.- , encoderScale :: !Float- -- ^ Scale input stream by multiplying samples by this value. Default+ encoderNoGap :: !(Maybe (Word32, Word32)),+ -- | Whether to enable the error protection. Error protection means that+ -- 2 bytes from each frame are used for CRC checksum. Default value:+ -- 'False'.+ encoderErrorProtection :: !Bool,+ -- | Whether to enforce strict ISO compliance. Default value: 'False'.+ encoderStrictISO :: !Bool,+ -- | Sample rate of the output file. Default value: 'Nothing', which+ -- means that the sample rate of the input file will be used.+ encoderSampleRate :: !(Maybe Word32),+ -- | Scale input stream by multiplying samples by this value. Default -- value: 1 (no scaling).- , encoderFreeFormat :: !Bool- -- ^ Use free format. Default value: 'False'.- , encoderCopyright :: !Bool- -- ^ Mark as copyright protected. Default value: 'False'.- , encoderOriginal :: !Bool- -- ^ Mark as original. Default value: 'True'.- , encoderWriteVbrTag :: !Bool- -- ^ Whether to write Xing VBR header frame. Default value: 'True'.- , encoderMetadataPlacement :: !MetadataPlacement- -- ^ Which container to use to write metadata. Default value:+ encoderScale :: !Float,+ -- | Use free format. Default value: 'False'.+ encoderFreeFormat :: !Bool,+ -- | Mark as copyright protected. Default value: 'False'.+ encoderCopyright :: !Bool,+ -- | Mark as original. Default value: 'True'.+ encoderOriginal :: !Bool,+ -- | Whether to write Xing VBR header frame. Default value: 'True'.+ encoderWriteVbrTag :: !Bool,+ -- | Which container to use to write metadata. Default value: -- 'Id3v2Only'.- , encoderTagTitle :: !(Maybe Text)- -- ^ Title tag to write. Default value: 'Nothing'.- , encoderTagArtist :: !(Maybe Text)- -- ^ Artist tag to write. Default value: 'Nothing'.- , encoderTagAlbum :: !(Maybe Text)- -- ^ Album tag to write. Default value: 'Nothing'.- , encoderTagYear :: !(Maybe Text)- -- ^ Year tag to write. Default value: 'Nothing'.- , encoderTagComment :: !(Maybe Text)- -- ^ Comment tag to write. Default value: 'Nothing'.- , encoderTagTrack :: !(Maybe (Word8, Maybe Word8))- -- ^ Track number (first in tuple) and (optionally) total number of- -- tracks (second in tuple). Default value: 'Nothing'.- , encoderTagGenre :: !(Maybe Text)- -- ^ Genre tag to write. Default value: 'Nothing'.- , encoderAlbumArt :: !(Maybe ByteString)- -- ^ Album art (a picture) to write (JPEG\/PNG\/GIF). Default value:+ encoderMetadataPlacement :: !MetadataPlacement,+ -- | Title tag to write. Default value: 'Nothing'.+ encoderTagTitle :: !(Maybe Text),+ -- | Artist tag to write. Default value: 'Nothing'.+ encoderTagArtist :: !(Maybe Text),+ -- | Album tag to write. Default value: 'Nothing'.+ encoderTagAlbum :: !(Maybe Text),+ -- | Year tag to write. Default value: 'Nothing'.+ encoderTagYear :: !(Maybe Text),+ -- | Comment tag to write. Default value: 'Nothing'.+ encoderTagComment :: !(Maybe Text),+ -- | Track number (the first component of the tuple) and (optionally)+ -- the total number of tracks (the second component of the tuple).+ -- Default value: 'Nothing'.+ encoderTagTrack :: !(Maybe (Word8, Maybe Word8)),+ -- | Genre tag to write. Default value: 'Nothing'.+ encoderTagGenre :: !(Maybe Text),+ -- | Album art (a picture) to write (JPEG\/PNG\/GIF). Default value: -- 'Nothing'.- , encoderLowpassFilter :: !FilterSetup- -- ^ Settings for low-pass filter. Default value: 'FilterAuto'.- , encoderHighpassFilter :: !FilterSetup- -- ^ Settings for high-pass filter. Default value: 'FilterAuto'.- } deriving (Show, Read, Eq, Ord)+ encoderAlbumArt :: !(Maybe ByteString),+ -- | Settings for the low-pass filter. Default value: 'FilterAuto'.+ encoderLowpassFilter :: !FilterSetup,+ -- | Settings for the high-pass filter. Default value: 'FilterAuto'.+ encoderHighpassFilter :: !FilterSetup+ }+ deriving (Show, Read, Eq, Ord) --- | Default value of 'EncoderSettings'.+-- | The default value of 'EncoderSettings'. -- -- @since 0.2.0- defaultEncoderSettings :: EncoderSettings-defaultEncoderSettings = EncoderSettings- { encoderScale = 1- , encoderSampleRate = Nothing- , encoderFreeFormat = False- , encoderNoGap = Nothing- , encoderCopyright = False- , encoderOriginal = True- , encoderErrorProtection = False- , encoderStrictISO = False- , encoderWriteVbrTag = True- , encoderQuality = Nothing- , encoderCompression = CompressionRatio 11- , encoderMetadataPlacement = Id3v2Only- , encoderTagTitle = Nothing- , encoderTagArtist = Nothing- , encoderTagAlbum = Nothing- , encoderTagYear = Nothing- , encoderTagComment = Nothing- , encoderTagTrack = Nothing- , encoderTagGenre = Nothing- , encoderAlbumArt = Nothing- , encoderLowpassFilter = FilterAuto- , encoderHighpassFilter = FilterAuto- }---- | The data type represents supported options for compression. You can--- specify either fixed bitrate, compression ratio, or use the VBR mode.+defaultEncoderSettings =+ EncoderSettings+ { encoderScale = 1,+ encoderSampleRate = Nothing,+ encoderFreeFormat = False,+ encoderNoGap = Nothing,+ encoderCopyright = False,+ encoderOriginal = True,+ encoderErrorProtection = False,+ encoderStrictISO = False,+ encoderWriteVbrTag = True,+ encoderQuality = Nothing,+ encoderCompression = CompressionRatio 11,+ encoderMetadataPlacement = Id3v2Only,+ encoderTagTitle = Nothing,+ encoderTagArtist = Nothing,+ encoderTagAlbum = Nothing,+ encoderTagYear = Nothing,+ encoderTagComment = Nothing,+ encoderTagTrack = Nothing,+ encoderTagGenre = Nothing,+ encoderAlbumArt = Nothing,+ encoderLowpassFilter = FilterAuto,+ encoderHighpassFilter = FilterAuto+ } +-- | The supported options for compression. You can specify either fixed+-- bitrate, compression ratio, or use the VBR mode. data Compression- = CompressionBitrate Word- -- ^ Specify compression by setting a fixed bitrate, e.g.+ = -- | Specify the compression by setting a fixed bitrate, e.g. -- @'CompressionBitrate' 320@.- | CompressionRatio Float- -- ^ Specify compression ratio.- | CompressionVBR VbrMode Word- -- ^ VBR. Here you can specify which mode to use and the second argument- -- is VBR quality from 0 (highest), to 9 (lowest). Good default is 4.+ CompressionBitrate Word+ | -- | Specify the compression ratio.+ CompressionRatio Float+ | -- | VBR. Here you can specify which mode to use and the second argument+ -- is VBR quality from 0 (highest), to 9 (lowest). A good default is 4.+ CompressionVBR VbrMode Word deriving (Show, Read, Eq, Ord) --- | Variable bitrate (VBR) modes with their associated parameters.-+-- | Variable bitrate (VBR) modes and their parameters. data VbrMode- = VbrRh- -- ^ VBR RH.- | VbrAbr (Maybe Word) (Maybe Word) (Maybe Word) Bool- -- ^ VBR ABR. Parameters of the data constructor in order:+ = -- | VBR RH.+ VbrRh+ | -- | VBR ABR. Parameters of the data constructor in order: -- -- * Minimal bitrate -- * Mean bitrate@@ -166,55 +164,62 @@ -- * “Hard minimal bitrate”, if 'True', then the specified minimal -- bitrate will be enforced in all cases (normally it's violated -- for silence).- | VbrMtrh- -- ^ VBR MTRH+ VbrAbr (Maybe Word) (Maybe Word) (Maybe Word) Bool+ | -- | VBR MTRH+ VbrMtrh deriving (Show, Read, Eq, Ord) -- | Which container to use to write tags\/metadata.- data MetadataPlacement- = Id3v1Only -- ^ Only write ID3v1 metadata- | Id3v2Only -- ^ Only write ID3v2 metadata- | Id3Both -- ^ Write ID3v1 and ID3v2+ = -- | Only write ID3v1 metadata+ Id3v1Only+ | -- | Only write ID3v2 metadata+ Id3v2Only+ | -- | Write ID3v1 and ID3v2+ Id3Both deriving (Show, Read, Eq, Ord) -- | Settings for a filter.- data FilterSetup- = FilterAuto -- ^ Filter settings are chosen automatically- | FilterDisabled -- ^ Filter is disabled- | FilterManual Word (Maybe Word)- -- ^ The first parameter is the filter's frequency (cut-off frequency)+ = -- | Filter settings are chosen automatically+ FilterAuto+ | -- | Filter is disabled+ FilterDisabled+ | -- | The first parameter is the filter's frequency (cut-off frequency) -- in Hz and the second parameter is width of transition band in Hz (if -- 'Nothing', it's chosen automatically).+ FilterManual Word (Maybe Word) deriving (Show, Read, Eq, Ord) --- | Encode a WAVE file or RF64 file to MP3.+-- | Encode a WAVE file or RF64 file. -- -- If the input file is not a valid WAVE file, 'WaveException' will be--- thrown. 'I.LameException' is thrown when underlying LAME encoder reports--- a problem.+-- thrown. 'I.LameException' is thrown when the LAME encoder reports a+-- problem. ----- Not all sample formats and bit depth are currently supported. Supported--- sample formats include:+-- Not all sample formats and bit depths are currently supported. The+-- supported sample formats include: -- -- * PCM with samples represented as signed integers > 8 bit and <= 16 -- bit per mono-sample. -- * IEEE floating point (32 bit) samples. -- * IEEE floating point (64 bit) samples. ----- If you feed the encoder with something else, expect--- 'I.LameUnsupportedSampleFormat' to be thrown.--encodeMp3 :: MonadIO m- => EncoderSettings -- ^ Encoder settings- -> FilePath -- ^ WAVE file to encode- -> FilePath -- ^ Where to save the resulting MP3 file- -> m ()+-- If you feed the encoder something else, 'I.LameUnsupportedSampleFormat'+-- will be thrown.+encodeMp3 ::+ MonadIO m =>+ -- | Encoder settings+ EncoderSettings ->+ -- | WAVE file to encode+ FilePath ->+ -- | Where to save the resulting MP3 file+ FilePath ->+ m () encodeMp3 EncoderSettings {..} ipath' opath' = liftIO . I.withLame $ \l -> do ipath <- makeAbsolute ipath' opath <- makeAbsolute opath'- wave@Wave {..} <- readWaveFile ipath+ wave@Wave {..} <- readWaveFile ipath case waveSampleFormat of SampleFormatPcmInt bps -> when (bps > 16 || bps <= 8) $@@ -229,7 +234,7 @@ I.setFreeFormat l encoderFreeFormat forM_ encoderNoGap $ \(currentIndex, total) -> do I.setNoGapCurrentIndex l (fromIntegral currentIndex)- I.setNoGapTotal l (fromIntegral total)+ I.setNoGapTotal l (fromIntegral total) I.setCopyright l encoderCopyright I.setOriginal l encoderOriginal I.setErrorProtection l encoderErrorProtection@@ -247,25 +252,25 @@ VbrRh -> I.setVBR l I.VbrRh VbrAbr minRate meanRate maxRate hardMin -> do I.setVBR l I.VbrAbr- forM_ minRate (I.setVBRMinBitrate l . fromIntegral)+ forM_ minRate (I.setVBRMinBitrate l . fromIntegral) forM_ meanRate (I.setVBRMeanBitrate l . fromIntegral)- forM_ maxRate (I.setVBRMaxBitrate l . fromIntegral)+ forM_ maxRate (I.setVBRMaxBitrate l . fromIntegral) I.setVBRHardMin l hardMin VbrMtrh -> I.setVBR l I.VbrMtrh I.id3TagInit l case encoderMetadataPlacement of Id3v1Only -> I.id3TagV1Only l Id3v2Only -> I.id3TagV2Only l- Id3Both -> I.id3TagAddV2 l- forM_ encoderTagTitle (I.id3TagSetTextInfo l "TIT2")- forM_ encoderTagArtist (I.id3TagSetTextInfo l "TPE1")- forM_ encoderTagAlbum (I.id3TagSetTextInfo l "TALB")- forM_ encoderTagYear (I.id3TagSetTextInfo l "TYER")+ Id3Both -> I.id3TagAddV2 l+ forM_ encoderTagTitle (I.id3TagSetTextInfo l "TIT2")+ forM_ encoderTagArtist (I.id3TagSetTextInfo l "TPE1")+ forM_ encoderTagAlbum (I.id3TagSetTextInfo l "TALB")+ forM_ encoderTagYear (I.id3TagSetTextInfo l "TYER") forM_ encoderTagComment (I.id3TagSetComment l)- forM_ encoderTagTrack (uncurry renderTrackNumber >=> I.id3TagSetTextInfo l "TRCK")- forM_ encoderTagGenre (I.id3TagSetTextInfo l "TCON")- forM_ encoderAlbumArt (I.id3TagSetAlbumArt l)- setupFilter I.setLowpassFreq I.setLowpassWidth l encoderLowpassFilter+ forM_ encoderTagTrack (uncurry renderTrackNumber >=> I.id3TagSetTextInfo l "TRCK")+ forM_ encoderTagGenre (I.id3TagSetTextInfo l "TCON")+ forM_ encoderAlbumArt (I.id3TagSetAlbumArt l)+ setupFilter I.setLowpassFreq I.setLowpassWidth l encoderLowpassFilter setupFilter I.setHighpassFreq I.setHighpassWidth l encoderHighpassFilter I.initParams l withTempFile' opath $ \otemp -> do@@ -275,14 +280,17 @@ ---------------------------------------------------------------------------- -- Helpers --- | Setup a filter for given 'I.Lame'.--setupFilter- :: (I.Lame -> Int -> IO ()) -- ^ How to set cut-off frequncy- -> (I.Lame -> Int -> IO ()) -- ^ How to set width of transition band- -> I.Lame -- ^ Settings to edit- -> FilterSetup -- ^ Filter setup to apply- -> IO ()+-- | Setup a filter for a given 'I.Lame'.+setupFilter ::+ -- | How to set cut-off frequncy+ (I.Lame -> Int -> IO ()) ->+ -- | How to set width of transition band+ (I.Lame -> Int -> IO ()) ->+ -- | Settings to edit+ I.Lame ->+ -- | Filter setup to apply+ FilterSetup ->+ IO () setupFilter setFreq _ l FilterAuto = setFreq l 0 setupFilter setFreq _ l FilterDisabled = setFreq l (-1) setupFilter setFreq setWidth l (FilterManual freq mwidth) = do@@ -292,31 +300,28 @@ -- | A custom wrapper for creating temporary files in the same directory as -- given file. 'Handle' is not opened, you only get 'FilePath' in the -- callback.- withTempFile' :: FilePath -> (FilePath -> IO a) -> IO a withTempFile' path = bracketOnError acquire cleanup where acquire = do- (path',h) <- openBinaryTempFile dir file+ (path', h) <- openBinaryTempFile dir file hClose h return path' cleanup = ignoringIOErrors . removeFile -- in case exception strikes- -- before we create the actual file- dir = takeDirectory path- file = takeFileName path---- | Perform specified action ignoring IO exceptions it may throw.+ -- before we create the actual file+ dir = takeDirectory path+ file = takeFileName path +-- | Perform an action ignoring IO exceptions it may throw. ignoringIOErrors :: IO () -> IO () ignoringIOErrors ioe = ioe `catch` handler where handler :: IOError -> IO () handler = const (return ()) --- | Render track number and optionally total number of tracks as a strict--- 'Text' value.-+-- | Render the track number and optionally the total number of tracks as a+-- strict 'Text' value. renderTrackNumber :: Word8 -> Maybe Word8 -> IO Text-renderTrackNumber 0 t = throwM (I.LameInvalidTrackNumber 0 t)+renderTrackNumber 0 t = throwM (I.LameInvalidTrackNumber 0 t) renderTrackNumber n t@(Just 0) = throwM (I.LameInvalidTrackNumber n t) renderTrackNumber n t = return . T.pack $ show n ++ maybe "" (("/" ++) . show) t
Codec/Audio/LAME/Internal.hs view
@@ -1,6 +1,10 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE RecordWildCards #-}+ -- | -- Module : Codec.Audio.LAME.Internal--- Copyright : © 2017–2019 Mark Karpov+-- Copyright : © 2017–present Mark Karpov -- License : BSD 3 clause -- -- Maintainer : Mark Karpov <markkarpov92@gmail.com>@@ -8,104 +12,114 @@ -- Portability : portable -- -- Low-level non-public helpers.--{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE RecordWildCards #-}- module Codec.Audio.LAME.Internal ( -- * Types- Lame- , VbrMode (..)- , LameException (..)+ Lame,+ VbrMode (..),+ LameException (..),+ -- * Low-level API- , withLame- , initParams+ withLame,+ initParams,+ -- ** Input stream description- , setNumSamples- , setInputSampleRate- , setNumChannels- , setScale- , setOutputSampleRate+ setNumSamples,+ setInputSampleRate,+ setNumChannels,+ setScale,+ setOutputSampleRate,+ -- ** General control parameters- , setWriteVbrTag- , setQuality- , setFreeFormat- , setFindReplayGain- , setNoGapTotal- , setNoGapCurrentIndex- , setBitrate- , setCompressionRatio+ setWriteVbrTag,+ setQuality,+ setFreeFormat,+ setFindReplayGain,+ setNoGapTotal,+ setNoGapCurrentIndex,+ setBitrate,+ setCompressionRatio,+ -- ** Frame parameters- , setCopyright- , setOriginal- , setErrorProtection- , setStrictISO+ setCopyright,+ setOriginal,+ setErrorProtection,+ setStrictISO,+ -- ** VBR control- , setVBR- , setVBRQ- , setVBRMinBitrate- , setVBRMeanBitrate- , setVBRMaxBitrate- , setVBRHardMin+ setVBR,+ setVBRQ,+ setVBRMinBitrate,+ setVBRMeanBitrate,+ setVBRMaxBitrate,+ setVBRHardMin,+ -- ** Filtering control- , setLowpassFreq- , setLowpassWidth- , setHighpassFreq- , setHighpassWidth+ setLowpassFreq,+ setLowpassWidth,+ setHighpassFreq,+ setHighpassWidth,+ -- * Tags- , id3TagInit- , id3TagAddV2- , id3TagV1Only- , id3TagV2Only- , id3TagSetTextInfo- , id3TagSetComment- , id3TagSetAlbumArt+ id3TagInit,+ id3TagAddV2,+ id3TagV1Only,+ id3TagV2Only,+ id3TagSetTextInfo,+ id3TagSetComment,+ id3TagSetAlbumArt,+ -- * Encoding- , encodingHelper )+ encodingHelper,+ ) where import Codec.Audio.Wave import Control.Monad.Catch import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Unsafe as B import Data.Coerce import Data.Text (Text)+import qualified Data.Text.Encoding as TE import Data.Void import Foreign hiding (void) import Foreign.C.String import Foreign.C.Types (CSize (..))-import qualified Data.ByteString.Unsafe as B-import qualified Data.Text.Foreign as TF ---------------------------------------------------------------------------- -- Types --- | An opaque newtype wrapper around @'Ptr' 'Void'@, serves to represent+-- | An opaque newtype wrapper around @'Ptr' 'Void'@ that represents the -- pointer to the structure that does all the bookkeeping in LAME.- newtype Lame = Lame (Ptr Void) -- | Enumeration of VBR modes.- data VbrMode- = VbrRh -- ^ VBR RH- | VbrAbr -- ^ VBR ABR- | VbrMtrh -- ^ VBR MTRH+ = -- | VBR RH+ VbrRh+ | -- | VBR ABR+ VbrAbr+ | -- | VBR MTRH+ VbrMtrh deriving (Show, Read, Eq, Ord, Bounded, Enum) --- | Enumeration of problems you can have with LAME.-+-- | Enumeration of problems you may have with LAME. data LameException- = LameGenericError -- ^ A “generic error” happened- | LameNoMemory -- ^ Memory allocation issue- | LameBadBitrate -- ^ Unsupported bitrate- | LameBadSampleFreq -- ^ Unsupported sample rate- | LameInternalError -- ^ An “Internal error” happened- | LameInvalidTrackNumber Word8 (Maybe Word8)- -- ^ Invalid track number (first argument) or total number of tracks+ = -- | A “generic error” happened+ LameGenericError+ | -- | Memory allocation issue+ LameNoMemory+ | -- | Unsupported bitrate+ LameBadBitrate+ | -- | Unsupported sample rate+ LameBadSampleFreq+ | -- | An “Internal error” happened+ LameInternalError+ | -- | Invalid track number (first argument) or total number of tracks -- (second argument) was supplied- | LameUnsupportedSampleFormat SampleFormat- -- ^ This sample format is not supported at this time+ LameInvalidTrackNumber Word8 (Maybe Word8)+ | -- | This sample format is not supported at this time+ LameUnsupportedSampleFormat SampleFormat deriving (Eq, Show, Read) instance Exception LameException@@ -116,17 +130,15 @@ -- | Create and use a 'Lame' (pointer structure needed for talking to the -- LAME API). ----- If memory cannot be allocated, corresponding 'LameException' is raised.-+-- If memory cannot be allocated, 'LameNoMemory' is thrown. withLame :: (Lame -> IO a) -> IO a withLame f = bracket lameInit (mapM_ lameClose) $ \mlame -> case mlame of Nothing -> throwM LameNoMemory Just x -> f x --- | Create a new 'Lame'. In the case of memory allocation problem 'Nothing'--- is returned.-+-- | Create a new 'Lame'. In the case of a memory allocation problem+-- 'Nothing' is returned. lameInit :: IO (Maybe Lame) lameInit = maybePtr <$> c_lame_init @@ -134,7 +146,6 @@ c_lame_init :: IO Lame -- | Free 'Lame' structure and all associated buffers.- lameClose :: Lame -> IO () lameClose = c_lame_close @@ -142,8 +153,7 @@ c_lame_close :: Lame -> IO () -- | Set more internal configuration based on previously set parameters.--- Should be called when all the other stuff is set.-+-- Should be called when everything else is set. initParams :: Lame -> IO () initParams = handleErrors . c_lame_init_params @@ -154,23 +164,20 @@ -- Input stream description -- | Set the total number of samples to encode.- setNumSamples :: Lame -> Word64 -> IO () setNumSamples l x = handleErrors (c_lame_set_num_samples l x) foreign import ccall unsafe "lame_set_num_samples" c_lame_set_num_samples :: Lame -> Word64 -> IO Int --- | Set sample rate of the input stream.-+-- | Set the sample rate of the input stream. setInputSampleRate :: Lame -> Int -> IO () setInputSampleRate l x = handleErrors (c_lame_set_in_samplerate l x) foreign import ccall unsafe "lame_set_in_samplerate" c_lame_set_in_samplerate :: Lame -> Int -> IO Int --- | Set number of channels in input stream.-+-- | Set the number of channels in the input stream. setNumChannels :: Lame -> Int -> IO () setNumChannels l x = handleErrors (c_lame_set_num_channels l x) @@ -178,16 +185,14 @@ c_lame_set_num_channels :: Lame -> Int -> IO Int -- | Scale the input by this amount before encoding.- setScale :: Lame -> Float -> IO () setScale l x = handleErrors (c_lame_set_scale l x) foreign import ccall unsafe "lame_set_scale" c_lame_set_scale :: Lame -> Float -> IO Int --- | Set output sample rate in Hz. 0 (default) means that LAME will pick+-- | Set the output sample rate in Hz. 0 (default) means that LAME will pick -- this value automatically.- setOutputSampleRate :: Lame -> Int -> IO () setOutputSampleRate l x = handleErrors (c_lame_set_out_samplerate l x) @@ -198,21 +203,19 @@ -- General control parameters -- | Set whether to write Xing VBR header frame.- setWriteVbrTag :: Lame -> Bool -> IO () setWriteVbrTag l x = handleErrors (c_lame_set_bWriteVbrTag l (fromBool x)) foreign import ccall unsafe "lame_set_bWriteVbrTag" c_lame_set_bWriteVbrTag :: Lame -> Int -> IO Int --- | Select algorithm. This variable will effect quality by selecting--- expensive or cheap algorithms. 0 gives the best quality (very slow). 9 is--- very fast, but gives worst quality.+-- | Select the algorithm. This variable will affect the quality by+-- selecting expensive or cheap algorithms. 0 gives the best quality (very+-- slow). 9 is very fast, but gives the worst quality. -- -- * 2—near-best quality, not too slow. -- * 5—good quality, fast. -- * 7—OK quality, really fast.- setQuality :: Lame -> Int -> IO () setQuality l x = handleErrors (c_lame_set_quality l x) @@ -220,7 +223,6 @@ c_lame_set_quality :: Lame -> Int -> IO Int -- | Set whether we should use free format.- setFreeFormat :: Lame -> Bool -> IO () setFreeFormat l x = handleErrors (c_lame_set_free_format l (fromBool x)) @@ -228,39 +230,34 @@ c_lame_set_free_format :: Lame -> Int -> IO Int -- | Set whether we should do ReplayGain analysis.- setFindReplayGain :: Lame -> Bool -> IO () setFindReplayGain l x = handleErrors (c_lame_set_findReplayGain l (fromBool x)) foreign import ccall unsafe "lame_set_findReplayGain" c_lame_set_findReplayGain :: Lame -> Int -> IO Int --- | Set total number of tracks to encode in “no gap” mode.-+-- | Set the total number of tracks to encode in “no gap” mode. setNoGapTotal :: Lame -> Int -> IO () setNoGapTotal l x = handleErrors (c_lame_set_nogap_total l x) foreign import ccall unsafe "lame_set_nogap_total" c_lame_set_nogap_total :: Lame -> Int -> IO Int --- | Set index of current track to encode in “no gap” mode.-+-- | Set the index of the current track to encode in “no gap” mode. setNoGapCurrentIndex :: Lame -> Int -> IO () setNoGapCurrentIndex l x = handleErrors (c_lame_set_nogap_currentindex l x) foreign import ccall unsafe "lame_set_nogap_currentindex" c_lame_set_nogap_currentindex :: Lame -> Int -> IO Int --- | Set bitrate.-+-- | Set the bitrate. setBitrate :: Lame -> Int -> IO () setBitrate l x = handleErrors (c_lame_set_brate l x) foreign import ccall unsafe "lame_set_brate" c_lame_set_brate :: Lame -> Int -> IO Int --- | Set compression ratio.-+-- | Set the compression ratio. setCompressionRatio :: Lame -> Float -> IO () setCompressionRatio l x = handleErrors (c_lame_set_compression_ratio l x) @@ -271,7 +268,6 @@ -- Frame parameters -- | Mark as copyright protected.- setCopyright :: Lame -> Bool -> IO () setCopyright l x = handleErrors (c_lame_set_copyright l (fromBool x)) @@ -279,7 +275,6 @@ c_lame_set_copyright :: Lame -> Int -> IO Int -- | Mark as original.- setOriginal :: Lame -> Bool -> IO () setOriginal l x = handleErrors (c_lame_set_original l (fromBool x)) @@ -287,7 +282,6 @@ c_lame_set_original :: Lame -> Int -> IO Int -- | Set whether to use 2 bytes from each frame for CRC checksum.- setErrorProtection :: Lame -> Bool -> IO () setErrorProtection l x = handleErrors (c_lame_set_error_protection l (fromBool x))@@ -296,7 +290,6 @@ c_lame_set_error_protection :: Lame -> Int -> IO Int -- | Enforce strict ISO compliance.- setStrictISO :: Lame -> Bool -> IO () setStrictISO l x = handleErrors (c_lame_set_strict_ISO l (fromBool x)) @@ -307,44 +300,39 @@ -- VBR control -- | Set type of VBR.- setVBR :: Lame -> VbrMode -> IO () setVBR l x' = handleErrors (c_lame_set_VBR l x) where x = case x' of- VbrRh -> 2- VbrAbr -> 3- VbrMtrh -> 4+ VbrRh -> 2+ VbrAbr -> 3+ VbrMtrh -> 4 foreign import ccall unsafe "lame_set_VBR" c_lame_set_VBR :: Lame -> Int -> IO Int -- | Set VBR quality level, 0 is highest, 9 is lowest.- setVBRQ :: Lame -> Int -> IO () setVBRQ l x = handleErrors (c_lame_set_VBR_q l x) foreign import ccall unsafe "lame_set_VBR_q" c_lame_set_VBR_q :: Lame -> Int -> IO Int --- | Only for VBR ABR: set min bitrate in kbps.-+-- | Only for VBR ABR: set the min bitrate in kbps. setVBRMinBitrate :: Lame -> Int -> IO () setVBRMinBitrate l x = handleErrors (c_lame_set_VBR_min_bitrate_kbps l x) foreign import ccall unsafe "lame_set_VBR_min_bitrate_kbps" c_lame_set_VBR_min_bitrate_kbps :: Lame -> Int -> IO Int --- | Only for VBR ABR: set mean bitrate in kbps.-+-- | Only for VBR ABR: set the mean bitrate in kbps. setVBRMeanBitrate :: Lame -> Int -> IO () setVBRMeanBitrate l x = handleErrors (c_lame_set_VBR_mean_bitrate_kbps l x) foreign import ccall unsafe "lame_set_VBR_mean_bitrate_kbps" c_lame_set_VBR_mean_bitrate_kbps :: Lame -> Int -> IO Int --- | Only for VBR ABR: set max bitrate in kbps.-+-- | Only for VBR ABR: set the max bitrate in kbps. setVBRMaxBitrate :: Lame -> Int -> IO () setVBRMaxBitrate l x = handleErrors (c_lame_set_VBR_max_bitrate_kbps l x) @@ -353,7 +341,6 @@ -- | Set whether to strictly enforce VBR min bitrate. Normally it will be -- violated for analog silence.- setVBRHardMin :: Lame -> Bool -> IO () setVBRHardMin l x = handleErrors (c_lame_set_VBR_hard_min l (fromBool x)) @@ -363,9 +350,8 @@ ---------------------------------------------------------------------------- -- Filtering control --- | Set frequency to put low-pass filter on. Default is 0 (LAME chooses),--- -1 will disable the filter altogether.-+-- | Set the frequency to put the low-pass filter on. Default is 0 (LAME+-- chooses), -1 will disable the filter altogether. setLowpassFreq :: Lame -> Int -> IO () setLowpassFreq l x = handleErrors (c_lame_set_lowpassfreq l x) @@ -374,16 +360,14 @@ -- | Set width of low-pass transition band. Default is one polyphase filter -- band.- setLowpassWidth :: Lame -> Int -> IO () setLowpassWidth l x = handleErrors (c_lame_set_lowpasswidth l x) foreign import ccall unsafe "lame_set_lowpasswidth" c_lame_set_lowpasswidth :: Lame -> Int -> IO Int --- | Set frequency to put high-pass filter on. Default is 0 (LAME chooses),--- -1 will disable the filter altogether.-+-- | Set the frequency to put the high-pass filter on. Default is 0 (LAME+-- chooses), -1 will disable the filter altogether. setHighpassFreq :: Lame -> Int -> IO () setHighpassFreq l x = handleErrors (c_lame_set_highpassfreq l x) @@ -392,7 +376,6 @@ -- | Set width of high-pass transition band. Default is one polyphase filter -- band.- setHighpassWidth :: Lame -> Int -> IO () setHighpassWidth l x = handleErrors (c_lame_set_highpasswidth l x) @@ -402,17 +385,15 @@ ---------------------------------------------------------------------------- -- Tags --- | Initialize something about tag editing library. The docs are silent--- what this does, but I guess we'll take it into the business.-+-- | Initialize something about the tag editing library. The docs are silent+-- what this does. id3TagInit :: Lame -> IO () id3TagInit = c_id3tag_init foreign import ccall unsafe "id3tag_init" c_id3tag_init :: Lame -> IO () --- | Force addition of version 2 tag.-+-- | Force addition of a version 2 tag. id3TagAddV2 :: Lame -> IO () id3TagAddV2 = c_id3tag_add_v2 @@ -420,7 +401,6 @@ c_id3tag_add_v2 :: Lame -> IO () -- | Add only a version 1 tag.- id3TagV1Only :: Lame -> IO () id3TagV1Only = c_id3tag_v1_only @@ -428,7 +408,6 @@ c_id3tag_v1_only :: Lame -> IO () -- | Add only a version 2 tag.- id3TagV2Only :: Lame -> IO () id3TagV2Only = c_id3tag_v2_only @@ -436,31 +415,32 @@ c_id3tag_v2_only :: Lame -> IO () -- | Set a textual tag identifying it by its ID.- id3TagSetTextInfo :: Lame -> String -> Text -> IO ()-id3TagSetTextInfo l id' text = handleErrors $- withCString id' $ \idPtr ->- TF.useAsPtr text $ \textPtr len ->- c_id3tag_set_textinfo_utf16 l idPtr textPtr (fromIntegral len)+id3TagSetTextInfo l id' text =+ handleErrors . withCString id' $ \idPtr ->+ let utf16encodedBs = TE.encodeUtf16LE text+ len = fromIntegral (B.length utf16encodedBs `div` 2)+ in B.unsafeUseAsCString utf16encodedBs $ \textPtr ->+ c_id3tag_set_textinfo_utf16 l idPtr (castPtr textPtr) len -foreign import ccall unsafe "id3tag_set_textinfo_utf16_"+foreign import ccall unsafe "id3tag_set_textinfo_utf16_wrapped" c_id3tag_set_textinfo_utf16 :: Lame -> CString -> Ptr Word16 -> Int -> IO Int -- | Set the comment tag.- id3TagSetComment :: Lame -> Text -> IO ()-id3TagSetComment l text = handleErrors $- TF.useAsPtr text $ \textPtr len ->- c_id3tag_set_comment_utf16 l textPtr (fromIntegral len)+id3TagSetComment l text =+ let utf16encodedBs = TE.encodeUtf16LE text+ len = fromIntegral (B.length utf16encodedBs `div` 2)+ in handleErrors . B.unsafeUseAsCString utf16encodedBs $ \textPtr ->+ c_id3tag_set_comment_utf16 l (castPtr textPtr) len -foreign import ccall unsafe "id3tag_set_comment_utf16_"+foreign import ccall unsafe "id3tag_set_comment_utf16_wrapped" c_id3tag_set_comment_utf16 :: Lame -> Ptr Word16 -> Int -> IO Int --- | Set album art.-+-- | Set the album art. id3TagSetAlbumArt :: Lame -> ByteString -> IO ()-id3TagSetAlbumArt l img = handleErrors $- B.unsafeUseAsCStringLen img $ \(dataPtr, dataLen) ->+id3TagSetAlbumArt l img =+ handleErrors . B.unsafeUseAsCStringLen img $ \(dataPtr, dataLen) -> c_id3tag_set_albumart l dataPtr (fromIntegral dataLen) foreign import ccall unsafe "id3tag_set_albumart"@@ -469,62 +449,64 @@ ---------------------------------------------------------------------------- -- Encoding --- | Encode given input file.--encodingHelper- :: Lame -- ^ The settings- -> Wave -- ^ Information about input WAVE file- -> FilePath -- ^ Location of input file (normalized)- -> FilePath -- ^ Location of output file (normalized)- -> IO ()-encodingHelper l wave@Wave {..} ipath opath = handleErrors $- withCString ipath $ \ipathPtr ->+-- | Encode a given input file.+encodingHelper ::+ -- | The settings+ Lame ->+ -- | Information about input WAVE file+ Wave ->+ -- | Location of input file (normalized)+ FilePath ->+ -- | Location of output file (normalized)+ FilePath ->+ IO ()+encodingHelper l wave@Wave {..} ipath opath =+ handleErrors . withCString ipath $ \ipathPtr -> withCString opath $ \opathPtr -> c_lame_encoding_helper- l -- lame settings structure- (fromIntegral waveDataOffset) -- offset of data chunk- waveDataSize -- size of data chunk- (case waveSampleFormat of- SampleFormatPcmInt _ -> 0- SampleFormatIeeeFloat32Bit -> 1- SampleFormatIeeeFloat64Bit -> 2)- (waveBitsPerSample wave) -- bits per sample- ipathPtr -- path to input file- opathPtr -- path to output file+ l -- lame settings structure+ (fromIntegral waveDataOffset) -- offset of the data chunk+ waveDataSize -- the size of the data chunk+ ( case waveSampleFormat of+ SampleFormatPcmInt _ -> 0+ SampleFormatIeeeFloat32Bit -> 1+ SampleFormatIeeeFloat64Bit -> 2+ )+ (waveBitsPerSample wave) -- bits per sample+ ipathPtr -- path to input file+ opathPtr -- path to output file foreign import ccall unsafe "lame_encoding_helper"- c_lame_encoding_helper- :: Lame -- lame settings structure- -> Word64 -- offset of data chunk- -> Word64 -- size of data chunk- -> Word16 -- code of sample format- -> Word16 -- bits per sample- -> CString -- path to input file- -> CString -- path to output file- -> IO Int+ c_lame_encoding_helper ::+ Lame -> -- lame settings structure+ Word64 -> -- offset of data chunk+ Word64 -> -- size of data chunk+ Word16 -> -- code of sample format+ Word16 -> -- bits per sample+ CString -> -- path to input file+ CString -> -- path to output file+ IO Int ---------------------------------------------------------------------------- -- Helpers -- | Coerce to 'Ptr' and check if it's a null pointer, return 'Nothing' if--- it is, otherwise return the given pointer unchanged. Needless to say that--- this thing is unsafe.-+-- it is, otherwise return the given pointer unchanged. Needless to say,+-- this is unsafe. maybePtr :: Coercible a (Ptr p) => a -> Maybe a maybePtr a | coerce a == nullPtr = Nothing- | otherwise = Just a+ | otherwise = Just a --- | Treat the 'Int' value as a error code. Unless it's 0, throw+-- | Treat an 'Int' value as an error code. Unless it's 0, throw a -- corresponding 'LameException', otherwise just return the unit.- handleErrors :: IO Int -> IO () handleErrors m = do n <- m case n of- 0 -> return ()+ 0 -> return () -10 -> throwM LameNoMemory -11 -> throwM LameBadBitrate -12 -> throwM LameBadSampleFreq -13 -> throwM LameInternalError- _ -> throwM LameGenericError+ _ -> throwM LameGenericError
LICENSE.md view
@@ -1,4 +1,4 @@-Copyright © 2017–2018 Mark Karpov+Copyright © 2017–present Mark Karpov All rights reserved.
README.md view
@@ -4,39 +4,36 @@ [](https://hackage.haskell.org/package/lame) [](http://stackage.org/nightly/package/lame) [](http://stackage.org/lts/package/lame)-[](https://travis-ci.org/mrkkrp/lame)+ -This is a fairly complete high-level Haskell binding to-[LAME](http://lame.sourceforge.net/).+This is a high-level Haskell binding to the+[LAME](http://lame.sourceforge.net/) encoder. ## Provided functionality -* Fast MP3 encoder working in various modes (you specify compression ratio,- desired bit-rate, or parameters for VBR), with quite a few interesting- options to tweak.+* Fast MP3 encoder working in different modes. * Setting of all common tags, including pictures. ## Limitations -* No decoding for now. It's actually done via a separate library in LAME.+* No decoding. It is done with a separate library in LAME. * Relatively limited (compared to [ID3 specs](http://id3.org/id3v2.3.0#Text_information_frames)) number of tag fields available for setting. * Some sample widths are not supported: less than or equal to 8 bit and- greater than 16 bits (for integer samples, floats work OK).+ greater than 16 bits (for integer samples, floats work fine). * Some psycho-acoustic and noise-shaping settings are not available for tweaking. -Open an issue if something of this is a deal breaker for you, some of these-limitations are easily lifted.- ## Contribution -Please kindly direct all issues, bugs, and questions to [the GitHub issue-tracker for this project](https://github.com/mrkkrp/lame/issues).+Please direct all issues, bugs, and questions to [the GitHub issue tracker+for this project](https://github.com/mrkkrp/lame/issues). +Pull requests are also welcome.+ ## License -Copyright © 2017–2019 Mark Karpov+Copyright © 2017–present Mark Karpov Distributed under BSD 3 clause license.
cbits/helpers.c view
@@ -1,6 +1,6 @@ #include "helpers.h" -int id3tag_set_textinfo_utf16_+int id3tag_set_textinfo_utf16_wrapped ( lame_global_flags *gfp , const char *id , const uint16_t *raw_text@@ -17,7 +17,7 @@ return id3tag_set_textinfo_utf16(gfp, id, text); } -int id3tag_set_comment_utf16_+int id3tag_set_comment_utf16_wrapped ( lame_global_flags *gfp , const uint16_t *raw_text , int len )
cbits/helpers.h view
@@ -9,8 +9,8 @@ #include <stdint.h> #include <stdlib.h> -int id3tag_set_textinfo_utf16_(lame_global_flags *, const char *, const uint16_t *, int);-int id3tag_set_comment_utf16_(lame_global_flags *, const uint16_t *, int);+int id3tag_set_textinfo_utf16_wrapped(lame_global_flags *, const char *, const uint16_t *, int);+int id3tag_set_comment_utf16_wrapped(lame_global_flags *, const uint16_t *, int); int lame_encoding_helper(lame_global_flags *, uint64_t, uint64_t, uint16_t, uint16_t, const char *, const char *); #endif /* LAME_HASKELL_HELPERS_H */
lame.cabal view
@@ -1,73 +1,79 @@-name: lame-version: 0.2.0-cabal-version: 1.18-tested-with: GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.4, GHC==8.6.3-license: BSD3-license-file: LICENSE.md-author: Mark Karpov <markkarpov92@gmail.com>-maintainer: Mark Karpov <markkarpov92@gmail.com>-homepage: https://github.com/mrkkrp/lame-bug-reports: https://github.com/mrkkrp/lame/issues-category: Codec, Audio-synopsis: Fairly complete high-level binding to LAME encoder-build-type: Simple-description: Fairly complete high-level binding to LAME encoder.-extra-source-files: cbits/*.h-extra-doc-files: CHANGELOG.md- , README.md-data-files: audio-samples/*.wav+cabal-version: 2.4+name: lame+version: 0.2.1+license: BSD-3-Clause+license-file: LICENSE.md+maintainer: Mark Karpov <markkarpov92@gmail.com>+author: Mark Karpov <markkarpov92@gmail.com>+tested-with: ghc ==9.0.2 ghc ==9.2.4 ghc ==9.4.1+homepage: https://github.com/mrkkrp/lame+bug-reports: https://github.com/mrkkrp/lame/issues+synopsis: A high-level binding to the LAME encoder+description: A high-level binding to the LAME encoder.+category: Codec, Audio+build-type: Simple+data-files: audio-samples/*.wav+extra-source-files: cbits/*.h+extra-doc-files:+ CHANGELOG.md+ README.md source-repository head- type: git- location: https://github.com/mrkkrp/lame.git+ type: git+ location: https://github.com/mrkkrp/lame.git flag dev- description: Turn on development settings.- manual: True- default: False+ description: Turn on development settings.+ default: False+ manual: True library- build-depends: base >= 4.8 && < 5.0- , bytestring >= 0.2 && < 0.11- , directory >= 1.2.2 && < 1.4- , exceptions >= 0.6 && < 0.11- , filepath >= 1.2 && < 1.5- , text >= 0.2 && < 1.3- , transformers >= 0.4 && < 0.6- , wave >= 0.1.2 && < 0.3- extra-libraries: mp3lame- exposed-modules: Codec.Audio.LAME- other-modules: Codec.Audio.LAME.Internal- c-sources: cbits/helpers.c- include-dirs: cbits- if flag(dev)- ghc-options: -Wall -Werror- else- ghc-options: -O2 -Wall- if flag(dev) && impl(ghc >= 8.0)- ghc-options: -Wcompat- -Wincomplete-record-updates- -Wincomplete-uni-patterns- -Wnoncanonical-monad-instances- -Wnoncanonical-monadfail-instances- default-language: Haskell2010+ exposed-modules: Codec.Audio.LAME+ c-sources: cbits/helpers.c+ other-modules: Codec.Audio.LAME.Internal+ default-language: Haskell2010+ extra-libraries: mp3lame+ include-dirs: cbits+ build-depends:+ base >=4.15 && <5.0,+ bytestring >=0.2 && <0.12,+ directory >=1.2.2 && <1.4,+ exceptions >=0.6 && <0.11,+ filepath >=1.2 && <1.5,+ text >=2.0 && <2.1,+ transformers >=0.4 && <0.6,+ wave >=0.1.2 && <0.3 + if flag(dev)+ ghc-options: -Wall -Werror++ else+ ghc-options: -O2 -Wall++ if flag(dev)+ ghc-options:+ -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns+ -Wnoncanonical-monad-instances+ test-suite tests- main-is: Spec.hs- hs-source-dirs: tests- type: exitcode-stdio-1.0- build-depends: base >= 4.8 && < 5.0- , directory >= 1.2.2 && < 1.4- , filepath >= 1.2 && < 1.5- , hspec >= 2.0 && < 3.0- , htaglib >= 1.0 && < 1.3- , lame- , temporary >= 1.1 && < 1.4- , text >= 0.2 && < 1.3- build-tools: hspec-discover >= 2.0 && < 3.0- other-modules: Codec.Audio.LAMESpec- if flag(dev)- ghc-options: -Wall -Werror- else- ghc-options: -O2 -Wall- default-language: Haskell2010+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ build-tool-depends: hspec-discover:hspec-discover >=2.0 && <3.0+ hs-source-dirs: tests+ other-modules: Codec.Audio.LAMESpec+ default-language: Haskell2010+ build-depends:+ base >=4.15 && <5.0,+ directory >=1.2.2 && <1.4,+ filepath >=1.2 && <1.5,+ hspec >=2.0 && <3.0,+ htaglib >=1.0 && <1.3,+ lame,+ temporary >=1.1 && <1.4,+ text >=2.0 && <2.1++ if flag(dev)+ ghc-options: -Wall -Werror++ else+ ghc-options: -O2 -Wall
tests/Codec/Audio/LAMESpec.hs view
@@ -1,69 +1,72 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE RecordWildCards #-} -module Codec.Audio.LAMESpec- ( spec )-where+module Codec.Audio.LAMESpec (spec) where import Codec.Audio.LAME import Control.Monad import Data.Text (Text)+import qualified Data.Text as T import Data.Word (Word8) import Sound.HTagLib import System.Directory import System.FilePath import System.IO.Temp (withSystemTempDirectory) import Test.Hspec-import qualified Data.Text as T data Info = Info- { infoTitle :: Title- , infoArtist :: Artist- , infoAlbum :: Album- , infoComment :: Comment- , infoGenre :: Genre- , infoYear :: Maybe Year- , infoTrackNumber :: Maybe TrackNumber- , infoDuration :: Duration- , infoBitRate :: BitRate- , infoSampleRate :: SampleRate- , infoChannels :: Channels- } deriving (Eq, Show)+ { infoTitle :: Title,+ infoArtist :: Artist,+ infoAlbum :: Album,+ infoComment :: Comment,+ infoGenre :: Genre,+ infoYear :: Maybe Year,+ infoTrackNumber :: Maybe TrackNumber,+ infoDuration :: Duration,+ infoBitRate :: BitRate,+ infoSampleRate :: SampleRate,+ infoChannels :: Channels+ }+ deriving (Eq, Show) spec :: Spec spec = describe "encodeMp3" $ withVariousWaves $ it "produces correct MP3 file" $ \(ipath, opath) -> do- encodeMp3 defaultEncoderSettings- { encoderTagTitle = pure tagTitle- , encoderTagArtist = pure tagArtist- , encoderTagAlbum = pure tagAlbum- , encoderTagYear = pure tagYear- , encoderTagComment = pure tagComment- , encoderTagTrack = pure tagTrack- , encoderTagGenre = pure tagGenre }+ encodeMp3+ defaultEncoderSettings+ { encoderTagTitle = pure tagTitle,+ encoderTagArtist = pure tagArtist,+ encoderTagAlbum = pure tagAlbum,+ encoderTagYear = pure tagYear,+ encoderTagComment = pure tagComment,+ encoderTagTrack = pure tagTrack,+ encoderTagGenre = pure tagGenre+ } ipath opath- Info {..} <- getTags' opath MPEG $ Info- <$> titleGetter- <*> artistGetter- <*> albumGetter- <*> commentGetter- <*> genreGetter- <*> yearGetter- <*> trackNumberGetter- <*> durationGetter- <*> bitRateGetter- <*> sampleRateGetter- <*> channelsGetter+ Info {..} <-+ getTags' opath MPEG $+ Info+ <$> titleGetter+ <*> artistGetter+ <*> albumGetter+ <*> commentGetter+ <*> genreGetter+ <*> yearGetter+ <*> trackNumberGetter+ <*> durationGetter+ <*> bitRateGetter+ <*> sampleRateGetter+ <*> channelsGetter unTitle infoTitle `shouldBe` tagTitle unArtist infoArtist `shouldBe` tagArtist unAlbum infoAlbum `shouldBe` tagAlbum- fmap (T.pack . show . unYear) infoYear `shouldBe` pure tagYear+ fmap (T.pack . show . unYear) infoYear `shouldBe` pure tagYear unComment infoComment `shouldBe` tagComment- fmap unTrackNumber infoTrackNumber `shouldBe`- (pure . fromIntegral . fst) tagTrack+ fmap unTrackNumber infoTrackNumber+ `shouldBe` (pure . fromIntegral . fst) tagTrack unGenre infoGenre `shouldBe` tagGenre unDuration infoDuration `shouldBe` 1 unBitRate infoBitRate `shouldBe` 128@@ -74,7 +77,6 @@ -- Helpers -- | Run given test with various WAVE files.- withVariousWaves :: SpecWith (FilePath, FilePath) -> SpecWith () withVariousWaves m = forM_ waveFiles $ \(path, desc) ->@@ -83,7 +85,6 @@ -- | Make a temporary copy of given file and provide the path to the file in -- a sandbox directory. Automatically remove the files when the test -- finishes.- withSandbox :: FilePath -> ActionWith (FilePath, FilePath) -> IO () withSandbox path action = withSystemTempDirectory "lame-test" $ \dir -> do@@ -94,21 +95,24 @@ waveFiles :: [(FilePath, String)] waveFiles =- [ ( "audio-samples/16bit-int.wav"- , "2 channels 44100 Hz 16 bit PCM" )- , ( "audio-samples/32bit-float.wav"- , "2 channels 44100 Hz 32 bit float PCM" )- , ( "audio-samples/64bit-float.wav"- , "2 channels 44100 Hz 64 bit float PCM" )+ [ ( "audio-samples/16bit-int.wav",+ "2 channels 44100 Hz 16 bit PCM"+ ),+ ( "audio-samples/32bit-float.wav",+ "2 channels 44100 Hz 32 bit float PCM"+ ),+ ( "audio-samples/64bit-float.wav",+ "2 channels 44100 Hz 64 bit float PCM"+ ) ] tagTitle, tagArtist, tagAlbum, tagYear, tagComment, tagGenre :: Text-tagTitle = "Название"-tagArtist = "Исполнитель"-tagAlbum = "Альбом"-tagYear = "2017"+tagTitle = "Название"+tagArtist = "Исполнитель"+tagAlbum = "Альбом"+tagYear = "2017" tagComment = "Комментарий тут…"-tagGenre = "Жанр"+tagGenre = "Жанр" tagTrack :: (Word8, Maybe Word8) tagTrack = (1, Just 10)