{-# LANGUAGE FlexibleContexts #-}
-- | Convert between FFmpeg frames and JuicyPixels images.
module Codec.FFmpeg.Juicy where
import Codec.Picture
import Codec.FFmpeg.Enums
import Codec.FFmpeg.Types
import Control.Applicative
import Control.Arrow ((&&&))
import Control.Monad (when, (>=>))
import Control.Monad.Trans.Maybe
import Data.Foldable (traverse_)
import qualified Data.Vector.Storable as V
import qualified Data.Vector.Storable.Mutable as VM
import Foreign.C.Types
import Foreign.Marshal.Array (advancePtr, copyArray)
import Foreign.Ptr (castPtr, Ptr)

-- | Convert an 'AVFrame' to a 'DynamicImage' with the result in the
-- 'MaybeT' transformer.
-- 
-- > toJuicyT = MaybeT . toJuicy
toJuicyT :: AVFrame -> MaybeT IO DynamicImage
toJuicyT = MaybeT . toJuicy

-- | Convert an 'AVFrame' to a 'DynamicImage'.
toJuicy :: AVFrame -> IO (Maybe DynamicImage)
toJuicy frame = do
  fmt <- getPixelFormat frame
  when (fmt /= avPixFmtRgb24)
       (putStrLn "Not RGB24?!")
  w <- fromIntegral <$> getWidth frame
  h <- fromIntegral <$> getHeight frame
  pixels <- castPtr <$> getData frame :: IO (Ptr CUChar)
  srcStride <- fromIntegral <$> getLineSize frame
  let dstStride = w * 3
  v <- VM.new (w*h*3)
  VM.unsafeWith v $ \vptr ->
    mapM_ (\(i,o) -> copyArray (advancePtr vptr o)
                               (advancePtr pixels i)
                               (w * 3))
          (map ((srcStride *) &&& (dstStride*)) [0 .. h - 1])
  v' <- V.unsafeFreeze v
  let mkImage :: V.Storable (PixelBaseComponent a)
              => (Image a -> DynamicImage) -> Maybe DynamicImage
      mkImage c = Just $ c (Image w h (V.unsafeCast v'))
  return $
    case () of
      _ | fmt == avPixFmtRgb24 -> mkImage ImageRGB8
        | fmt == avPixFmtGray8 -> mkImage ImageY8
        | fmt == avPixFmtGray16 -> mkImage ImageY16
        | otherwise -> Nothing

-- | Save an 'AVFrame' to a PNG file on disk assuming the frame could
-- be converted to a 'DynamicImage' using 'toJuicy'.
saveJuicy :: FilePath -> AVFrame -> IO ()
saveJuicy name = toJuicy >=> traverse_ (savePngImage name)