"""
Apple Quicktime Movie (file extension ".mov") parser.

Documents:
- Parsing and Writing QuickTime Files in Java (by Chris Adamson, 02/19/2003)
  http://www.onjava.com/pub/a/onjava/2003/02/19/qt_file_format.html
- QuickTime File Format (official technical reference)
  http://developer.apple.com/documentation/QuickTime/QTFF/qtff.pdf
- Apple QuickTime:
  http://wiki.multimedia.cx/index.php?title=Apple_QuickTime
- File type (ftyp):
  http://www.ftyps.com/
- MPEG4 standard
  http://neuron2.net/library/avc/c041828_ISO_IEC_14496-12_2005%28E%29.pdf

Author: Victor Stinner, Robert Xiao
Creation: 2 august 2006
"""

from hachoir_parser import Parser
from hachoir_parser.common.win32 import GUID
from hachoir_core.field import (ParserError, FieldSet, MissingField,
    Enum,
    Bit, NullBits, Bits, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, TimestampMac32,
    String, PascalString8, PascalString16, CString,
    RawBytes, NullBytes, PaddingBytes)
from hachoir_core.endian import BIG_ENDIAN
from hachoir_core.text_handler import textHandler, hexadecimal

from hachoir_core.tools import MAC_TIMESTAMP_T0, timedelta
def timestampMac64(value):
    if not isinstance(value, (float, int, long)):
        raise TypeError("an integer or float is required")
    return MAC_TIMESTAMP_T0 + timedelta(seconds=value)
from hachoir_core.field.timestamp import timestampFactory
TimestampMac64 = timestampFactory("TimestampMac64", timestampMac64, 64)

def fixedFloatFactory(name, int_bits, float_bits, doc):
    size = int_bits + float_bits
    class Float(FieldSet):
        static_size = size
        __doc__ = doc
        def createFields(self):
            yield Bits(self, "int_part", int_bits)
            yield Bits(self, "float_part", float_bits)
        def createValue(self):
            return self["int_part"].value + float(self["float_part"].value) / (1<<float_bits)
    klass = Float
    klass.__name__ = name
    return klass

QTFloat16 = fixedFloatFactory("QTFloat32", 8, 8, "8.8 fixed point number")
QTFloat32 = fixedFloatFactory("QTFloat32", 16, 16, "16.16 fixed point number")
QTFloat2_30 = fixedFloatFactory("QTFloat2_30", 2, 30, "2.30 fixed point number")

class AtomList(FieldSet):
    def createFields(self):
        while not self.eof:
            yield Atom(self, "atom[]")

