{-# LANGUAGE OverloadedStrings #-}
# LANGUAGE RecordWildCards #

-- |
-- Module      :  Codec.Audio.LAME
  Copyright    :   © 2017 – present  
  License      :   BSD 3 clause
--
  Maintainer   :     <  >
-- Stability   :  experimental
-- Portability :  portable
--
-- The module provides an interface to the LAME encoder.
module Codec.Audio.LAME
  ( 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
import Control.Monad.IO.Class
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

-- | LAME encoder settings.
--
-- Currently some noise-shaping and psycho-acoustic parameters are omitted,
-- but can be added on request.
data EncoderSettings = EncoderSettings
  { -- | How to determine amount of compression to apply, see 'Compression'.
  Default value : @'CompressionRatio ' 11@ ( corresponds to 128 kbps ) .
    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 .
      * 7 — OK quality , really fast .
    --
    -- Default value: 'Nothing', meaning that LAME will select the best
    -- value itself.
    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.
    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 ) .
    encoderScale :: !Float,
    -- | Use free format. Default value: 'False'.
    encoderFreeFormat :: !Bool,
  |  as copyright protected . Default value : ' False ' .
    encoderCopyright :: !Bool,
  |  as original . Default value : ' True ' .
    encoderOriginal :: !Bool,
  | Whether to write   header frame . Default value : ' True ' .
    encoderWriteVbrTag :: !Bool,
    -- | Which container to use to write metadata. Default value:
    -- 'Id3v2Only'.
    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 (  ) . Default value :
    -- 'Nothing'.
    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)

  | 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 supported options for compression. You can specify either fixed
  bitrate , compression ratio , or use the VBR mode .
data Compression
  = -- | Specify the compression by setting a fixed bitrate, e.g.
  @'CompressionBitrate ' 320@.
    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 and their parameters .
data VbrMode
  | VBR RH .
    VbrRh
  | VBR ABR . Parameters of the data constructor in order :
    --
    --     * Minimal bitrate
    --     * Mean bitrate
      * Maximal bitrate
    --     * “Hard minimal bitrate”, if 'True', then the specified minimal
    --       bitrate will be enforced in all cases (normally it's violated
    --       for silence).
    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
  = -- | 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
  = -- | 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 .
--
  If the input file is not a valid WAVE file , ' WaveException ' will be
-- thrown. 'I.LameException' is thrown when the LAME encoder reports a
-- problem.
--
-- 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 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
  case waveSampleFormat of
    SampleFormatPcmInt bps ->
      when (bps > 16 || bps <= 8) $
        throwM (I.LameUnsupportedSampleFormat waveSampleFormat)
    _ -> return ()
  I.setNumSamples l waveSamplesTotal
  I.setInputSampleRate l (fromIntegral waveSampleRate)
  I.setNumChannels l (fromIntegral $ waveChannels wave)
  I.setScale l encoderScale
  let targetSampleRate = fromMaybe waveSampleRate encoderSampleRate
  I.setOutputSampleRate l (fromIntegral targetSampleRate)
  I.setFreeFormat l encoderFreeFormat
  forM_ encoderNoGap $ \(currentIndex, total) -> do
    I.setNoGapCurrentIndex l (fromIntegral currentIndex)
    I.setNoGapTotal l (fromIntegral total)
  I.setCopyright l encoderCopyright
  I.setOriginal l encoderOriginal
  I.setErrorProtection l encoderErrorProtection
  I.setStrictISO l encoderStrictISO
  I.setWriteVbrTag l encoderWriteVbrTag
  forM_ encoderQuality (I.setQuality l . fromIntegral . min 9)
  case encoderCompression of
    CompressionBitrate brate ->
      I.setBitrate l (fromIntegral brate)
    CompressionRatio ratio ->
      I.setCompressionRatio l ratio
    CompressionVBR vbrMode vbrQuality -> do
      (I.setVBRQ l . fromIntegral . min 9) vbrQuality
      case vbrMode of
        VbrRh -> I.setVBR l I.VbrRh
        VbrAbr minRate meanRate maxRate hardMin -> do
          I.setVBR l I.VbrAbr
          forM_ minRate (I.setVBRMinBitrate l . fromIntegral)
          forM_ meanRate (I.setVBRMeanBitrate 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")
  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
  setupFilter I.setHighpassFreq I.setHighpassWidth l encoderHighpassFilter
  I.initParams l
  withTempFile' opath $ \otemp -> do
    I.encodingHelper l wave ipath otemp
    renameFile otemp opath

----------------------------------------------------------------------------
-- Helpers

-- | 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
  setFreq l (fromIntegral freq)
  forM_ mwidth (setWidth l . fromIntegral)

-- | 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
      hClose h
      return path'
    cleanup = ignoringIOErrors . removeFile -- in case exception strikes
    -- 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 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 n t@(Just 0) = throwM (I.LameInvalidTrackNumber n t)
renderTrackNumber n t = return . T.pack $ show n ++ maybe "" (("/" ++) . show) t

# LANGUAGE OverloadedStrings #
 |
 Module      :  Codec.Audio.LAME

 Stability   :  experimental
 Portability :  portable

 The module provides an interface to the LAME encoder.
 | LAME encoder settings.

 Currently some noise-shaping and psycho-acoustic parameters are omitted,
 but can be added on request.
 | How to determine amount of compression to apply, see 'Compression'.
 | Select the algorithm. This variable will affect the quality by
 selecting expensive or cheap algorithms. 0 gives the best quality


 Default value: 'Nothing', meaning that LAME will select the best
 value itself.
 Default value: 'Nothing'—disabled.
 | Whether to enable the error protection. Error protection means that
 'False'.
 | Sample rate of the output file. Default value: 'Nothing', which
 means that the sample rate of the input file will be used.
 | Scale input stream by multiplying samples by this value. Default
 | Use free format. Default value: 'False'.
 | Which container to use to write metadata. Default value:
 'Id3v2Only'.
 | Title tag to write. Default value: 'Nothing'.
 | Artist tag to write. Default value: 'Nothing'.
 | Album tag to write. Default value: 'Nothing'.
 | Year tag to write. Default value: 'Nothing'.
 | Comment tag to write. Default value: 'Nothing'.
 Default value: 'Nothing'.
 | Genre tag to write. Default value: 'Nothing'.
 'Nothing'.
 | Settings for the low-pass filter. Default value: 'FilterAuto'.
 | Settings for the high-pass filter. Default value: 'FilterAuto'.

 @since 0.2.0
 | The supported options for compression. You can specify either fixed
 | Specify the compression by setting a fixed bitrate, e.g.
 | Specify the compression ratio.

     * Minimal bitrate
     * Mean bitrate
     * “Hard minimal bitrate”, if 'True', then the specified minimal
       bitrate will be enforced in all cases (normally it's violated
       for silence).
 | Which container to use to write tags\/metadata.
 | Only write ID3v1 metadata
 | Only write ID3v2 metadata
 | Write ID3v1 and ID3v2
 | Settings for a filter.
 | Filter settings are chosen automatically
 | Filter is disabled
 'Nothing', it's chosen automatically).

 thrown. 'I.LameException' is thrown when the LAME encoder reports a
 problem.

 Not all sample formats and bit depths are currently supported. The
 supported sample formats include:

       bit per mono-sample.

 If you feed the encoder something else, 'I.LameUnsupportedSampleFormat'
 will be thrown.
 | Encoder settings
 | Where to save the resulting MP3 file
--------------------------------------------------------------------------
 Helpers
 | Setup a filter for a given 'I.Lame'.
 | How to set cut-off frequncy
 | How to set width of transition band
 | Settings to edit
 | Filter setup to apply
 | 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.
 in case exception strikes
 before we create the actual file
 | Render the track number and optionally the total number of tracks as a
 strict 'Text' value.
# LANGUAGE RecordWildCards #

  Copyright    :   © 2017 – present  
  License      :   BSD 3 clause
  Maintainer   :     <  >
module Codec.Audio.LAME
  ( 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
import Control.Monad.IO.Class
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

data EncoderSettings = EncoderSettings
  Default value : @'CompressionRatio ' 11@ ( corresponds to 128 kbps ) .
    encoderCompression :: !Compression,
  ( 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 .
    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 .
    encoderNoGap :: !(Maybe (Word32, Word32)),
  2 bytes from each frame are used for CRC checksum . Default value :
    encoderErrorProtection :: !Bool,
  | Whether to enforce strict ISO compliance . Default value : ' False ' .
    encoderStrictISO :: !Bool,
    encoderSampleRate :: !(Maybe Word32),
  value : 1 ( no scaling ) .
    encoderScale :: !Float,
    encoderFreeFormat :: !Bool,
  |  as copyright protected . Default value : ' False ' .
    encoderCopyright :: !Bool,
  |  as original . Default value : ' True ' .
    encoderOriginal :: !Bool,
  | Whether to write   header frame . Default value : ' True ' .
    encoderWriteVbrTag :: !Bool,
    encoderMetadataPlacement :: !MetadataPlacement,
    encoderTagTitle :: !(Maybe Text),
    encoderTagArtist :: !(Maybe Text),
    encoderTagAlbum :: !(Maybe Text),
    encoderTagYear :: !(Maybe Text),
    encoderTagComment :: !(Maybe Text),
  | Track number ( the first component of the tuple ) and ( optionally )
  the total number of tracks ( the second component of the tuple ) .
    encoderTagTrack :: !(Maybe (Word8, Maybe Word8)),
    encoderTagGenre :: !(Maybe Text),
  | Album art ( a picture ) to write (  ) . Default value :
    encoderAlbumArt :: !(Maybe ByteString),
    encoderLowpassFilter :: !FilterSetup,
    encoderHighpassFilter :: !FilterSetup
  }
  deriving (Show, Read, Eq, Ord)

  | The default value of ' EncoderSettings ' .
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
    }

  bitrate , compression ratio , or use the VBR mode .
data Compression
  @'CompressionBitrate ' 320@.
    CompressionBitrate Word
    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 and their parameters .
data VbrMode
  | VBR RH .
    VbrRh
  | VBR ABR . Parameters of the data constructor in order :
      * Maximal bitrate
    VbrAbr (Maybe Word) (Maybe Word) (Maybe Word) Bool
  | VBR MTRH
    VbrMtrh
  deriving (Show, Read, Eq, Ord)

data MetadataPlacement
    Id3v1Only
    Id3v2Only
    Id3Both
  deriving (Show, Read, Eq, Ord)

data FilterSetup
    FilterAuto
    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
    FilterManual Word (Maybe Word)
  deriving (Show, Read, Eq, Ord)

  | Encode a WAVE file or RF64 file .
  If the input file is not a valid WAVE file , ' WaveException ' will be
      * PCM with samples represented as signed integers > 8 bit and < = 16
      * IEEE floating point ( 32 bit ) samples .
      * IEEE floating point ( 64 bit ) samples .
encodeMp3 ::
  (MonadIO m) =>
  EncoderSettings ->
  | WAVE file to encode
  FilePath ->
  FilePath ->
  m ()
encodeMp3 EncoderSettings {..} ipath' opath' = liftIO . I.withLame $ \l -> do
  ipath <- makeAbsolute ipath'
  opath <- makeAbsolute opath'
  wave@Wave {..} <- readWaveFile ipath
  case waveSampleFormat of
    SampleFormatPcmInt bps ->
      when (bps > 16 || bps <= 8) $
        throwM (I.LameUnsupportedSampleFormat waveSampleFormat)
    _ -> return ()
  I.setNumSamples l waveSamplesTotal
  I.setInputSampleRate l (fromIntegral waveSampleRate)
  I.setNumChannels l (fromIntegral $ waveChannels wave)
  I.setScale l encoderScale
  let targetSampleRate = fromMaybe waveSampleRate encoderSampleRate
  I.setOutputSampleRate l (fromIntegral targetSampleRate)
  I.setFreeFormat l encoderFreeFormat
  forM_ encoderNoGap $ \(currentIndex, total) -> do
    I.setNoGapCurrentIndex l (fromIntegral currentIndex)
    I.setNoGapTotal l (fromIntegral total)
  I.setCopyright l encoderCopyright
  I.setOriginal l encoderOriginal
  I.setErrorProtection l encoderErrorProtection
  I.setStrictISO l encoderStrictISO
  I.setWriteVbrTag l encoderWriteVbrTag
  forM_ encoderQuality (I.setQuality l . fromIntegral . min 9)
  case encoderCompression of
    CompressionBitrate brate ->
      I.setBitrate l (fromIntegral brate)
    CompressionRatio ratio ->
      I.setCompressionRatio l ratio
    CompressionVBR vbrMode vbrQuality -> do
      (I.setVBRQ l . fromIntegral . min 9) vbrQuality
      case vbrMode of
        VbrRh -> I.setVBR l I.VbrRh
        VbrAbr minRate meanRate maxRate hardMin -> do
          I.setVBR l I.VbrAbr
          forM_ minRate (I.setVBRMinBitrate l . fromIntegral)
          forM_ meanRate (I.setVBRMeanBitrate 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")
  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
  setupFilter I.setHighpassFreq I.setHighpassWidth l encoderHighpassFilter
  I.initParams l
  withTempFile' opath $ \otemp -> do
    I.encodingHelper l wave ipath otemp
    renameFile otemp opath


setupFilter ::
  (I.Lame -> Int -> IO ()) ->
  (I.Lame -> Int -> IO ()) ->
  I.Lame ->
  FilterSetup ->
  IO ()
setupFilter setFreq _ l FilterAuto = setFreq l 0
setupFilter setFreq _ l FilterDisabled = setFreq l (-1)
setupFilter setFreq setWidth l (FilterManual freq mwidth) = do
  setFreq l (fromIntegral freq)
  forM_ mwidth (setWidth l . fromIntegral)

withTempFile' :: FilePath -> (FilePath -> IO a) -> IO a
withTempFile' path = bracketOnError acquire cleanup
  where
    acquire = do
      (path', h) <- openBinaryTempFile dir file
      hClose h
      return path'
    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 ())

renderTrackNumber :: Word8 -> Maybe Word8 -> IO Text
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
