idiii-0.1.1: src/ID3/Type/Frame.hs
module ID3.Type.Frame where
import Data.Accessor
import Data.Accessor.Basic (compose)
import ID3.Type.Flags
import ID3.Type.Unparse
import ID3.Type.FrameInfo
import ID3.Parser.NativeFrames
{-- | /ID3V2 FRAME OVERVIEW/
All ID3v2 frames consists of one frame header followed by one or more
fields containing the actual information. The header is always 10
bytes and laid out as follows:
@
Frame ID $xx xx xx xx (four characters)
Size 4 * %0xxxxxxx
Flags $xx xx
@
--}
data ID3Frame = ID3Frame
{ frHeader_ :: FrameHeader -- ^ frame Header
, frInfo_ :: FrameInfo -- ^ frame Information Value
} deriving Eq
emptyID3Frame = ID3Frame emptyFrameHeader (Unknown "")
initID3Frame = flip compose emptyID3Frame
frHeader = accessor frHeader_ (\x fr -> fr {frHeader_ = x})
frInfo = accessor frInfo_ (\x fr -> fr {frInfo_ = x})
instance HasSize ID3Frame where
size = accessor (toInteger . length . unparse . getVal frInfo) (setVal $ frHeader .> frSize)
--content :: (FrameInfo -> a) -> Accessor ID3Frame a
--content a = accessor (\f -> a (f^.frInfo)) (\x f -> frInfo^=((f^.frInfo) {a=x}) $ f)
textContent = frInfo .> infoTextContent
instance Show ID3Frame where
show fr = (show $ fr^.frHeader) ++"\n"++
(show $ fr^.frInfo )++"\n"
{-showFrameInfo info = concatMap showRec info
where showRec (s, bs) = "\t"++s++":\t"++bs++"\n"-}
instance Parsed ID3Frame where
unparse fr = (unparse $ fr^.frHeader) ++
(unparse $ fr^.frInfo )
type FrameName = String
{-type FrameInfo = [(String, String)]-}
{- | Frame Header
-}
data FrameHeader = FrameHeader
{ frID_ :: FrameID -- ^ frame ID
, frSize_ :: FrameSize -- ^ frame Size
, frFlags_ :: FrameFlags -- ^ frame Flags
} deriving Eq
emptyFrameHeader = FrameHeader "" 0 emptyFrameFlags
initFrameHeader = flip compose emptyFrameHeader
frID = accessor frID_ (\x h -> h {frID_ = x})
frSize = accessor frSize_ (\x h -> h {frSize_ = x})
frFlags = accessor frFlags_ (\x h -> h {frFlags_ = x})
instance Show FrameHeader where
show (FrameHeader i s fs) = "\tFrame ID:\t"++i++"\n"++
"\tFrame size:\t"++(show s)++
(show fs)
instance Parsed FrameHeader where
unparse fh = (unparse $ Str $ fh^.frID ) ++
(unparse $ fh^.frSize ) ++
(unparse $ fh^.frFlags )
{-- | /FRAME ID/
The frame ID is made out of the characters capital A-Z and 0-9.
Identifiers beginning with "X", "Y" and "Z" are for experimental
frames and free for everyone to use, without the need to set the
experimental bit in the tag header. Bear in mind that someone else
might have used the same identifier as you. All other identifiers are
either used or reserved for future use.
--}
type FrameID = String
{-- | /SIZE BYTES/
The frame ID is followed by a size descriptor containing the size of
the data in the final frame, after encryption, compression and
unsynchronisation. The size is excluding the frame header ('total
frame size' - 10 bytes) and stored as a 32 bit synchsafe integer.
--}
type FrameSize = Integer
{-- | /Frame header flags/
In the frame header the size descriptor is followed by two flag
bytes. All unused flags MUST be cleared. The first byte is for
'status messages' and the second byte is a format description. If an
unknown flag is set in the first byte the frame MUST NOT be changed
without that bit cleared. If an unknown flag is set in the second
byte the frame is likely to not be readable. Some flags in the second
byte indicates that extra information is added to the header. These
fields of extra information is ordered as the flags that indicates
them. The flags field is defined as follows (l and o left out because
ther resemblence to one and zero):
@
%0abc0000 %0h00kmnp
@
Some frame format flags indicate that additional information fields
are added to the frame. This information is added after the frame
header and before the frame data in the same order as the flags that
indicates them. I.e. the four bytes of decompressed size will precede
the encryption method byte. These additions affects the 'frame size'
field, but are not subject to encryption or compression.
The default status flags setting for a frame is, unless stated
otherwise, 'preserved if tag is altered' and 'preserved if file is
altered', i.e. @%00000000@.
--}
data FrameFlags = FrameFlags
{ statusF :: StatusFlags -- ^ Frame status flags
, formatF :: FormatFlags -- ^ Frame format flags
} deriving Eq
emptyFrameFlags = FrameFlags emptyFlags emptyFlags
initFrameFlags = flip compose emptyFrameFlags
status = accessor statusF (\x fs -> fs {statusF = x})
format = accessor formatF (\x fs -> fs {formatF = x})
instance Show FrameFlags where
show fs = if not ((or $ fs^.status^.allFlags) || (or $ fs^.format^.allFlags)) then "" else
"\tFlags:\n"++
(showStatusFlags $ fs^.status)++"\n"++
(showFormatFlags $ fs^.format)
instance Parsed FrameFlags where
unparse fs = (unparse $ fs^.status ) ++
(unparse $ fs^.format )
{-- | /Frame status flags/
Format: @%0abc0000@ where
a - /Tag alter preservation/
This flag tells the tag parser what to do with this frame if it is
unknown and the tag is altered in any way. This applies to all
kinds of alterations, including adding more padding and reordering
the frames.
@
0 Frame should be preserved.
1 Frame should be discarded.
@
b - /File alter preservation/
This flag tells the tag parser what to do with this frame if it is
unknown and the file, excluding the tag, is altered. This does not
apply when the audio is completely replaced with other audio data.
@
0 Frame should be preserved.
1 Frame should be discarded.
@
c - /Read only/
This flag, if set, tells the software that the contents of this
frame are intended to be read only. Changing the contents might
break something, e.g. a signature. If the contents are changed,
without knowledge of why the frame was flagged read only and
without taking the proper means to compensate, e.g. recalculating
the signature, the bit MUST be cleared.
--}
type StatusFlags = Flags
frameDiscard = accessFlag 1
fileDiscard = accessFlag 2
readOnly = accessFlag 3
showStatusFlags stat = if not (or $ stat^.allFlags) then "" else
"\t\tStatus Flags:\n"++
(if stat^.frameDiscard then "\t\t\t- Frame should be discarded\n" else "")++
(if stat^.fileDiscard then "\t\t\t- Frame should be discarded\n" else "")++
(if stat^.readOnly then "\t\t\t- Read only!\n" else "")
{-- | /Frame format flags/
Format: @%0h00kmnp@ where
h - /Grouping identity/
This flag indicates whether or not this frame belongs in a group
with other frames. If set, a group identifier byte is added to the
frame. Every frame with the same group identifier belongs to the
same group.
@
0 Frame does not contain group information
1 Frame contains group information
@
k - /Compression/
This flag indicates whether or not the frame is compressed.
A 'Data Length Indicator' byte MUST be included in the frame.
@
0 Frame is not compressed.
1 Frame is compressed using zlib [zlib] deflate method.
If set, this requires the 'Data Length Indicator' bit
to be set as well.
@
m - /Encryption/
This flag indicates whether or not the frame is encrypted. If set,
one byte indicating with which method it was encrypted will be
added to the frame. See description of the ENCR frame for more
information about encryption method registration. Encryption
should be done after compression. Whether or not setting this flag
requires the presence of a 'Data Length Indicator' depends on the
specific algorithm used.
@
0 Frame is not encrypted.
1 Frame is encrypted.
@
n - /Unsynchronisation/
This flag indicates whether or not unsynchronisation was applied
to this frame. See section 6 for details on unsynchronisation.
If this flag is set all data from the end of this header to the
end of this frame has been unsynchronised. Although desirable, the
presence of a 'Data Length Indicator' is not made mandatory by
unsynchronisation.
@
0 Frame has not been unsynchronised.
1 Frame has been unsyrchronised.
@
p - /Data length indicator/
This flag indicates that a data length indicator has been added to
the frame. The data length indicator is the value one would write
as the 'Frame length' if all of the frame format flags were
zeroed, represented as a 32 bit synchsafe integer.
@
0 There is no Data Length Indicator.
1 A data length Indicator has been added to the frame.
@
--}
type FormatFlags = Flags
groupPart = accessFlag 1 -- Grouping identity
compressed = accessFlag 2 -- Compression
encrypted = accessFlag 3 -- Encryption
unsychronised = accessFlag 4 -- Unsynchronisation
dataLengthId = accessFlag 5 -- Data length indicator
showFormatFlags form = if not (or $ form^.allFlags) then "" else
"\t\tFormat Flags:\n"++
(if form^.groupPart then "\t\t\t- frame is a part of group\n" else "")++
(if form^.compressed then "\t\t\t- frame is compressed\n" else "")++
(if form^.encrypted then "\t\t\t- frame is encrypted\n" else "")++
(if form^.unsychronised then "\t\t\t- frame is unsynchronised\n" else "")++
(if form^.dataLengthId then "\t\t\t- frame has data length indicator\n" else "")
--------------------------------------------------------------------------------------------
initFrame id = updateSize $ initID3Frame [ frHeader.>frID ^= id, frInfo ^= inf ]
where inf = case id of
"UFID" -> UFID "" []
"TXXX" -> TXXX 03 "" ""
"TCMP" -> TCMP False
('T':_)-> Text 03 ""
"WXXX" -> WXXX 03 "" ""
('W':_)-> URL ""
"MCDI" -> MCDI []
--ETCO -- { format :: Integer
-- , events :: [Event]} -- TODO
--MLLT -- TODO
--SYTC -- TODO
"USLT" -> USLT 03 "eng" "" ""
--SYLT enc lang timeFormat content descr
"COMM" -> COMM 03 "eng" "" ""
--RVA2 -- TODO
--EQU2 -- TODO
--RVRB -- TODO
"APIC" -> APIC 03 "" 00 "" []
--GEOB -- TODO
"PCNT" -> PCNT 00
"POPM" -> POPM "" 00 00
--RBUF -- TODO
--AENC -- TODO
--LINK -- TODO
--POSS -- TODO
"USER" -> USER 03 "eng" ""
--OWNE -- TODO
--COMR -- TODO
--ENCR -- TODO
--GRID -- TODO
"PRIV" -> PRIV "" []
--SIGN -- TODO
--ASPI -- TODO
_ -> Unknown ""