module Data.ByteString.IsoBaseFileFormat.Boxes.Box
(module Data.ByteString.IsoBaseFileFormat.Boxes.Box, module X)
where
import Data.ByteString.IsoBaseFileFormat.Boxes.Brand as X
import Data.Bits as X
import Data.ByteString.Builder as X
import Data.Monoid as X
import Data.Proxy as X
import Data.Word as X
import Data.Kind
import GHC.TypeLits as X
import Data.String
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Singletons.Prelude.List ((:++))
import qualified Data.ByteString as B
class (IsBoxContent (BoxContent t)) => IsBoxType t where
type BoxContent t
type BoxContent t = ()
toBoxType :: proxy t -> BoxContent t -> BoxType
class IsBoxContent a where
boxSize :: a -> BoxSize
boxBuilder :: a -> Builder
data Box brand b where
Box ::
(IsBoxType b, IsBrandConform brand ('Just b) ts) =>
BoxContent b -> Boxes brand ts -> Box brand b
instance IsBoxContent (Box brand cnt) where
boxBuilder b@(Box cnt nested) = sB <> tB <> sExtB <> tExtB <> cntB <> nestedB
where s = boxSize b
t = toBoxType b cnt
sB = boxBuilder s
sExtB = boxBuilder (BoxSizeExtension s)
tB = boxBuilder t
tExtB = boxBuilder (BoxTypeExtension t)
cntB = boxBuilder cnt
nestedB = boxBuilder nested
boxSize b@(Box cnt nested) = sPayload + boxSize (BoxSizeExtension sPayload)
where sPayload =
boxSize (BoxSize undefined) + boxSize t + boxSize cnt +
boxSize (BoxTypeExtension t) +
boxSize nested
t = toBoxType b cnt
data Boxes brand (boxTypes :: [Type]) where
NoBoxes :: Boxes brand '[]
(:.) :: Box brand l -> Boxes brand r -> Boxes brand (l ': r)
(:|) :: Box brand l -> Box brand r -> Boxes brand '[l, r]
(:<>) :: Boxes brand l -> Boxes brand r -> Boxes brand (l :++ r)
infixr 1 :<>
infixr 2 :.
infixr 2 :|
infixr 3 $:
($:) :: (Boxes brand '[l] -> r) -> Box brand l -> r
($:) f = f . singletonBox
singletonBox :: Box brand l -> Boxes brand '[l]
singletonBox b = b :. NoBoxes
instance IsBoxContent (Boxes brand bs) where
boxSize NoBoxes = 0
boxSize (l :. r) = boxSize l + boxSize r
boxSize (l :| r) = boxSize l + boxSize r
boxSize (l :<> r) = boxSize l + boxSize r
boxBuilder NoBoxes = mempty
boxBuilder (l :. r) = boxBuilder l <> boxBuilder r
boxBuilder (l :| r) = boxBuilder l <> boxBuilder r
boxBuilder (l :<> r) = boxBuilder l <> boxBuilder r
closedBox :: (IsBoxType t, IsBrandConform brand ('Just t) '[])
=> BoxContent t -> Box brand t
closedBox c = Box c NoBoxes
containerBox :: (IsBoxType t,IsBrandConform brand ('Just t) ts,BoxContent t ~ ())
=> Boxes brand ts -> Box brand t
containerBox = Box ()
data BoxSize
= UnlimitedSize
| BoxSize Word64
deriving (Show,Eq)
instance IsBoxContent BoxSize where
boxSize _ = BoxSize 4
boxBuilder UnlimitedSize = word32BE 0
boxBuilder (BoxSize n) =
word32BE $
if n < (4294967296 :: Word64)
then fromIntegral n
else 1
instance Num BoxSize where
(+) UnlimitedSize _ = UnlimitedSize
(+) _ UnlimitedSize = UnlimitedSize
(+) (BoxSize l) (BoxSize r) = BoxSize (l + r)
() UnlimitedSize _ = UnlimitedSize
() _ UnlimitedSize = UnlimitedSize
() (BoxSize l) (BoxSize r) = BoxSize (l r)
(*) UnlimitedSize _ = UnlimitedSize
(*) _ UnlimitedSize = UnlimitedSize
(*) (BoxSize l) (BoxSize r) = BoxSize (l * r)
abs UnlimitedSize = UnlimitedSize
abs (BoxSize n) = BoxSize (abs n)
signum UnlimitedSize = UnlimitedSize
signum (BoxSize n) = BoxSize (signum n)
fromInteger n = BoxSize $ fromInteger n
data BoxSizeExtension =
BoxSizeExtension BoxSize
instance IsBoxContent BoxSizeExtension where
boxBuilder (BoxSizeExtension UnlimitedSize) = mempty
boxBuilder (BoxSizeExtension (BoxSize n)) =
if n < 4294967296
then mempty
else word64BE n
boxSize (BoxSizeExtension UnlimitedSize) = 0
boxSize (BoxSizeExtension (BoxSize n)) =
BoxSize $
if n < 4294967296
then 0
else 8
data BoxType
=
StdType FourCc
|
CustomBoxType String
deriving (Show,Eq)
newtype FourCc =
FourCc (Char,Char,Char,Char)
deriving (Show,Eq)
instance IsString FourCc where
fromString str
| length str == 4 =
let [a,b,c,d] = str
in FourCc (a,b,c,d)
| otherwise =
error ("cannot make a 'FourCc' of a String which isn't exactly 4 bytes long: " ++
show str ++ " has a length of " ++ show (length str))
instance IsBoxContent FourCc where
boxSize _ = 4
boxBuilder (FourCc (a,b,c,d)) = putW a <> putW b <> putW c <> putW d
where putW = word8 . fromIntegral . fromEnum
instance IsBoxContent BoxType where
boxSize _ = boxSize (FourCc undefined)
boxBuilder t =
case t of
StdType x -> boxBuilder x
CustomBoxType _ -> boxBuilder (FourCc ('u','u','i','d'))
data BoxTypeExtension =
BoxTypeExtension BoxType
instance IsBoxContent BoxTypeExtension where
boxSize (BoxTypeExtension (StdType _)) = 0
boxSize (BoxTypeExtension (CustomBoxType _)) = 16 * 4
boxBuilder (BoxTypeExtension (StdType _)) = mempty
boxBuilder (BoxTypeExtension (CustomBoxType str)) =
mconcat (map (word8 . fromIntegral . fromEnum)
(take (16 * 4) str) ++
repeat (word8 0))
instance IsBoxContent () where
boxSize _ = 0
boxBuilder _ = mempty
instance IsBoxContent B.ByteString where
boxSize = fromIntegral . B.length
boxBuilder = byteString
instance IsBoxContent T.Text where
boxSize = (1+) . fromIntegral . T.length
boxBuilder txt = boxBuilder (T.encodeUtf8 txtNoNulls) <> word8 0
where txtNoNulls = T.map (\c -> if c == '\0' then ' ' else c) txt