packages feed

flac-0.2.1: tests/Codec/Audio/FLAC/StreamEncoderSpec.hs

{-# LANGUAGE RecordWildCards #-}

module Codec.Audio.FLAC.StreamEncoderSpec (spec) where

import Codec.Audio.FLAC.StreamDecoder
import Codec.Audio.FLAC.StreamEncoder
import Codec.Audio.Wave
import Control.Monad
import Data.ByteString (ByteString)
import Data.ByteString qualified as B
import System.Directory
import System.FilePath
import System.IO
import System.IO.Temp (withSystemTempDirectory)
import Test.Hspec

spec :: Spec
spec =
  describe "encodeFlac and decodeFlac" $
    withVariousWaves $
      it "encodes and decodes back correctly" $ \path -> do
        -- Input and output paths coincide on purpose, we test that nothing
        -- bad can happen in this case.
        old <- Blind <$> fetchWaveBody path
        -- We first let the built-in verification mechanics catch possible
        -- errors.
        encodeFlac
          defaultEncoderSettings
            { encoderVerify = True
            }
          path
          path
        -- Then we let decoder check that the streams match with the MD5
        -- hash.
        decodeFlac
          defaultDecoderSettings
            { decoderMd5Checking = True
            }
          path
          path
        -- But we also want to be sure that audio streams match
        -- byte-by-byte, we can't trust just FLAC's own checking.
        new <- Blind <$> fetchWaveBody path
        new `shouldBe` old

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

newtype Blind a = Blind a

instance (Eq a) => Eq (Blind a) where
  (Blind x) == (Blind y) = x == y

instance Show (Blind a) where
  show _ = "<blind data cannot be shown>"

-- | Run given test with various WAVE files.
withVariousWaves :: SpecWith FilePath -> SpecWith ()
withVariousWaves m =
  forM_ waveFiles $ \(path, desc) ->
    context ("when given " ++ desc) (around (withSandbox path) m)

-- | 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 -> IO ()
withSandbox path action =
  withSystemTempDirectory "flac-test" $ \dir -> do
    let path' = dir </> "файл" -- testing Unicode
    copyFile path path'
    action path'

-- | Extract body of given WAVE or RF64 file as a strict 'ByteString'.
-- Needless to say that the body should be relatively short.
fetchWaveBody :: FilePath -> IO ByteString
fetchWaveBody path = do
  Wave {..} <- readWaveFile path
  withBinaryFile path ReadMode $ \h -> do
    hSeek h AbsoluteSeek (fromIntegral waveDataOffset)
    B.hGet h (fromIntegral waveDataSize)

waveFiles :: [(FilePath, String)]
waveFiles =
  [ ( "audio-samples/1ch-44100hz-16bit.rf64",
      "1 channel 44100 Hz 16 bit (RF64)"
    ),
    ( "audio-samples/1ch-44100hz-16bit.wav",
      "1 channel 44100 Hz 16 bit (WAVE)"
    ),
    ( "audio-samples/1ch-44100hz-16bit-x.wav",
      "1 channel 44100 Hz 16 bit (WAVEX)"
    ),
    ( "audio-samples/2ch-11025hz-24bit.rf64",
      "2 channels 11025 Hz 24 bit (RF64)"
    ),
    ( "audio-samples/2ch-11025hz-24bit.wav",
      "2 channels 11025 Hz 24 bit (WAVE)"
    ),
    ( "audio-samples/2ch-11025hz-24bit-x.wav",
      "2 channels 11025 Hz 24 bit (WAVEX)"
    ),
    ( "audio-samples/2ch-8000hz-8bit.rf64",
      "2 channels 8000 Hz 8 bit (RF64)"
    ),
    ( "audio-samples/2ch-8000hz-8bit.wav",
      "2 channels 8000 Hz 8 bit (WAVE)"
    ),
    ( "audio-samples/2ch-8000hz-8bit-x.wav",
      "2 channels 8000 Hz 8 bit (WAVEX)"
    ),
    ( "audio-samples/1ch-44100hz-8bit-full-range.wav",
      "1 channel 44100 Hz 8 bit full range (WAVE)"
    ),
    ( "audio-samples/1ch-44100hz-8bit-silence.wav",
      "1 channel 44100 Hz 8 bit silence (WAVE)"
    ),
    ( "audio-samples/1ch-44100hz-16bit-full-range.wav",
      "1 channel 44100 Hz 16 bit full range (WAVE)"
    ),
    ( "audio-samples/1ch-44100hz-16bit-silence.wav",
      "1 channel 44100 Hz 16 bit silence (WAVE)"
    ),
    ( "audio-samples/1ch-44100hz-24bit-full-range.wav",
      "1 channel 44100 Hz 24 bit full range (WAVE)"
    ),
    ( "audio-samples/1ch-44100hz-24bit-silence.wav",
      "1 channel 44100 Hz 24 bit silence (WAVE)"
    )
  ]