wavy-0.1.0.0: Sound/Wav/Info.hs
-- | The Sound.Wav.Info module is responsible for abolutely everything in the INFO
-- Metadata chunk of a RIFF file. It allows you to read, write and modify that chunk of
-- data easily.
module Sound.Wav.Info
( parseWaveInfo
, updateWaveInfo
, getInfoData
, waveInfoToRiffChunks
) where
import Data.List (intercalate)
import Data.List.Split (splitOn)
import Data.Maybe (catMaybes, fromMaybe)
import qualified Data.Riff as R
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BLC
import Sound.Wav.Data
import Control.Applicative ((<$>))
-- TODO the code in this function could be greatly cleaned up via lenses
-- | Update the INFO metadata chunk inside an existing WaveFile. This will allow you to
-- edit the metadata inside a file.
updateWaveInfo
:: (WaveInfo -> WaveInfo) -- ^ The conversion function from original to new metadata.
-> WaveFile -- ^ The input WaveFile that will be modified.
-> WaveFile -- ^ A new WaveFile that contains the updated INFO section.
updateWaveInfo updater waveFile = waveFile { waveInfo = updater <$> waveInfo waveFile }
-- | You want to be able to get the info chunk from your WaveFiles, however, if the info
-- chunk does not exist then you will be provided with a default info chunk.
getInfoData
:: WaveFile -- ^ The file that you wish to extract INFO metadata from.
-> WaveInfo -- ^ The info metadata.
getInfoData = fromMaybe waveInfoDefault . waveInfo
waveInfoToRiffChunks :: WaveInfo -> [R.RiffChunk]
waveInfoToRiffChunks waveInfo = catMaybes $ fmap (\x -> x waveInfo)
[ convertString "IARL" . archiveLocation
, convertString "IART" . artist
, convertString "ICMS" . commissionedBy
, convertString "ICMT" . comments
, convertStringList "ICOP" . copyrights
, convertString "ICRD" . creationDate
, convertString "ICRP" . croppedDetails
, convertString "IDIM" . originalDimensions
, convertString "IDPI" . dotsPerInch
, convertStringList "IENG" . engineers
, convertString "IGNR" . genre
, convertStringList "IKEY" . keywords
, convertString "ILGT" . lightness
, convertString "IMED" . originalMedium
, convertString "INAM" . name
, convertString "IPLT" . coloursInPalette
, convertString "IPRD" . originalProduct
, convertString "ISBJ" . subject
-- TODO put our own name in here, then make it optional
, convertString "ISFT" . creationSoftware
, convertString "ISHP" . sharpness
, convertString "ISCR" . contentSource
, convertString "ISRF" . originalForm
, convertString "ITCH" . technician
]
where
convertString :: String -> Maybe String -> Maybe R.RiffChunk
convertString riffId = fmap (R.RiffChunkChild riffId . BLC.pack)
convertStringList :: String -> Maybe [String] -> Maybe R.RiffChunk
convertStringList riffId = convertString riffId . fmap (intercalate "; ")
-- | Get the INFO metadata from a Byte Stream.
parseWaveInfo :: [R.RiffChunk] -> WaveInfo
parseWaveInfo = foldr appendWaveInfo waveInfoDefault
appendWaveInfo :: R.RiffChunk -> WaveInfo -> WaveInfo
appendWaveInfo (R.RiffChunkChild riffId rawData) initial =
case riffId of
"IARL" -> initial { archiveLocation = Just asString}
"IART" -> initial { artist = Just asString}
"ICMS" -> initial { commissionedBy = Just asString}
"ICMT" -> initial { comments = Just asString}
"ICOP" -> initial { copyrights = Just asStringList}
"ICRD" -> initial { creationDate = Just asString}
"ICRP" -> initial { croppedDetails = Just asString}
"IDIM" -> initial { originalDimensions = Just asString}
"IDPI" -> initial { dotsPerInch = Just asString}
"IENG" -> initial { engineers = Just asStringList}
"IGNR" -> initial { genre = Just asString}
"IKEY" -> initial { keywords = Just asStringList}
"ILGT" -> initial { lightness = Just asString}
"IMED" -> initial { originalMedium = Just asString}
"INAM" -> initial { name = Just asString}
"IPLT" -> initial { coloursInPalette = Just asString}
"IPRD" -> initial { originalProduct = Just asString}
"ISBJ" -> initial { subject = Just asString}
"ISFT" -> initial { creationSoftware = Just asString}
"ISHP" -> initial { sharpness = Just asString}
"ISCR" -> initial { contentSource = Just asString}
"ISRF" -> initial { originalForm = Just asString}
"ITCH" -> initial { technician = Just asString}
-- Skipping and ignoring them kinda sucks, in the future make it so
-- that you put them in a buffer somewhere
_ -> initial
where
asString = parseInfoString rawData
asStringList = parseInfoStrings rawData
appendWaveInfo _ initial = initial
parseInfoString :: BL.ByteString -> String
parseInfoString = dropTrailingNull . BLC.unpack
dropTrailingNull :: String -> String
dropTrailingNull = reverse . dropWhile (== '\0') . reverse
parseInfoStrings :: BL.ByteString -> [String]
parseInfoStrings = splitOn "; " . parseInfoString