module Data.ByteString.IsoBaseFileFormat.Boxes.Box
(module Data.ByteString.IsoBaseFileFormat.Boxes.Box, module X)
where
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 GHC.TypeLits as X
import Data.String
import Data.Type.Equality
import Data.Type.List
import Data.Type.Bool
import Data.Type.Equality
import GHC.Exts
class BoxRules (t :: k) where
type RestrictedTo t :: Maybe [k]
type RestrictedTo t = 'Just '[]
type IsTopLevelBox t :: Bool
type IsTopLevelBox t = 'True
type RequiredNestedBoxes t :: [k]
type RequiredNestedBoxes t = '[]
type GetCardinality t (c :: k) :: Cardinality
type GetCardinality t any = ExactlyOnce
data Cardinality = AtMostOnce | ExactlyOnce | OnceOrMore
class BoxRules t => IsBoxType (t :: k) where
toBoxType :: proxy t -> BoxType
instance (BoxRules t, KnownSymbol t) => IsBoxType t where
toBoxType _ = StdType (fromString (symbolVal (Proxy :: Proxy t)))
class IsBoxContent a where
boxSize :: a -> BoxSize
boxBuilder :: a -> Builder
instance IsBoxContent () where
boxSize _ = 0
boxBuilder _ = mempty
data Extend a b =
Extend a
b
instance (IsBoxContent p,IsBoxContent c) => IsBoxContent (Extend p c) where
boxSize (Extend p c) = boxSize p + boxSize c
boxBuilder (Extend p c) = boxBuilder p <> boxBuilder c
box :: forall t c.
(IsBoxType t,IsBoxContent c)
=> c -> Box t
box cnt = Box (toBoxType (Proxy :: Proxy t)) cnt
emptyBox :: forall t . (IsBoxType t) => Box t
emptyBox = box ()
data Box (b :: t) where
Box :: (IsBoxType t,IsBoxContent c) => BoxType -> c -> Box t
instance IsBoxContent (Box t) where
boxBuilder b@(Box t cnt) = sFix <> tFix <> sExt <> tExt <> boxBuilder cnt
where s = boxSize b
sFix = boxBuilder s
sExt = boxBuilder (BoxSizeExtension s)
tFix = boxBuilder t
tExt = boxBuilder (BoxTypeExtension t)
boxSize b@(Box t cnt) = sPayload + boxSize (BoxSizeExtension sPayload)
where sPayload =
boxSize sPayload + boxSize t + boxSize cnt +
boxSize (BoxTypeExtension t)
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 < 2 ^ 32
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 < 2 ^ 32
then mempty
else word64BE n
boxSize (BoxSizeExtension UnlimitedSize) = 0
boxSize (BoxSizeExtension (BoxSize n)) =
BoxSize $
if n < 2 ^ 32
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 u -> 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))
data ParentBox (b :: t) where
ParentBox :: (IsBoxType t,IsBoxContent c) => BoxType -> c -> ParentBox t
parentBox :: forall t c . (IsBoxType t, IsBoxContent c) => c -> ParentBox t
parentBox = toParentBox . box
where toParentBox :: IsBoxType s => Box s -> ParentBox s
toParentBox (Box t c) = ParentBox t c
emptyParentBox :: forall t . (IsBoxType t) => ParentBox t
emptyParentBox = parentBox ()
boxes :: (IsBoxType t,IsBoxContent (Boxes t ts))
=> ParentBox t -> Boxes t ts -> Box t
boxes p = box . Extend (toBox p)
where
toBox :: IsBoxType t => ParentBox t -> Box t
toBox (ParentBox t c) = Box t c
(^-) :: (IsBoxType t,IsBoxContent (Boxes t ts))
=> ParentBox t -> Boxes t ts -> Box t
parent ^- nested = parent ^- nested
infixr 1 ^-
data Boxes (cont :: x) (boxTypes :: [x]) where
Nested :: IsBoxType t => Box t -> Boxes c '[t]
(:-) :: IsBoxType t => Boxes c ts -> Box t -> Boxes c (t ': ts)
infixl 2 :-
instance (IsBoxType t,ValidBoxes t bs) => IsBoxContent (Boxes t bs) where
boxSize bs = boxSize (UnverifiedBoxes bs)
boxBuilder bs = boxBuilder (UnverifiedBoxes bs)
newtype UnverifiedBoxes t ts = UnverifiedBoxes (Boxes t ts)
instance IsBoxContent (UnverifiedBoxes t bs) where
boxSize (UnverifiedBoxes (Nested b)) = boxSize b
boxSize (UnverifiedBoxes (bs :- b)) = boxSize (UnverifiedBoxes bs) + boxSize b
boxBuilder (UnverifiedBoxes (Nested b)) = boxBuilder b
boxBuilder (UnverifiedBoxes (bs :- b)) = boxBuilder (UnverifiedBoxes bs) <> boxBuilder b
type ValidBoxes t ts =
( AllAllowedIn t ts ~ 'True
, HasAllRequiredBoxes t (RequiredNestedBoxes t) ts ~ 'True
, CheckTopLevelOk t ~ 'True)
type family AllAllowedIn (container :: k) (boxes :: [k]) :: Bool
where
AllAllowedIn c '[] = 'True
AllAllowedIn c (t ': ts) =
If (CheckAllowedIn c t (RestrictedTo t))
(AllAllowedIn c ts)
(TypeError (NotAllowedMsg c t))
type family CheckAllowedIn (c :: k) (t :: k) (a :: Maybe [k]) :: Bool where
CheckAllowedIn c t 'Nothing = 'True
CheckAllowedIn c t ('Just rs) = Find c rs
type NotAllowedMsg c t =
Text "Boxes of type: "
:<>: ShowType c
:<>: Text " may not contain boxes of type "
:<>: ShowType t
:$$: Text "Valid containers for "
:<>: ShowType t
:<>: Text " boxes are: "
:$$: ShowType (RestrictedTo t)
:$$: ShowType t
:<>: If (IsTopLevelBox c)
(Text " boxes may appear top-level in a file.")
(Text " boxes must be nested.")
type family HasAllRequiredBoxes (c :: k) (req :: [k]) (nested :: [k]) :: Bool
where
HasAllRequiredBoxes c '[] nested = 'True
HasAllRequiredBoxes c (r ': restReq) nested =
If (Find r nested)
(HasAllRequiredBoxes c restReq nested)
(TypeError (MissingRequired c r nested))
type IsSubSet base sub = Intersection base sub == sub
type MissingRequired c r nested =
Text "Boxes of type: "
:<>: ShowType c
:<>: Text " require these nested boxes: "
:<>: ShowType (RequiredNestedBoxes c)
:$$: Text "but only these box types were nested: "
:<>: ShowType nested
:$$: Text "e.g. this type is missing: "
:<>: ShowType r
type family CheckTopLevelOk (t :: k) :: Bool where
CheckTopLevelOk t = IsTopLevelBox t || TypeError (NotTopLevenError t)
type NotTopLevenError c =
Text "Boxes of type "
:<>: ShowType c
:<>: Text " MUST be nested inside boxes of these types: "
:$$: ShowType (RestrictedTo c)
type FullBox t = Extend FullBoxHeader t
fullBox
:: (IsBoxType t, IsBoxContent c)
=> BoxVersion -> BoxFlags 24 -> c -> Box t
fullBox ver fs cnt = box (Extend (FullBoxHeader ver fs) cnt)
data FullBoxHeader =
FullBoxHeader BoxVersion
(BoxFlags 24)
instance IsBoxContent FullBoxHeader where
boxSize (FullBoxHeader _ f) = 1 + boxSize f
boxBuilder (FullBoxHeader (BoxVersion v) f) = word8 v <> boxBuilder f
newtype BoxVersion =
BoxVersion Word8
newtype BoxFlags bits =
BoxFlags Integer
deriving (Eq,Show,Num)
boxFlagBitMask :: KnownNat bits
=> BoxFlags bits -> Integer
boxFlagBitMask px = 2 ^ natVal px 1
cropBits :: KnownNat bits
=> BoxFlags bits -> BoxFlags bits
cropBits f@(BoxFlags b) = BoxFlags (b .&. boxFlagBitMask f)
instance KnownNat bits => IsBoxContent (BoxFlags bits) where
boxSize f =
let minBytes = fromInteger $ natVal f `div` 8
modBytes = fromInteger $ natVal f `mod` 8
in BoxSize $ minBytes + signum modBytes
boxBuilder f@(BoxFlags b) =
let bytes =
let (BoxSize bytes') = boxSize f
in fromIntegral bytes'
wordSeq n
| n <= bytes =
word8 (fromIntegral (shiftR b ((bytes n) * 8) .&. 255)) <>
wordSeq (n + 1)
| otherwise = mempty
in wordSeq 1
instance KnownNat bits => Bits (BoxFlags bits) where
(.&.) lf@(BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ l .&. r
(.|.) lf@(BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ l .&. r
xor (BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ xor l r
complement (BoxFlags x) = cropBits $ BoxFlags $ complement x
shift (BoxFlags x) = cropBits . BoxFlags . shift x
rotateL = error "TODO rotateL"
rotateR = error "TODO rotateR"
bitSize = fromInteger . natVal
bitSizeMaybe = Just . fromInteger . natVal
isSigned _ = False
testBit f n =
let (BoxFlags b) = cropBits f
in testBit b n
bit = cropBits . BoxFlags . bit
popCount f =
let (BoxFlags b) = cropBits f
in popCount b
zeroBits = BoxFlags 0