class TrackHeader(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version (0 or 1)")
        yield NullBits(self, "flags", 20)
        yield Bit(self, "is_in_poster")
        yield Bit(self, "is_in_preview", "Is this track used when previewing the presentation?")
        yield Bit(self, "is_in_movie", "Is this track used in the presentation?")
        yield Bit(self, "is_enabled", "Is this track enabled?")

        if self['version'].value == 0:
            # 32-bit version
            yield TimestampMac32(self, "creation_date", "Creation time of this track")
            yield TimestampMac32(self, "lastmod_date", "Last modification time of this track")
            yield UInt32(self, "track_id", "Unique nonzero identifier of this track within the presentation")
            yield NullBytes(self, "reserved[]", 4)
            yield UInt32(self, "duration", "Length of track, in movie time-units")
        elif self['version'].value == 1:
            # 64-bit version
            yield TimestampMac64(self, "creation_date", "Creation time of this track")
            yield TimestampMac64(self, "lastmod_date", "Last modification time of this track")
            yield UInt32(self, "track_id", "Unique nonzero identifier of this track within the presentation")
            yield NullBytes(self, "reserved[]", 4)
            yield UInt64(self, "duration", "Length of track, in movie time-units")
        yield NullBytes(self, "reserved[]", 8)
        yield Int16(self, "video_layer", "Middle layer is 0; lower numbers are closer to the viewer")
        yield Int16(self, "alternate_group", "Group ID that this track belongs to (0=no group)")
        yield QTFloat16(self, "volume", "Track relative audio volume (1.0 = full)")
        yield NullBytes(self, "reserved[]", 2)
        yield QTFloat32(self, "geom_a", "Width scale")
        yield QTFloat32(self, "geom_b", "Width rotate")
        yield QTFloat2_30(self, "geom_u", "Width angle")
        yield QTFloat32(self, "geom_c", "Height rotate")
        yield QTFloat32(self, "geom_d", "Height scale")
        yield QTFloat2_30(self, "geom_v", "Height angle")
        yield QTFloat32(self, "geom_x", "Position X")
        yield QTFloat32(self, "geom_y", "Position Y")
        yield QTFloat2_30(self, "geom_w", "Divider scale")
        yield QTFloat32(self, "frame_size_width")
        yield QTFloat32(self, "frame_size_height")

class TrackReferenceType(FieldSet):
    def createFields(self):
        while not self.eof:
            yield UInt32(self, "track_id[]", "Referenced track ID")

class Handler(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield NullBits(self, "flags", 24)
        yield String(self, "creator", 4)
        yield String(self, "subtype", 4)
        yield String(self, "manufacturer", 4)
        yield UInt32(self, "res_flags")
        yield UInt32(self, "res_flags_mask")
        if self.root.is_mpeg4:
            yield CString(self, "name", charset="UTF-8")
        else:
            yield PascalString8(self, "name")

class LanguageCode(FieldSet):
    static_size = 16
    MAC_LANG = {
        0: 'English',
        1: 'French',
        2: 'German',
        3: 'Italian',
        4: 'Dutch',
        5: 'Swedish',
        6: 'Spanish',
        7: 'Danish',
        8: 'Portuguese',
        9: 'Norwegian',
        10: 'Hebrew',
        11: 'Japanese',
        12: 'Arabic',
        13: 'Finnish',
        14: 'Greek',
        15: 'Icelandic',
        16: 'Maltese',
        17: 'Turkish',
        18: 'Croatian',
        19: 'Traditional Chinese',
        20: 'Urdu',
        21: 'Hindi',
        22: 'Thai',
        23: 'Korean',
        24: 'Lithuanian',
        25: 'Polish',
        26: 'Hungarian',
        27: 'Estonian',
        28: 'Latvian',
        28: 'Lettish',
        29: 'Lappish',
        29: 'Saamisk',
        30: 'Faeroese',
        31: 'Farsi',
        32: 'Russian',
        33: 'Simplified Chinese',
        34: 'Flemish',
        35: 'Irish',
        36: 'Albanian',
        37: 'Romanian',
        38: 'Czech',
        39: 'Slovak',
        40: 'Slovenian',
        41: 'Yiddish',
        42: 'Serbian',
        43: 'Macedonian',
        44: 'Bulgarian',
        45: 'Ukrainian',
        46: 'Byelorussian',
        47: 'Uzbek',
        48: 'Kazakh',
        49: 'Azerbaijani',
        50: 'AzerbaijanAr',
        51: 'Armenian',
        52: 'Georgian',
        53: 'Moldavian',
        54: 'Kirghiz',
        55: 'Tajiki',
        56: 'Turkmen',
        57: 'Mongolian',
        58: 'MongolianCyr',
        59: 'Pashto',
        60: 'Kurdish',
        61: 'Kashmiri',
        62: 'Sindhi',
        63: 'Tibetan',
        64: 'Nepali',
        65: 'Sanskrit',
        66: 'Marathi',
        67: 'Bengali',
        68: 'Assamese',
        69: 'Gujarati',
        70: 'Punjabi',
        71: 'Oriya',
        72: 'Malayalam',
        73: 'Kannada',
        74: 'Tamil',
        75: 'Telugu',
        76: 'Sinhalese',
        77: 'Burmese',
        78: 'Khmer',
        79: 'Lao',
        80: 'Vietnamese',
        81: 'Indonesian',
        82: 'Tagalog',
        83: 'MalayRoman',
        84: 'MalayArabic',
        85: 'Amharic',
        86: 'Tigrinya',
        88: 'Somali',
        89: 'Swahili',
        90: 'Ruanda',
        91: 'Rundi',
        92: 'Chewa',
        93: 'Malagasy',
        94: 'Esperanto',
        128: 'Welsh',
        129: 'Basque',
        130: 'Catalan',
        131: 'Latin',
        132: 'Quechua',
        133: 'Guarani',
        134: 'Aymara',
        135: 'Tatar',
        136: 'Uighur',
        137: 'Dzongkha',
        138: 'JavaneseRom',
    }

    def fieldHandler(self, field):
        if field.value == 0:
            return ' '
        return chr(field.value + 0x60)
    def createFields(self):
        value = self.stream.readBits(self.absolute_address, 16, self.endian)
        if value < 1024:
            yield Enum(UInt16(self, "lang"), self.MAC_LANG)
        else:
            yield NullBits(self, "padding[]", 1)
            yield textHandler(Bits(self, "lang[0]", 5), self.fieldHandler)
            yield textHandler(Bits(self, "lang[1]", 5), self.fieldHandler)
            yield textHandler(Bits(self, "lang[2]", 5), self.fieldHandler)
    def createValue(self):
        if 'lang' in self:
            return self['lang'].display
        return self['lang[0]'].display + self['lang[1]'].display + self['lang[2]'].display

class MediaHeader(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version (0 or 1)")
        yield NullBits(self, "flags", 24)
        if self['version'].value == 0:
            # 32-bit version
            yield TimestampMac32(self, "creation_date", "Creation time of this media")
            yield TimestampMac32(self, "lastmod_date", "Last modification time of this media")
            yield UInt32(self, "time_scale", "Number of time-units per second")
            yield UInt32(self, "duration", "Length of media, in time-units")
        elif self['version'].value == 1:
            # 64-bit version
            yield TimestampMac64(self, "creation_date", "Creation time of this media")
            yield TimestampMac64(self, "lastmod_date", "Last modification time of this media")
            yield UInt32(self, "time_scale", "Number of time-units per second")
            yield UInt64(self, "duration", "Length of media, in time-units")
        yield LanguageCode(self, "language")
        yield Int16(self, "quality")

class VideoMediaHeader(FieldSet):
    GRAPHICSMODE = {
        0: ('Copy', "Copy the source image over the destination"),
        0x20: ('Blend', "Blend of source and destination; blending factor is controlled by op color"),
        0x24: ('Transparent', "Replace destination pixel with source pixel if the source pixel is not the op color"),
        0x40: ('Dither copy', "Dither image if necessary, else copy"),
        0x100: ('Straight alpha', "Blend of source and destination; blending factor is controlled by alpha channel"),
        0x101: ('Premul white alpha', "Remove white from each pixel and blend"),
        0x102: ('Premul black alpha', "Remove black from each pixel and blend"),
        0x103: ('Composition', "Track drawn offscreen and dither copied onto screen"),
        0x104: ('Straight alpha blend', "Blend of source and destination; blending factor is controlled by combining alpha channel and op color")
    }

    def graphicsDisplay(self, field):
        if field.value in self.GRAPHICSMODE:
            return self.GRAPHICSMODE[field.value][0]
        return hex(field.value)

    def graphicsDescription(self, field):
        if field.value in self.GRAPHICSMODE:
            return self.GRAPHICSMODE[field.value][1]
        return ""

    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield Bits(self, "flags", 24, "Flags (=1)")
        graphics = UInt16(self, "graphicsmode")
        graphics.createDisplay = lambda:self.graphicsDisplay(graphics)
        graphics.createDescription = lambda:self.graphicsDescription(graphics)
        yield graphics
        yield UInt16(self, "op_red", "Red value for graphics mode")
        yield UInt16(self, "op_green", "Green value for graphics mode")
        yield UInt16(self, "op_blue", "Blue value for graphics mode")

class SoundMediaHeader(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield NullBits(self, "flags", 24)
        yield QTFloat16(self, "balance")
        yield UInt16(self, "reserved[]")

class HintMediaHeader(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield NullBits(self, "flags", 24)
        yield UInt16(self, "max_pdu_size")
        yield UInt16(self, "avg_pdu_size")
        yield UInt32(self, "max_bit_rate")
        yield UInt32(self, "avg_bit_rate")
        yield UInt32(self, "reserved[]")

class DataEntryUrl(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield NullBits(self, "flags", 23)
        yield Bit(self, "is_same_file", "Is the reference to this file?")
        if not self['is_same_file'].value:
            yield CString(self, "location")

class DataEntryUrn(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield NullBits(self, "flags", 23)
        yield Bit(self, "is_same_file", "Is the reference to this file?")
        if not self['is_same_file'].value:
            yield CString(self, "name")
            yield CString(self, "location")

class DataReference(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count")
        for i in xrange(self['count'].value):
            yield Atom(self, "atom[]")

class EditList(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version (0 or 1)")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count")
        version = self['version'].value
        if version == 0:
            UInt, Int = UInt32, Int32
        elif version == 1:
            UInt, Int = UInt64, Int64
        else:
            raise ParserError("elst version %d not supported"%version)
        for i in xrange(self['count'].value):
            yield UInt(self, "duration[]", "Duration of this edit segment")
            yield Int(self, "time[]", "Starting time of this edit segment within the media (-1 = empty edit)")
            yield QTFloat32(self, "play_speed[]", "Playback rate (0 = dwell edit, 1 = normal playback)")

class Load(FieldSet):
    def createFields(self):
        yield UInt32(self, "start")
        yield UInt32(self, "length")
        yield UInt32(self, "flags") # PreloadAlways = 1 or TrackEnabledPreload = 2
        yield UInt32(self, "hints") # KeepInBuffer = 0x00000004; HighQuality = 0x00000100; SingleFieldVideo = 0x00100000

class MovieHeader(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version (0 or 1)")
        yield NullBits(self, "flags", 24)
        if self['version'].value == 0:
            # 32-bit version
            yield TimestampMac32(self, "creation_date", "Creation time of this presentation")
            yield TimestampMac32(self, "lastmod_date", "Last modification time of this presentation")
            yield UInt32(self, "time_scale", "Number of time-units per second")
            yield UInt32(self, "duration", "Length of presentation, in time-units")
        elif self['version'].value == 1:
            # 64-bit version
            yield TimestampMac64(self, "creation_date", "Creation time of this presentation")
            yield TimestampMac64(self, "lastmod_date", "Last modification time of this presentation")
            yield UInt32(self, "time_scale", "Number of time-units per second")
            yield UInt64(self, "duration", "Length of presentation, in time-units")
        yield QTFloat32(self, "play_speed", "Preferred playback speed (1.0 = normal)")
        yield QTFloat16(self, "volume", "Preferred playback volume (1.0 = full)")
        yield NullBytes(self, "reserved[]", 10)
        yield QTFloat32(self, "geom_a", "Width scale")
        yield QTFloat32(self, "geom_b", "Width rotate")
        yield QTFloat2_30(self, "geom_u", "Width angle")
        yield QTFloat32(self, "geom_c", "Height rotate")
        yield QTFloat32(self, "geom_d", "Height scale")
        yield QTFloat2_30(self, "geom_v", "Height angle")
        yield QTFloat32(self, "geom_x", "Position X")
        yield QTFloat32(self, "geom_y", "Position Y")
        yield QTFloat2_30(self, "geom_w", "Divider scale")
        yield UInt32(self, "preview_start")
        yield UInt32(self, "preview_length")
        yield UInt32(self, "still_poster")
        yield UInt32(self, "sel_start")
        yield UInt32(self, "sel_length")
        yield UInt32(self, "current_time")
        yield UInt32(self, "next_track_ID", "Value to use as the track ID for the next track added")

class FileType(FieldSet):
    def createFields(self):
        yield String(self, "brand", 4, "Major brand")
        yield UInt32(self, "version", "Version")
        while not self.eof:
            yield String(self, "compat_brand[]", 4, "Compatible brand")

class MovieFragmentHeader(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "sequence_number")

class TrackFragmentRandomAccess(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "track_id")
        yield NullBits(self, "reserved", 26)
        yield Bits(self, "length_size_of_traf_num", 2)
        yield Bits(self, "length_size_of_trun_num", 2)
        yield Bits(self, "length_size_of_sample_num", 2)
        yield UInt32(self, "number_of_entry")
        for i in xrange(self['number_of_entry'].value):
            if self['version'].value == 1:
                yield UInt64(self, "time[%i]" % i)
                yield UInt64(self, "moof_offset[%i]" %i)
            else:
                yield UInt32(self, "time[%i]" %i)
                yield UInt32(self, "moof_offset[%i]" %i)

            if self['length_size_of_traf_num'].value == 3:
                yield UInt64(self, "traf_number[%i]" %i) 
            elif self['length_size_of_traf_num'].value == 2:
                yield UInt32(self, "traf_number[%i]" %i) 
            elif self['length_size_of_traf_num'].value == 1:
                yield UInt16(self, "traf_number[%i]" %i) 
            else:
                yield UInt8(self, "traf_number[%i]" %i) 

            if self['length_size_of_trun_num'].value == 3:
                yield UInt64(self, "trun_number[%i]" %i) 
            elif self['length_size_of_trun_num'].value == 2:
                yield UInt32(self, "trun_number[%i]" %i) 
            elif self['length_size_of_trun_num'].value == 1:
                yield UInt16(self, "trun_number[%i]" %i) 
            else:
                yield UInt8(self, "trun_number[%i]" %i) 

            if self['length_size_of_sample_num'].value == 3:
                yield UInt64(self, "sample_number[%i]" %i) 
            elif self['length_size_of_sample_num'].value == 2:
                yield UInt32(self, "sample_number[%i]" %i) 
            elif self['length_size_of_sample_num'].value == 1:
                yield UInt16(self, "sample_number[%i]" %i) 
            else:
                yield UInt8(self, "sample_number[%i]" %i) 

class MovieFragmentRandomAccessOffset(FieldSet):
    def createFields(self):
        yield UInt8(self, "version", "Version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "size")

def findHandler(self):
    ''' find the handler corresponding to this fieldset '''
    while self:
        if self.name in ('media', 'tags'):
            break
        self = self.parent
    else:
        return None
    for atom in self:
        if atom['tag'].value == 'hdlr':
            return atom['hdlr']
    return None

class METATAG(FieldSet):
    def createFields(self):
        yield UInt8(self, "unk[]", "0x80 or 0x00")
        yield PascalString16(self, "tag_name", charset='UTF-8')
        yield UInt16(self, "unk[]", "0x0001")
        yield UInt16(self, "unk[]", "0x0000")
        yield PascalString16(self, "tag_value", charset='UTF-8')

class META(FieldSet):
    def createFields(self):
        # This tag has too many variant forms.
        if '/tags/' in self.path:
            yield UInt32(self, "count")
            for i in xrange(self['count'].value):
                yield METATAG(self, "tag[]")
        elif self.stream.readBits(self.absolute_address, 32, self.endian) == 0:
            yield UInt8(self, "version")
            yield Bits(self, "flags", 24)
            yield AtomList(self, "tags")
        else:
            yield AtomList(self, "tags")

class Item(FieldSet):
    def createFields(self):
        yield UInt32(self, "size")
        yield UInt32(self, "index")
        yield Atom(self, "value")

class KeyList(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count")
        for i in xrange(self['count'].value):
            yield Atom(self, "key[]")

class ItemList(FieldSet):
    def createFields(self):
        handler = findHandler(self)
        if handler is None:
            raise ParserError("ilst couldn't find metadata handler")
        if handler['subtype'].value == 'mdir':
            while not self.eof:
                yield Atom(self, "atom[]")
        elif handler['subtype'].value == 'mdta':
            while not self.eof:
                yield Item(self, "item[]")

class NeroChapters(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "unknown")
        yield UInt8(self, "count", description="Number of chapters")
        for i in xrange(self['count'].value):
            yield UInt64(self, "chapter_start[]")
            yield PascalString8(self, "chapter_name[]", charset='UTF-8')

class SampleDecodeTimeTable(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count", description="Total entries in sample time table")
        for i in xrange(self['count'].value):
            yield UInt32(self, "sample_count[]", "Number of consecutive samples with this delta")
            yield UInt32(self, "sample_delta[]", "Decode time delta since last sample, in time-units")

class SampleCompositionTimeTable(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count", description="Total entries in sample time table")
        for i in xrange(self['count'].value):
            yield UInt32(self, "sample_count[]", "Number of consecutive samples with this offset")
            yield UInt32(self, "sample_offset[]", "Difference between decode time and composition time of this sample, in time-units")

class ChunkOffsetTable(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count", description="Total entries in offset table")
        for i in xrange(self['count'].value):
            yield UInt32(self, "chunk_offset[]")

class ChunkOffsetTable64(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count", description="Total entries in offset table")
        for i in xrange(self['count'].value):
            yield UInt64(self, "chunk_offset[]")

class SampleEntry(FieldSet):
    def createFields(self):
        yield UInt32(self, "size")
        yield RawBytes(self, "format", 4, "Data Format (codec)")
        yield NullBytes(self, "reserved[]", 6, "Reserved")
        yield UInt16(self, "data_reference_index")
        handler = findHandler(self)
        if not handler:
            raise ParserError("stsd couldn't find track handler")
        if handler['subtype'].value == 'soun':
            # Audio sample entry
            yield NullBytes(self, "reserved[]", 8)
            yield UInt16(self, "channels", "Number of audio channels")
            yield UInt16(self, "samplesize", "Sample size in bits")
            yield UInt16(self, "unknown")
            yield NullBytes(self, "reserved[]", 2)
            yield QTFloat32(self, "samplerate", "Sample rate in Hz")
        elif handler['subtype'].value == 'vide':
            # Video sample entry
            yield UInt16(self, "version")
            yield UInt16(self, "revision_level")
            yield RawBytes(self, "vendor_id", 4)
            yield UInt32(self, "temporal_quality")
            yield UInt32(self, "spatial_quality")
            yield UInt16(self, "width", "Width (pixels)")
            yield UInt16(self, "height", "Height (pixels)")
            yield QTFloat32(self, "horizontal_resolution", "Horizontal resolution in DPI")
            yield QTFloat32(self, "vertical resolution", "Vertical resolution in DPI")
            yield UInt32(self, "data_size")
            yield UInt16(self, "frame_count")
            yield UInt8(self, "compressor_name_length")
            yield String(self, "compressor_name", 31, strip='\0')
            yield UInt16(self, "depth", "Bit depth of image")
            yield Int16(self, "unknown")
        elif handler['subtype'].value == 'hint':
            # Hint sample entry
            pass
        
        size = self['size'].value - self.current_size//8
        if size > 0:
            yield RawBytes(self, "extra_data", size)

class SampleDescription(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count", description="Total entries in table")
        for i in xrange(self['count'].value):
            yield SampleEntry(self, "sample_entry[]")

class SyncSampleTable(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count", description="Number of sync samples")
        for i in xrange(self['count'].value):
            yield UInt32(self, "sample_number[]")

class SampleSizeTable(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "uniform_size", description="Uniform size of each sample (0 if non-uniform)")
        yield UInt32(self, "count", description="Number of samples")
        if self['uniform_size'].value == 0:
            for i in xrange(self['count'].value):
                yield UInt32(self, "sample_size[]")

class CompactSampleSizeTable(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield NullBits(self, "reserved[]", 24)
        yield UInt8(self, "field_size", "Size of each entry in this table, in bits")
        yield UInt32(self, "count", description="Number of samples")
        bitsize = self['field_size'].value
        for i in xrange(self['count'].value):
            yield Bits(self, "sample_size[]", bitsize)
        if self.current_size % 8 != 0:
            yield NullBits(self, "padding[]", 8 - (self.current_size % 8))

class SampleToChunkTable(FieldSet):
    def createFields(self):
        yield UInt8(self, "version")
        yield NullBits(self, "flags", 24)
        yield UInt32(self, "count", description="Number of samples")
        for i in xrange(self['count'].value):
            yield UInt32(self, "first_chunk[]")
            yield UInt32(self, "samples_per_chunk[]")
            yield UInt32(self, "sample_description_index[]")

class Atom(FieldSet):
    tag_info = {
        "ftyp": (FileType, "file_type", "File type and compatibility"),
        # pdin: progressive download information
        # pnot: movie preview (old QT spec)
        "moov": (AtomList, "movie", "Container for all metadata"),
            "mvhd": (MovieHeader, "movie_hdr", "Movie header, overall declarations"),
            # clip: movie clipping (old QT spec)
                # crgn: movie clipping region (old QT spec)
            "trak": (AtomList, "track", "Container for an individual track or stream"),
                "tkhd": (TrackHeader, "track_hdr", "Track header, overall information about the track"),
                # matt: track matte (old QT spec)
                    # kmat: compressed matte (old QT spec)
                "tref": (AtomList, "tref", "Track reference container"),
                    "hint": (TrackReferenceType, "hint", "Original media track(s) for this hint track"),
                    "cdsc": (TrackReferenceType, "cdsc", "Reference to track described by this track"),
                "edts": (AtomList, "edts", "Edit list container"),
                    "elst": (EditList, "elst", "Edit list"),
                "load": (Load, "load", "Track loading settings (old QT spec)"),
                # imap: Track input map (old QT spec)
                "mdia": (AtomList, "media", "Container for the media information in a track"),
                    "mdhd": (MediaHeader, "media_hdr", "Media header, overall information about the media"),
                    "hdlr": (Handler, "hdlr", "Handler, declares the media or metadata (handler) type"),
                    "minf": (AtomList, "minf", "Media information container"),
                        "vmhd": (VideoMediaHeader, "vmhd", "Video media header, overall information (video track only)"),
                        "smhd": (SoundMediaHeader, "smhd", "Sound media header, overall information (sound track only)"),
                        "hmhd": (HintMediaHeader, "hmhd", "Hint media header, overall information (hint track only)"),
                        # nmhd: Null media header, overall information (some tracks only) (unparsed)
                        "dinf": (AtomList, "dinf", "Data information, container"),
                            "dref": (DataReference, "dref", "Data reference, declares source(s) of media data in track"),
                                "url ": (DataEntryUrl, "url", "URL data reference"),
                                "urn ": (DataEntryUrn, "urn", "URN data reference"),
                        "stbl": (AtomList, "stbl", "Sample table, container for the time/space map"),
                            "stsd": (SampleDescription, "stsd", "Sample descriptions (codec types, initialization etc.)"),
                            "stts": (SampleDecodeTimeTable, "stts", "decoding time-to-sample delta table"),
                            "ctts": (SampleCompositionTimeTable, "ctts", "composition time-to-sample offset table"),
                            "stsc": (SampleToChunkTable, "stsc", "sample-to-chunk, partial data-offset information"),
                            "stsz": (SampleSizeTable, "stsz", "Sample size table (framing)"),
                            "stz2": (CompactSampleSizeTable, "stz2", "Compact sample size table (framing)"),
                            "stco": (ChunkOffsetTable, "stco", "Chunk offset, partial data-offset information"),
                            "co64": (ChunkOffsetTable64, "co64", "64-bit chunk offset"),
                            "stss": (SyncSampleTable, "stss", "Sync sample table (random access points)"),
                            # stsh: shadow sync sample table
                            # padb: sample padding bits
                            # stdp: sample degradation priority
                            # sdtp: independent and disposable samples
                            # sbgp: sample-to-group
                            # sgpd: sample group description
                            # subs: sub-sample information
            # ctab color table (old QT spec)
            # mvex: movie extends
                # mehd: movie extends header
                # trex: track extends defaults
            # ipmc: IPMP control
        "moof": (AtomList, "moof", "movie fragment"),
            "mfhd": (MovieFragmentHeader, "mfhd", "movie fragment header"),
            # traf: track fragment
                # tfhd: track fragment header
                # trun: track fragment run
                # sdtp: independent and disposable samples
                # sbgp: sample-to-group
                # subs: sub-sample information
        "mfra": (AtomList, "mfra", "movie fragment random access"),
            "tfra": (TrackFragmentRandomAccess, "tfra", "track fragment random access"),
            "mfro": (MovieFragmentRandomAccessOffset, "mfro", "movie fragment random access offset"),
        # mdat: media data container
        # free: free space (unparsed)
        # skip: free space (unparsed)
        "udta": (AtomList, "udta", "User data"),
        "meta": (META, "meta", "File metadata"),
            "keys": (KeyList, "keys", "Metadata keys"),
            ## hdlr
            ## dinf
                ## dref: data reference, declares source(s) of metadata items
            ## ipmc: IPMP control
            # iloc: item location
            # ipro: item protection
                # sinf: protection scheme information
                    # frma: original format
                    # imif: IPMP information
                    # schm: scheme type
                    # schi: scheme information
            # iinf: item information
            # xml : XML container
            # bxml: binary XML container
            # pitm: primary item reference
        ## other tags
        "ilst": (ItemList, "ilst", "Item list"),
            "trkn": (AtomList, "trkn", "Metadata: Track number"),
            "disk": (AtomList, "disk", "Metadata: Disk number"),
            "tmpo": (AtomList, "tempo", "Metadata: Tempo"),
            "cpil": (AtomList, "cpil", "Metadata: Compilation"),
            "gnre": (AtomList, "gnre", "Metadata: Genre"),
            "\xa9cpy": (AtomList, "copyright", "Metadata: Copyright statement"),
            "\xa9day": (AtomList, "date", "Metadata: Date of content creation"),
            "\xa9dir": (AtomList, "director", "Metadata: Movie director"),
            "\xa9ed1": (AtomList, "edit1", "Metadata: Edit date and description (1)"),
            "\xa9ed2": (AtomList, "edit2", "Metadata: Edit date and description (2)"),
            "\xa9ed3": (AtomList, "edit3", "Metadata: Edit date and description (3)"),
            "\xa9ed4": (AtomList, "edit4", "Metadata: Edit date and description (4)"),
            "\xa9ed5": (AtomList, "edit5", "Metadata: Edit date and description (5)"),
            "\xa9ed6": (AtomList, "edit6", "Metadata: Edit date and description (6)"),
            "\xa9ed7": (AtomList, "edit7", "Metadata: Edit date and description (7)"),
            "\xa9ed8": (AtomList, "edit8", "Metadata: Edit date and description (8)"),
            "\xa9ed9": (AtomList, "edit9", "Metadata: Edit date and description (9)"),
            "\xa9fmt": (AtomList, "format", "Metadata: Movie format (CGI, digitized, etc.)"),
            "\xa9inf": (AtomList, "info", "Metadata: Information about the movie"),
            "\xa9prd": (AtomList, "producer", "Metadata: Movie producer"),
            "\xa9prf": (AtomList, "performers", "Metadata: Performer names"),
            "\xa9req": (AtomList, "requirements", "Metadata: Special hardware and software requirements"),
            "\xa9src": (AtomList, "source", "Metadata: Credits for those who provided movie source content"),
            "\xa9nam": (AtomList, "name", "Metadata: Name of song or video"),
            "\xa9des": (AtomList, "description", "Metadata: File description"),
            "\xa9cmt": (AtomList, "comment", "Metadata: General comment"),
            "\xa9alb": (AtomList, "album", "Metadata: Album name"),
            "\xa9gen": (AtomList, "genre", "Metadata: Custom genre"),
            "\xa9ART": (AtomList, "artist", "Metadata: Artist name"),
            "\xa9too": (AtomList, "encoder", "Metadata: Encoder"),
            "\xa9wrt": (AtomList, "writer", "Metadata: Writer"),
            "covr": (AtomList, "cover", "Metadata: Cover art"),
            "----": (AtomList, "misc", "Metadata: Miscellaneous"),
        "tags": (AtomList, "tags", "File tags"),
        "tseg": (AtomList, "tseg", "tseg"),
        "chpl": (NeroChapters, "chpl", "Nero chapter data"),
    }
    tag_handler = [ item[0] for item in tag_info ]
    tag_desc = [ item[1] for item in tag_info ]

    def createFields(self):
        yield UInt32(self, "size")
        yield RawBytes(self, "tag", 4)
        size = self["size"].value
        if size == 1:
            # 64-bit size
            yield UInt64(self, "size64")
            size = self["size64"].value - 16
        elif size == 0:
            # Unbounded atom
            if self._size is None:
                size = (self.parent.size - self.parent.current_size) / 8 - 8
            else:
                size = (self.size - self.current_size) / 8
        else:
            size = size - 8
        if self['tag'].value == 'uuid':
            yield GUID(self, "usertag")
            tag = self["usertag"].value
            size -= 16
        else:
            tag = self["tag"].value
        if size > 0:
            if tag in self.tag_info:
                handler, name, desc = self.tag_info[tag]
                yield handler(self, name, desc, size=size*8)
            else:
                yield RawBytes(self, "data", size)

    def createDescription(self):
        if self["tag"].value == "uuid":
            return "Atom: uuid: "+self["usertag"].value
        return "Atom: %s" % self["tag"].value

class MovFile(Parser):
    PARSER_TAGS = {
        "id": "mov",
        "category": "video",
        "file_ext": ("mov", "qt", "mp4", "m4v", "m4a", "m4p", "m4b"),
        "mime": (u"video/quicktime", u'video/mp4'),
        "min_size": 8*8,
        "magic": (("moov", 4*8),),
        "description": "Apple QuickTime movie"
    }
    BRANDS = {
        # File type brand => MIME type
        'mp41': u'video/mp4',
        'mp42': u'video/mp4',
        'avc1': u'video/mp4',
        'isom': u'video/mp4',
        'iso2': u'video/mp4',
    }
    endian = BIG_ENDIAN

    def __init__(self, *args, **kw):
        Parser.__init__(self, *args, **kw)

    is_mpeg4 = property(lambda self:self.mime_type==u'video/mp4')

    def validate(self):
        # TODO: Write better code, erk!
        size = self.stream.readBits(0, 32, self.endian)
        if size < 8:
            return "Invalid first atom size"
        tag = self.stream.readBytes(4*8, 4)
        return tag in ("ftyp", "moov", "free")

    def createFields(self):
        while not self.eof:
            yield Atom(self, "atom[]")

    def createMimeType(self):
        first = self[0]
        try:
            # Read brands in the file type
            if first['tag'].value != "ftyp":
                return None
            file_type = first["file_type"]
            brand = file_type["brand"].value
            if brand in self.BRANDS:
                return self.BRANDS[brand]
            for field in file_type.array("compat_brand"):
                brand = field.value
                if brand in self.BRANDS:
                    return self.BRANDS[brand]
        except MissingField:
            pass
        return u'video/quicktime'

