#------------------------------------------------------------------------------
# File:         QuickTime.pm
#
# Description:  Read QuickTime, MP4 and M4A meta information
#
# Revisions:    10/04/2005 - P. Harvey Created
#               12/19/2005 - P. Harvey Added MP4 support
#               09/22/2006 - P. Harvey Added M4A support
#
# References:   1) http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html
#               2) http://search.cpan.org/dist/MP4-Info-1.04/
#               3) http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
#               4) http://wiki.multimedia.cx/index.php?title=Apple_QuickTime
#               5) ISO 14496-12 (http://neuron2.net/library/avc/c041828_ISO_IEC_14496-12_2005(E).pdf)
#               6) ISO 14496-16 (http://www.iec-normen.de/previewpdf/info_isoiec14496-16%7Bed2.0%7Den.pdf)
#               7) http://atomicparsley.sourceforge.net/mpeg-4files.html
#               8) http://wiki.multimedia.cx/index.php?title=QuickTime_container
#               9) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008)
#               10) http://code.google.com/p/mp4v2/wiki/iTunesMetadata
#               11) http://www.canieti.com.mx/assets/files/1011/IEC_100_1384_DC.pdf
#------------------------------------------------------------------------------

package Image::ExifTool::QuickTime;

use strict;
use vars qw($VERSION);
use Image::ExifTool qw(:DataAccess :Utils);
use Image::ExifTool::Exif;

$VERSION = '1.35';

sub FixWrongFormat($);
sub ProcessMOV($$;$);
sub ProcessKeys($$$);
sub ProcessMetaData($$$);

# information for time/date-based tags (time zero is Jan 1, 1904)
my %timeInfo = (
    # Note: This value will be in UTC if generated by a system that is aware of the time zone
    ValueConv => 'ConvertUnixTime($val - ((66 * 365 + 17) * 24 * 3600))',
    PrintConv => '$self->ConvertDateTime($val)',
    # (can't put Groups here because they aren't constant!)
);
# information for duration tags
my %durationInfo = (
    ValueConv => '$$self{TimeScale} ? $val / $$self{TimeScale} : $val',
    PrintConv => '$$self{TimeScale} ? ConvertDuration($val) : $val',
);

# 4-character Vendor ID codes (ref PH)
my %vendorID = (
    appl => 'Apple',
    FFMP => 'FFmpeg',
    kdak => 'Kodak',
    KMPI => 'Konica-Minolta',
    mino => 'Minolta',
    niko => 'Nikon',
    NIKO => 'Nikon',
    olym => 'Olympus',
    pana => 'Panasonic',
    pent => 'Pentax',
    sany => 'Sanyo',
    # have also seen:
    # VendorID - 'SMI ', fe20 (Olympus FE200), pr01, 
    # HandlerVendorID - ZORA, pr01, 'GIC '
);

# MIME types for all entries in the ftypLookup with file extensions
# (defaults to 'video/mp4' if not found in this lookup)
my %mimeLookup = (
   '3G2' => 'video/3gpp2',
   '3GP' => 'video/3gpp',
    DVB  => 'video/vnd.dvb.file',
    F4A  => 'audio/mp4',
    F4B  => 'audio/mp4',
    JP2  => 'image/jp2',
    JPM  => 'image/jpm',
    JPX  => 'image/jpx',
    M4A  => 'audio/mp4',
    M4B  => 'audio/mp4',
    M4P  => 'audio/mp4',
    M4V  => 'video/x-m4v',
    MOV  => 'video/quicktime',
    MQV  => 'video/quicktime',
);

my %graphicsMode = (
    # (ref http://homepage.mac.com/vanhoek/MovieGuts%20docs/64.html)
    0x00 => 'srcCopy',
    0x01 => 'srcOr',
    0x02 => 'srcXor',
    0x03 => 'srcBic',
    0x04 => 'notSrcCopy',
    0x05 => 'notSrcOr',
    0x06 => 'notSrcXor',
    0x07 => 'notSrcBic',
    0x08 => 'patCopy',
    0x09 => 'patOr',
    0x0a => 'patXor',
    0x0b => 'patBic',
    0x0c => 'notPatCopy',
    0x0d => 'notPatOr',
    0x0e => 'notPatXor',
    0x0f => 'notPatBic',
    0x20 => 'blend',
    0x21 => 'addPin',
    0x22 => 'addOver',
    0x23 => 'subPin',
    0x24 => 'transparent',
    0x25 => 'addMax',
    0x26 => 'subOver',
    0x27 => 'addMin',
    0x31 => 'grayishTextOr',
    0x32 => 'hilite',
    0x40 => 'ditherCopy',
    # the following ref ISO/IEC 15444-3
    0x100 => 'Alpha',
    0x101 => 'White Alpha',
    0x102 => 'Pre-multiplied Black Alpha',
    0x110 => 'Component Alpha',
);

# look up file type from ftyp atom type, with MIME type in comment if known
# (ref http://www.ftyps.com/)
my %ftypLookup = (
    '3g2a' => '3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-0 V1.0', # video/3gpp2
    '3g2b' => '3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-A V1.0.0', # video/3gpp2
    '3g2c' => '3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-B v1.0', # video/3gpp2
    '3ge6' => '3GPP (.3GP) Release 6 MBMS Extended Presentations', # video/3gpp
    '3ge7' => '3GPP (.3GP) Release 7 MBMS Extended Presentations', # video/3gpp
    '3gg6' => '3GPP Release 6 General Profile', # video/3gpp
    '3gp1' => '3GPP Media (.3GP) Release 1 (probably non-existent)', # video/3gpp
    '3gp2' => '3GPP Media (.3GP) Release 2 (probably non-existent)', # video/3gpp
    '3gp3' => '3GPP Media (.3GP) Release 3 (probably non-existent)', # video/3gpp
    '3gp4' => '3GPP Media (.3GP) Release 4', # video/3gpp
    '3gp5' => '3GPP Media (.3GP) Release 5', # video/3gpp
    '3gp6' => '3GPP Media (.3GP) Release 6 Basic Profile', # video/3gpp
    '3gp6' => '3GPP Media (.3GP) Release 6 Progressive Download', # video/3gpp
    '3gp6' => '3GPP Media (.3GP) Release 6 Streaming Servers', # video/3gpp
    '3gs7' => '3GPP Media (.3GP) Release 7 Streaming Servers', # video/3gpp
    'avc1' => 'MP4 Base w/ AVC ext [ISO 14496-12:2005]', # video/mp4
    'CAEP' => 'Canon Digital Camera',
    'caqv' => 'Casio Digital Camera',
    'CDes' => 'Convergent Design',
    'da0a' => 'DMB MAF w/ MPEG Layer II aud, MOT slides, DLS, JPG/PNG/MNG images',
    'da0b' => 'DMB MAF, extending DA0A, with 3GPP timed text, DID, TVA, REL, IPMP',
    'da1a' => 'DMB MAF audio with ER-BSAC audio, JPG/PNG/MNG images',
    'da1b' => 'DMB MAF, extending da1a, with 3GPP timed text, DID, TVA, REL, IPMP',
    'da2a' => 'DMB MAF aud w/ HE-AAC v2 aud, MOT slides, DLS, JPG/PNG/MNG images',
    'da2b' => 'DMB MAF, extending da2a, with 3GPP timed text, DID, TVA, REL, IPMP',
    'da3a' => 'DMB MAF aud with HE-AAC aud, JPG/PNG/MNG images',
    'da3b' => 'DMB MAF, extending da3a w/ BIFS, 3GPP timed text, DID, TVA, REL, IPMP',
    'dmb1' => 'DMB MAF supporting all the components defined in the specification',
    'dmpf' => 'Digital Media Project', # various
    'drc1' => 'Dirac (wavelet compression), encapsulated in ISO base media (MP4)',
    'dv1a' => 'DMB MAF vid w/ AVC vid, ER-BSAC aud, BIFS, JPG/PNG/MNG images, TS',
    'dv1b' => 'DMB MAF, extending dv1a, with 3GPP timed text, DID, TVA, REL, IPMP',
    'dv2a' => 'DMB MAF vid w/ AVC vid, HE-AAC v2 aud, BIFS, JPG/PNG/MNG images, TS',
    'dv2b' => 'DMB MAF, extending dv2a, with 3GPP timed text, DID, TVA, REL, IPMP',
    'dv3a' => 'DMB MAF vid w/ AVC vid, HE-AAC aud, BIFS, JPG/PNG/MNG images, TS',
    'dv3b' => 'DMB MAF, extending dv3a, with 3GPP timed text, DID, TVA, REL, IPMP',
    'dvr1' => 'DVB (.DVB) over RTP', # video/vnd.dvb.file
    'dvt1' => 'DVB (.DVB) over MPEG-2 Transport Stream', # video/vnd.dvb.file
    'F4A ' => 'Audio for Adobe Flash Player 9+ (.F4A)', # audio/mp4
    'F4B ' => 'Audio Book for Adobe Flash Player 9+ (.F4B)', # audio/mp4
    'F4P ' => 'Protected Video for Adobe Flash Player 9+ (.F4P)', # video/mp4
    'F4V ' => 'Video for Adobe Flash Player 9+ (.F4V)', # video/mp4
    'isc2' => 'ISMACryp 2.0 Encrypted File', # ?/enc-isoff-generic
    'iso2' => 'MP4 Base Media v2 [ISO 14496-12:2005]', # video/mp4
    'isom' => 'MP4  Base Media v1 [IS0 14496-12:2003]', # video/mp4
    'JP2 ' => 'JPEG 2000 Image (.JP2) [ISO 15444-1 ?]', # image/jp2
    'JP20' => 'Unknown, from GPAC samples (prob non-existent)',
    'jpm ' => 'JPEG 2000 Compound Image (.JPM) [ISO 15444-6]', # image/jpm
    'jpx ' => 'JPEG 2000 with extensions (.JPX) [ISO 15444-2]', # image/jpx
    'KDDI' => '3GPP2 EZmovie for KDDI 3G cellphones', # video/3gpp2
    'M4A ' => 'Apple iTunes AAC-LC (.M4A) Audio', # audio/x-m4a
    'M4B ' => 'Apple iTunes AAC-LC (.M4B) Audio Book', # audio/mp4
    'M4P ' => 'Apple iTunes AAC-LC (.M4P) AES Protected Audio', # audio/mp4
    'M4V ' => 'Apple iTunes Video (.M4V) Video', # video/x-m4v
    'M4VH' => 'Apple TV (.M4V)', # video/x-m4v
    'M4VP' => 'Apple iPhone (.M4V)', # video/x-m4v
    'mj2s' => 'Motion JPEG 2000 [ISO 15444-3] Simple Profile', # video/mj2
    'mjp2' => 'Motion JPEG 2000 [ISO 15444-3] General Profile', # video/mj2
    'mmp4' => 'MPEG-4/3GPP Mobile Profile (.MP4/3GP) (for NTT)', # video/mp4
    'mp21' => 'MPEG-21 [ISO/IEC 21000-9]', # various
    'mp41' => 'MP4 v1 [ISO 14496-1:ch13]', # video/mp4
    'mp42' => 'MP4 v2 [ISO 14496-14]', # video/mp4
    'mp71' => 'MP4 w/ MPEG-7 Metadata [per ISO 14496-12]', # various
    'MPPI' => 'Photo Player, MAF [ISO/IEC 23000-3]', # various
    'mqt ' => 'Sony / Mobile QuickTime (.MQV) US Patent 7,477,830 (Sony Corp)', # video/quicktime
    'MSNV' => 'MPEG-4 (.MP4) for SonyPSP', # audio/mp4
    'NDAS' => 'MP4 v2 [ISO 14496-14] Nero Digital AAC Audio', # audio/mp4
    'NDSC' => 'MPEG-4 (.MP4) Nero Cinema Profile', # video/mp4
    'NDSH' => 'MPEG-4 (.MP4) Nero HDTV Profile', # video/mp4
    'NDSM' => 'MPEG-4 (.MP4) Nero Mobile Profile', # video/mp4
    'NDSP' => 'MPEG-4 (.MP4) Nero Portable Profile', # video/mp4
    'NDSS' => 'MPEG-4 (.MP4) Nero Standard Profile', # video/mp4
    'NDXC' => 'H.264/MPEG-4 AVC (.MP4) Nero Cinema Profile', # video/mp4
    'NDXH' => 'H.264/MPEG-4 AVC (.MP4) Nero HDTV Profile', # video/mp4
    'NDXM' => 'H.264/MPEG-4 AVC (.MP4) Nero Mobile Profile', # video/mp4
    'NDXP' => 'H.264/MPEG-4 AVC (.MP4) Nero Portable Profile', # video/mp4
    'NDXS' => 'H.264/MPEG-4 AVC (.MP4) Nero Standard Profile', # video/mp4
    'odcf' => 'OMA DCF DRM Format 2.0 (OMA-TS-DRM-DCF-V2_0-20060303-A)', # various
    'opf2' => 'OMA PDCF DRM Format 2.1 (OMA-TS-DRM-DCF-V2_1-20070724-C)',
    'opx2' => 'OMA PDCF DRM + XBS extensions (OMA-TS-DRM_XBS-V1_0-20070529-C)',
    'pana' => 'Panasonic Digital Camera',
    'qt  ' => 'Apple QuickTime (.MOV/QT)', # video/quicktime
    'ROSS' => 'Ross Video',
    'sdv ' => 'SD Memory Card Video', # various?
    'ssc1' => 'Samsung stereoscopic, single stream',
    'ssc2' => 'Samsung stereoscopic, dual stream',
);

# QuickTime atoms
%Image::ExifTool::QuickTime::Main = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    NOTES => q{
        The QuickTime format is used for many different types of audio, video and
        image files.  Exiftool extracts meta information from the UserData atom
        (including some proprietary manufacturer-specific information), as well as
        extracting various audio, video and image parameters.  Tags with a question
        mark after their name are not extracted unless the Unknown option is set.
    },
    free => { Unknown => 1, Binary => 1 },
    skip => { Unknown => 1, Binary => 1 },
    wide => { Unknown => 1, Binary => 1 },
    ftyp => { #MP4
        Name => 'FileType',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::FileType' },
    },
    pnot => {
        Name => 'Preview',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Preview' },
    },
    PICT => {
        Name => 'PreviewPICT',
        Binary => 1,
    },
    pict => { #8
        Name => 'PreviewPICT',
        Binary => 1,
    },
    moov => {
        Name => 'Movie',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Movie' },
    },
    mdat => { Unknown => 1, Binary => 1 },
    junk => { Unknown => 1, Binary => 1 }, #8
    uuid => [
        { #9 (MP4 files)
            Name => 'UUID-XMP',
            Condition => '$$valPt=~/^\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::XMP::Main',
                Start => 16,
            },
        },
        { #11 (MP4 files)
            Name => 'UUID-PROF',
            Condition => '$$valPt=~/^PROF!\xd2\x4f\xce\xbb\x88\x69\x5c\xfa\xc9\xc7\x40/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::QuickTime::Profile',
                Start => 24, # uid(16) + version(1) + flags(3) + count(4)
            },
        },
        { #8
            Name => 'UUID-Unknown',
            Unknown => 1,
            Binary => 1,
        },
    ],
);

# MPEG-4 'ftyp' atom
# (ref http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html)
%Image::ExifTool::QuickTime::FileType = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    FORMAT => 'int32u',
    0 => {
        Name => 'MajorBrand',
        Format => 'undef[4]',
        PrintConv => \%ftypLookup,
    },
    1 => {
        Name => 'MinorVersion',
        Format => 'undef[4]',
        ValueConv => 'sprintf("%x.%x.%x", unpack("nCC", $val))',
    },
    2 => {
        Name => 'CompatibleBrands',
        Format => 'undef[$size-8]',
        # ignore any entry with a null, and return others as a list
        ValueConv => 'my @a=($val=~/.{4}/sg); @a=grep(!/\0/,@a); \@a', 
    },
);

# atoms used in QTIF files
%Image::ExifTool::QuickTime::ImageFile = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Image' },
    NOTES => 'Tags used in QTIF QuickTime Image Files.',
    idsc => {
        Name => 'ImageDescription',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ImageDesc' },
    },
    idat => {
        Name => 'ImageData',
        Binary => 1,
    },
    iicc => {
        Name => 'ICC_Profile',
        SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' },
    },
);

# image description data block
%Image::ExifTool::QuickTime::ImageDesc = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Image' },
    FORMAT => 'int16u',
    2 => {
        Name => 'CompressorID',
        Format => 'string[4]',
# not very useful since this isn't a complete list and name is given below
#        # ref http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html
#        PrintConv => {
#            cvid => 'Cinepak',
#            jpeg => 'JPEG',
#           'smc '=> 'Graphics',
#           'rle '=> 'Animation',
#            rpza => 'Apple Video',
#            kpcd => 'Kodak Photo CD',
#           'png '=> 'Portable Network Graphics',
#            mjpa => 'Motion-JPEG (format A)',
#            mjpb => 'Motion-JPEG (format B)',
#            SVQ1 => 'Sorenson video, version 1',
#            SVQ3 => 'Sorenson video, version 3',
#            mp4v => 'MPEG-4 video',
#           'dvc '=> 'NTSC DV-25 video',
#            dvcp => 'PAL DV-25 video',
#           'gif '=> 'Compuserve Graphics Interchange Format',
#            h263 => 'H.263 video',
#            tiff => 'Tagged Image File Format',
#           'raw '=> 'Uncompressed RGB',
#           '2vuY'=> "Uncompressed Y'CbCr, 3x8-bit 4:2:2 (2vuY)",
#           'yuv2'=> "Uncompressed Y'CbCr, 3x8-bit 4:2:2 (yuv2)",
#            v308 => "Uncompressed Y'CbCr, 8-bit 4:4:4",
#            v408 => "Uncompressed Y'CbCr, 8-bit 4:4:4:4",
#            v216 => "Uncompressed Y'CbCr, 10, 12, 14, or 16-bit 4:2:2",
#            v410 => "Uncompressed Y'CbCr, 10-bit 4:4:4",
#            v210 => "Uncompressed Y'CbCr, 10-bit 4:2:2",
#        },
    },
    10 => {
        Name => 'VendorID',
        Format => 'string[4]',
        RawConv => 'length $val ? $val : undef',
        PrintConv => \%vendorID,
        SeparateTable => 'VendorID',
    },
  # 14 - ("Quality" in QuickTime docs) ??
    16 => 'SourceImageWidth',
    17 => 'SourceImageHeight',
    18 => { Name => 'XResolution',  Format => 'fixed32u' },
    20 => { Name => 'YResolution',  Format => 'fixed32u' },
  # 24 => 'FrameCount', # always 1 (what good is this?)
    25 => {
        Name => 'CompressorName',
        Format => 'string[32]',
        # (sometimes this is a Pascal string, and sometimes it is a C string)
        RawConv => q{
            $val=substr($val,1,ord($1)) if $val=~/^([\0-\x1f])/ and ord($1)<length($val);
            length $val ? $val : undef;
        },
    },
    41 => 'BitDepth',
);

# preview data block
%Image::ExifTool::QuickTime::Preview = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Image' },
    FORMAT => 'int16u',
    0 => {
        Name => 'PreviewDate',
        Format => 'int32u',
        Groups => { 2 => 'Time' },
        %timeInfo,
    },
    2 => 'PreviewVersion',
    3 => {
        Name => 'PreviewAtomType',
        Format => 'string[4]',
    },
    5 => 'PreviewAtomIndex',
);

# movie atoms
%Image::ExifTool::QuickTime::Movie = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    mvhd => {
        Name => 'MovieHeader',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MovieHdr' },
    },
    trak => {
        Name => 'Track',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Track' },
    },
    udta => {
        Name => 'UserData',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::UserData' },
    },
    meta => { # 'meta' is found here in my EX-F1 MOV sample - PH
        Name => 'Meta',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Meta' },
    },
    iods => {
        Name => 'InitialObjectDescriptor',
        Flags => ['Binary','Unknown'],
    },
    uuid => [
        { #11 (MP4 files) (also found in QuickTime::Track)
            Name => 'UUID-USMT',
            Condition => '$$valPt=~/^USMT!\xd2\x4f\xce\xbb\x88\x69\x5c\xfa\xc9\xc7\x40/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::QuickTime::UserMedia',
                Start => 16,
            },
        },
        {
            Name => 'UUID-Unknown',
            Unknown => 1,
            Binary => 1,
        },
    ],
);

# movie header data block
%Image::ExifTool::QuickTime::MovieHdr = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    FORMAT => 'int32u',
    0 => { Name => 'Version', Format => 'int8u' },
    1 => {
        Name => 'CreateDate',
        Groups => { 2 => 'Time' },
        %timeInfo,
    },
    2 => {
        Name => 'ModifyDate',
        Groups => { 2 => 'Time' },
        %timeInfo,
    },
    3 => {
        Name => 'TimeScale',
        RawConv => '$$self{TimeScale} = $val',
    },
    4 => { Name => 'Duration', %durationInfo },
    5 => {
        Name => 'PreferredRate',
        ValueConv => '$val / 0x10000',
    },
    6 => {
        Name => 'PreferredVolume',
        Format => 'int16u',
        ValueConv => '$val / 256',
        PrintConv => 'sprintf("%.2f%%", $val * 100)',
    },
    9 => {
        Name => 'MatrixStructure',
        Format => 'fixed32s[9]',
        # (the right column is fixed 2.30 instead of 16.16)
        ValueConv => q{
            my @a = split ' ',$val;
            $_ /= 0x4000 foreach @a[2,5,8];
            return "@a";
        },
    },
    18 => { Name => 'PreviewTime',      %durationInfo },
    19 => { Name => 'PreviewDuration',  %durationInfo },
    20 => { Name => 'PosterTime',       %durationInfo },
    21 => { Name => 'SelectionTime',    %durationInfo },
    22 => { Name => 'SelectionDuration',%durationInfo },
    23 => { Name => 'CurrentTime',      %durationInfo },
    24 => 'NextTrackID',
);

# track atoms
%Image::ExifTool::QuickTime::Track = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    tkhd => {
        Name => 'TrackHeader',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TrackHdr' },
    },
    udta => {
        Name => 'UserData',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::UserData' },
    },
    mdia => { #MP4
        Name => 'Media',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Media' },
    },
    meta => { #PH (MOV)
        Name => 'Meta',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Meta' },
    },
    tref => {
        Name => 'TrackRef',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TrackRef' },
    },
    uuid => [
        { #11 (MP4 files) (also found in QuickTime::Movie)
            Name => 'UUID-USMT',
            Condition => '$$valPt=~/^USMT!\xd2\x4f\xce\xbb\x88\x69\x5c\xfa\xc9\xc7\x40/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::QuickTime::UserMedia',
                Start => 16,
            },
        },
        {
            Name => 'UUID-Unknown',
            Unknown => 1,
            Binary => 1,
        },
    ],
    # edts - edits --> contains elst (edit list)
    # tapt - TrackApertureModeDimensionsAID --> contains clef (TrackCleanApertureDimensionsAID),
    #        prof (TrackProductionApertureDimensionsAID), enof (TrackEncodedPixelsDimensionsAID)
    # clip - clipping --> contains crgn (clip region)
    # matt - track matt --> contains kmat (compressed matt)
    # load - track loading settings
    # imap - track input map --> contains '  in' --> contains '  ty', obid
);

# track header data block
%Image::ExifTool::QuickTime::TrackHdr = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 1 => 'Track#', 2 => 'Video' },
    FORMAT => 'int32u',
    0 => {
        Name => 'TrackVersion',
        Format => 'int8u',
        Priority => 0,
    },
    1 => {
        Name => 'TrackCreateDate',
        Priority => 0,
        Groups => { 2 => 'Time' },
        %timeInfo,
    },
    2 => {
        Name => 'TrackModifyDate',
        Priority => 0,
        Groups => { 2 => 'Time' },
        %timeInfo,
    },
    3 => {
        Name => 'TrackID',
        Priority => 0,
    },
    5 => {
        Name => 'TrackDuration',
        Priority => 0,
        %durationInfo,
    },
    8 => {
        Name => 'TrackLayer',
        Format => 'int16u',
        Priority => 0,
    },
    9 => {
        Name => 'TrackVolume',
        Format => 'int16u',
        Priority => 0,
        ValueConv => '$val / 256',
        PrintConv => 'sprintf("%.2f%%", $val * 100)',
    },
    10 => {
        Name => 'MatrixStructure',
        Format => 'fixed32s[9]',
        # (the right column is fixed 2.30 instead of 16.16)
        ValueConv => q{
            my @a = split ' ',$val;
            $_ /= 0x4000 foreach @a[2,5,8];
            return "@a";
        },
    },
    19 => {
        Name => 'ImageWidth',
        Priority => 0,
        RawConv => \&FixWrongFormat,
    },
    20 => {
        Name => 'ImageHeight',
        Priority => 0,
        RawConv => \&FixWrongFormat,
    },
);

# user data atoms
%Image::ExifTool::QuickTime::UserData = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    NOTES => q{
        Tag ID's beginning with the copyright symbol (hex 0xa9) are multi-language
        text, but ExifTool only extracts the text from the first language in the
        record.  ExifTool will extract any multi-language user data tags found, even
        if they don't exist in this table.
    },
    "\xa9cpy" => { Name => 'Copyright',  Groups => { 2 => 'Author' } },
    "\xa9day" => {
        Name => 'CreateDate',
        Groups => { 2 => 'Time' },
        # handle values in the form "2010-02-12T13:27:14-0800" (written by Apple iPhone)
        ValueConv => q{
            require Image::ExifTool::XMP;
            $val =  Image::ExifTool::XMP::ConvertXMPDate($val);
            $val =~ s/([-+]\d{2})(\d{2})$/$1:$2/; # add colon to timezone if necessary
            return $val;
        },
        PrintConv => '$self->ConvertDateTime($val)',
    },
    "\xa9dir" => 'Director',
    "\xa9ed1" => 'Edit1',
    "\xa9ed2" => 'Edit2',
    "\xa9ed3" => 'Edit3',
    "\xa9ed4" => 'Edit4',
    "\xa9ed5" => 'Edit5',
    "\xa9ed6" => 'Edit6',
    "\xa9ed7" => 'Edit7',
    "\xa9ed8" => 'Edit8',
    "\xa9ed9" => 'Edit9',
    "\xa9fmt" => 'Format',
    "\xa9inf" => 'Information',
    "\xa9prd" => 'Producer',
    "\xa9prf" => 'Performers',
    "\xa9req" => 'Requirements',
    "\xa9src" => 'Source',
    "\xa9wrt" => 'Writer',
    name => 'Name',
    WLOC => {
        Name => 'WindowLocation',
        Format => 'int16u',
    },
    LOOP => {
        Name => 'LoopStyle',
        Format => 'int32u',
        PrintConv => {
            1 => 'Normal',
            2 => 'Palindromic',
        },
    },
    SelO => {
        Name => 'PlaySelection',
        Format => 'int8u',
    },
    AllF => {
        Name => 'PlayAllFrames',
        Format => 'int8u',
    },
    meta => {
        Name => 'Meta',
        SubDirectory => {
            TagTable => 'Image::ExifTool::QuickTime::Meta',
            Start => 4, # must skip 4-byte version number header
        },
    },
    DcMD => {
        Name => 'DcMD',
        SubDirectory => {
            TagTable => 'Image::ExifTool::QuickTime::DcMD',
        },
    },
   'ptv '=> {
        Name => 'PrintToVideo',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Video' },
    },
   'hnti'=> {
        Name => 'HintInfo',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::HintInfo' },
    },
   'hinf' => {
        Name => 'HintTrackInfo',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::HintTrackInfo' },
    },
    TAGS => [ #PH
        # these tags were initially discovered in a Pentax movie,
        # but similar information is found in videos from other manufacturers
        {
            Name => 'KodakTags',
            Condition => '$$valPt =~ /^EASTMAN KODAK COMPANY/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::Kodak::MOV',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'KonicaMinoltaTags',
            Condition => '$$valPt =~ /^KONICA MINOLTA DIGITAL CAMERA/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::Minolta::MOV1',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'MinoltaTags',
            Condition => '$$valPt =~ /^MINOLTA DIGITAL CAMERA/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::Minolta::MOV2',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'NikonTags',
            Condition => '$$valPt =~ /^NIKON DIGITAL CAMERA\0/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::Nikon::MOV',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'OlympusTags1',
            Condition => '$$valPt =~ /^OLYMPUS DIGITAL CAMERA\0.{9}\x01\0/s',
            SubDirectory => {
                TagTable => 'Image::ExifTool::Olympus::MOV1',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'OlympusTags2',
            Condition => '$$valPt =~ /^OLYMPUS DIGITAL CAMERA(?!\0.{21}\x0a\0{3})/s',
            SubDirectory => {
                TagTable => 'Image::ExifTool::Olympus::MOV2',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'OlympusTags3',
            Condition => '$$valPt =~ /^OLYMPUS DIGITAL CAMERA\0/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::Olympus::MP4',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'PentaxTags',
            Condition => '$$valPt =~ /^PENTAX DIGITAL CAMERA\0/',
            SubDirectory => {
                TagTable => 'Image::ExifTool::Pentax::MOV',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'SanyoMOV',
            Condition => q{
                $$valPt =~ /^SANYO DIGITAL CAMERA\0/ and
                $self->{VALUE}->{FileType} eq "MOV"
            },
            SubDirectory => {
                TagTable => 'Image::ExifTool::Sanyo::MOV',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'SanyoMP4',
            Condition => q{
                $$valPt =~ /^SANYO DIGITAL CAMERA\0/ and
                $self->{VALUE}->{FileType} eq "MP4"
            },
            SubDirectory => {
                TagTable => 'Image::ExifTool::Sanyo::MP4',
                ByteOrder => 'LittleEndian',
            },
        },
        {
            Name => 'UnknownTags',
            Unknown => 1,
            Binary => 1
        },
    ],
    NCDT => { #PH
        Name => 'NikonNCDT',
        SubDirectory => {
            TagTable => 'Image::ExifTool::Nikon::NCDT',
            ProcessProc => \&ProcessMOV,
        },
    },
    QVMI => { #PH
        Name => 'CasioQVMI',
        # Casio stores standard EXIF-format information in MOV videos (ie. EX-S880)
        SubDirectory => {
            TagTable => 'Image::ExifTool::Exif::Main',
            DirName => 'IFD0',
            Start => 10,
            ByteOrder => 'BigEndian',
        },
    },
    MMA0 => { #PH (DiMage 7Hi)
        Name => 'MinoltaMMA0',
        SubDirectory => { TagTable => 'Image::ExifTool::Minolta::MMA' },
    },
    MMA1 => { #PH (Dimage A2)
        Name => 'MinoltaMMA1',
        SubDirectory => { TagTable => 'Image::ExifTool::Minolta::MMA' },
    },
    XMP_ => { #PH (Adobe CS3 Bridge)
        Name => 'XMP',
        SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
    },
    vndr => 'Vendor', #PH (Samsung PL70)
    # Canon tags
    CNCV => 'CompressorVersion', #PH (5D Mark II)
    CNMN => 'Model', #PH (EOS 550D)
    CNFV => 'FirmwareVersion', #PH (EOS 550D)
    # CNDB - ? (550D)
    INFO => {
        Name => 'SamsungINFO',
        SubDirectory => { TagTable => 'Image::ExifTool::Samsung::INFO' },
    },
);

# User-specific media data atoms (ref 11)
%Image::ExifTool::QuickTime::UserMedia = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    MTDT => {
        Name => 'MetaData',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MetaData' },
    },
);

# User-specific media data atoms (ref 11)
%Image::ExifTool::QuickTime::MetaData = (
    PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMetaData,
    GROUPS => { 2 => 'Video' },
    TAG_PREFIX => 'MetaData',
    0x01 => 'Title',
    0x03 => {
        Name => 'ProductionDate',
        # translate from format "yyyy/mm/dd hh:mm:ss"
        ValueConv => '$val=~tr{/}{:}; $val',
        PrintConv => '$exifTool->ConvertDateTime($val)',
    },
    0x04 => 'Software',
    0x05 => 'Product',
    0x0a => {
        Name => 'TrackProperty',
        RawConv => 'my @a=unpack("Nnn",$val); "@a"',
        PrintConv => [
            { 0 => 'No presentation', BITMASK => { 0 => 'Main track' } },
            { 0 => 'No attributes',   BITMASK => { 15 => 'Read only' } },
            '"Priority $val"',
        ],
    },
    0x0b => {
        Name => 'TimeZone',
        Groups => { 2 => 'Time' },
        RawConv => 'Get16s(\$val,0)',
        PrintConv => 'TimeZoneString($val)',
    },
    0x0c => {
        Name => 'ModifyDate',
        Groups => { 2 => 'Time' },
        # translate from format "yyyy/mm/dd hh:mm:ss"
        ValueConv => '$val=~tr{/}{:}; $val',
        PrintConv => '$exifTool->ConvertDateTime($val)',
    },
);

# Profile atoms (ref 11)
%Image::ExifTool::QuickTime::Profile = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    FPRF => {
        Name => 'FileGlobalProfile',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::FileProf' },
    },
    APRF => {
        Name => 'AudioProfile',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::AudioProf' },
    },
    VPRF => {
        Name => 'VideoProfile',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::VideoProf' },
    },
);

# FPRF atom information (ref 11)
%Image::ExifTool::QuickTime::FileProf = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    FORMAT => 'int32u',
    0 => { Name => 'FileProfileVersion', Unknown => 1 }, # unknown = uninteresting
    1 => {
        Name => 'FileFunctionFlags',
        PrintConv => { BITMASK => {
            28 => 'Fragmented',
            29 => 'Additional tracks',
            30 => 'Edited', # (main AV track is edited)
        }},
    },
    # 2 - reserved
);

# APRF atom information (ref 11)
%Image::ExifTool::QuickTime::AudioProf = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Audio' },
    FORMAT => 'int32u',
    0 => { Name => 'AudioProfileVersion', Unknown => 1 },
    1 => 'AudioTrackID',
    2 => {
        Name => 'AudioCodec',
        Format => 'undef[4]',
    },
    3 => {
        Name => 'AudioCodecInfo',
        Unknown => 1,
        PrintConv => 'sprintf("0x%.4x", $val)',
    },
    4 => {
        Name => 'AudioAttributes',
        PrintConv => { BITMASK => {
            0 => 'Encrypted',
            1 => 'Variable bitrate',
            2 => 'Dual mono',
        }},
    },
    5 => {
        Name => 'AudioAvgBitrate',
        ValueConv => '$val * 1000',
    },
    6 => {
        Name => 'AudioMaxBitrate',
        ValueConv => '$val * 1000',
    },
    7 => 'AudioSampleRate',
    8 => 'AudioChannels',
);

# VPRF atom information (ref 11)
%Image::ExifTool::QuickTime::VideoProf = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    FORMAT => 'int32u',
    0 => { Name => 'VideoProfileVersion', Unknown => 1 },
    1 => 'VideoTrackID',
    2 => {
        Name => 'VideoCodec',
        Format => 'undef[4]',
    },
    3 => {
        Name => 'VideoCodecInfo',
        Unknown => 1,
        PrintConv => 'sprintf("0x%.4x", $val)',
    },
    4 => {
        Name => 'VideoAttributes',
        PrintConv => { BITMASK => {
            0 => 'Encrypted',
            1 => 'Variable bitrate',
            2 => 'Variable frame rate',
            3 => 'Interlaced',
        }},
    },
    5 => {
        Name => 'VideoAvgBitrate',
        ValueConv => '$val * 1000',
    },
    6 => {
        Name => 'VideoMaxBitrate',
        ValueConv => '$val * 1000',
    },
    7 => {
        Name => 'VideoAvgFrameRate',
        Format => 'fixed32u',
        PrintConv => 'int($val * 1000 + 0.5) / 1000',
    },
    8 => {
        Name => 'VideoMaxFrameRate',
        Format => 'fixed32u',
        PrintConv => 'int($val * 1000 + 0.5) / 1000',
    },
    9 => {
        Name => 'VideoSize',
        Format => 'int16u[2]',
        PrintConv => '$val=~tr/ /x/; $val',
    },
    10 => {
        Name => 'PixelAspectRatio',
        Format => 'int16u[2]',
        PrintConv => '$val=~tr/ /:/; $val',
    },
);

# meta atoms
%Image::ExifTool::QuickTime::Meta = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    ilst => {
        Name => 'InfoList',
        SubDirectory => {
            TagTable => 'Image::ExifTool::QuickTime::InfoList',
            HasData => 1, # process atoms as containers with 'data' elements
        },
    },
    # MP4 tags (ref 5)
    hdlr => {
        Name => 'Handler',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Handler' },
    },
    dinf => {
        Name => 'DataInformation',
        Flags => ['Binary','Unknown'],
    },
    ipmc => {
        Name => 'IPMPControl',
        Flags => ['Binary','Unknown'],
    },
    iloc => {
        Name => 'ItemLocation',
        Flags => ['Binary','Unknown'],
    },
    ipro => {
        Name => 'ItemProtection',
        Flags => ['Binary','Unknown'],
    },
    iinf => {
        Name => 'ItemInformation',
        Flags => ['Binary','Unknown'],
    },
   'xml ' => {
        Name => 'XML',
        SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
    },
   'keys' => {
        Name => 'Keys',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Keys' },
    },
    bxml => {
        Name => 'BinaryXML',
        Flags => ['Binary','Unknown'],
    },
    pitm => {
        Name => 'PrimaryItemReference',
        Flags => ['Binary','Unknown'],
    },
);

# track reference atoms
%Image::ExifTool::QuickTime::TrackRef = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    chap => { Name => 'ChapterList', Format => 'int32u' },
    # also: tmcd, sync, scpt, ssrc, iTunesInfo
);

# info list atoms
# -> these atoms are unique, and contain one or more 'data' atoms
%Image::ExifTool::QuickTime::InfoList = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Audio' },
    NOTES => q{
        As well as these tags, the 'mdta' handler uses numerical tag ID's which are
        added dynamically to this table after processing the Meta Keys information.
    },
    # in this table, binary 1 and 2-byte "data"-type tags are interpreted as
    # int8u and int16u.  Multi-byte binary "data" tags are extracted as binary data
    "\xa9ART" => 'Artist',
    "\xa9alb" => 'Album',
    "\xa9cmt" => 'Comment',
    "\xa9com" => 'Composer',
    "\xa9day" => 'Year',
    "\xa9des" => 'Description', #4
    "\xa9enc" => 'EncodedBy', #10
    "\xa9gen" => 'Genre',
    "\xa9grp" => 'Grouping',
    "\xa9lyr" => 'Lyrics',
    "\xa9nam" => 'Title',
    # "\xa9st3" ? #10
    "\xa9too" => 'Encoder',
    "\xa9trk" => 'Track',
    "\xa9wrt" => 'Composer',
    '----' => {
        Name => 'iTunesInfo',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::iTunesInfo' },
    },
    aART => 'AlbumArtist',
    covr => 'CoverArt',
    cpil => { #10
        Name => 'Compilation',
        PrintConv => { 0 => 'No', 1 => 'Yes' },
    },
    disk => {
        Name => 'DiskNumber',
        ValueConv => 'length($val) >= 6 ? join(" of ",unpack("x2nn",$val)) : \$val',
    },
    pgap => { #10
        Name => 'PlayGap',
        PrintConv => {
            0 => 'Insert Gap',
            1 => 'No Gap',
        },
    },
    tmpo => {
        Name => 'BeatsPerMinute',
        Format => 'int16u', # marked as boolean but really int16u in my sample
    },
    trkn => {
        Name => 'TrackNumber',
        ValueConv => 'length($val) >= 6 ? join(" of ",unpack("x2nn",$val)) : \$val',
    },
#
# Note: it is possible that the tags below are not being decoded properly
# because I don't have samples to verify many of these - PH
#
    akID => { #10
        Name => 'AppleStoreAccountType',
        PrintConv => {
            0 => 'iTunes',
            1 => 'AOL',
        },
    },
    albm => 'Album', #(ffmpeg source)
    apID => 'AppleStoreAccount',
    atID => { #10 (or TV series)
        Name => 'AlbumTitleID',
        Format => 'int32u',
    },
    auth => 'Author',
    catg => 'Category', #7
    cnID => { #10
        Name => 'AppleStoreCatalogID',
        Format => 'int32u',
    },
    cprt => 'Copyright',
    dscp => 'Description',
    desc => 'Description', #7
    gnre => { #10
        Name => 'Genre',
        PrintConv => q{
            return $val unless $val =~ /^\d+$/;
            require Image::ExifTool::ID3;
            Image::ExifTool::ID3::PrintGenre($val - 1); # note the "- 1"
        },
    },
    egid => 'EpisodeGlobalUniqueID', #7
    geID => { #10
        Name => 'GenreID',
        Format => 'int32u',
        # 4005 = Kids
        # 4010 = Teens
    },
    grup => 'Grouping', #10
    hdvd => { #10
        Name => 'HDVideo',
        PrintConv => { 0 => 'No', 1 => 'Yes' },
    },
    keyw => 'Keyword', #7
    ldes => 'LongDescription', #10
    pcst => { #7
        Name => 'Podcast',
        PrintConv => { 0 => 'No', 1 => 'Yes' },
    },
    perf => 'Performer',
    plID => { #10 (or TV season)
        Name => 'PlayListID',
        Format => 'int8u',  # actually int64u, but split it up
    },
    purd => 'PurchaseDate', #7
    purl => 'PodcastURL', #7
    rtng => { #10
        Name => 'Rating',
        PrintConv => {
            0 => 'none',
            2 => 'Clean',
            4 => 'Explicit',
        },
    },
    sfID => { #10
        Name => 'AppleStoreCountry',
        Format => 'int32u',
        PrintConv => {
            143460 => 'Australia',
            143445 => 'Austria',
            143446 => 'Belgium',
            143455 => 'Canada',
            143458 => 'Denmark',
            143447 => 'Finland',
            143442 => 'France',
            143443 => 'Germany',
            143448 => 'Greece',
            143449 => 'Ireland',
            143450 => 'Italy',
            143462 => 'Japan',
            143451 => 'Luxembourg',
            143452 => 'Netherlands',
            143461 => 'New Zealand',
            143457 => 'Norway',
            143453 => 'Portugal',
            143454 => 'Spain',
            143456 => 'Sweden',
            143459 => 'Switzerland',
            143444 => 'United Kingdom',
            143441 => 'United States',
        },
    },
    soaa => 'SortAlbumArtist', #10
    soal => 'SortAlbum', #10
    soar => 'SortArtist', #10
    soco => 'SortComposer', #10
    sonm => 'SortName', #10
    sosn => 'SortShow', #10
    stik => { #10
        Name => 'MediaType',
        PrintConv => { #(http://weblog.xanga.com/gryphondwb/615474010/iphone-ringtones---what-did-itunes-741-really-do.html)
            0 => 'Movie',
            1 => 'Normal (Music)',
            2 => 'Audiobook',
            5 => 'Whacked Bookmark',
            6 => 'Music Video',
            9 => 'Short Film',
            10 => 'TV Show',
            11 => 'Booklet',
            14 => 'Ringtone',
        },
    },
    titl => 'Title',
    tven => 'TVEpisodeID', #7
    tves => { #7/10
        Name => 'TVEpisode',
        Format => 'int32u',
    },
    tvnn => 'TVNetworkName', #7
    tvsh => 'TVShow', #10
    tvsn => { #7/10
        Name => 'TVSeason',
        Format => 'int32u',
    },
    yrrc => 'Year', #(ffmpeg source)
);

# info list keys
%Image::ExifTool::QuickTime::Keys = (
    PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessKeys,
    NOTES => q{
        This directory contains a list of key names which are used to decode
        InfoList tags written by the "mdta" handler.  The prefix of
        "com.apple.quicktime." has been removed from all TagID's below.
    },
    'version'                       => 'Version',
    'player.version'                => 'PlayerVersion',
    'player.movie.visual.brightness'=> 'Brightness',
    'player.movie.visual.color'     => 'Color',
    'player.movie.visual.tint'      => 'Tint',
    'player.movie.visual.contrast'  => 'Contrast',
    'player.movie.audio.gain'       => 'AudioGain',
    'player.movie.audio.treble'     => 'Trebel',
    'player.movie.audio.bass'       => 'Bass',
    'player.movie.audio.balance'    => 'Balance',
    'player.movie.audio.pitchshift' => 'PitchShift',
    'player.movie.audio.mute' => {
        Name => 'Mute',
        Format => 'int8u',
        PrintConv => { 0 => 'Off', 1 => 'On' },
    },
);

# info list atoms
%Image::ExifTool::QuickTime::iTunesInfo = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Audio' },
    # 'mean'/'name'/'data' atoms form a triplet, but unfortunately
    # I can't find any source for decoding 'data'.
    # 'mean' is normally 'com.apple.iTunes'
    # 'name' values: 'tool', 'iTunNORM' (volume normalization),
    #   'iTunSMPB', 'iTunes_CDDB_IDs', 'iTunes_CDDB_TrackNumber'
    mean => {
        Name => 'Mean',
        Unknown => 1,
    },
    name => {
        Name => 'Name',
        Unknown => 1,
    },
    data => {
        Name => 'Data',
        Flags => ['Binary','Unknown'],
    },
);

# print to video data block
%Image::ExifTool::QuickTime::Video = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    0 => {
        Name => 'DisplaySize',
        PrintConv => {
            0 => 'Normal',
            1 => 'Double Size',
            2 => 'Half Size',
            3 => 'Full Screen',
            4 => 'Current Size',
        },
    },
    6 => {
        Name => 'SlideShow',
        PrintConv => {
            0 => 'No',
            1 => 'Yes',
        },
    },
);

# 'hnti' atoms
%Image::ExifTool::QuickTime::HintInfo = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    'rtp ' => {
        Name => 'RealtimeStreamingProtocol',
        PrintConv => '$val=~s/^sdp /(SDP) /; $val',
    },
    'sdp ' => 'StreamingDataProtocol',
);

# 'hinf' atoms
%Image::ExifTool::QuickTime::HintTrackInfo = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    trpY => { Name => 'TotalBytes', Format => 'int64u' }, #(documented)
    trpy => { Name => 'TotalBytes', Format => 'int64u' }, #(observed)
    totl => { Name => 'TotalBytes', Format => 'int32u' },
    nump => { Name => 'NumPackets', Format => 'int64u' },
    npck => { Name => 'NumPackets', Format => 'int32u' },
    tpyl => { Name => 'TotalBytesNoRTPHeaders', Format => 'int64u' },
    tpaY => { Name => 'TotalBytesNoRTPHeaders', Format => 'int32u' }, #(documented)
    tpay => { Name => 'TotalBytesNoRTPHeaders', Format => 'int32u' }, #(observed)
    maxr => {
        Name => 'MaxDataRate',
        Format => 'int32u',
        Count => 2,
        PrintConv => 'my @a=split(" ",$val);sprintf("%d bytes in %.3f s",$a[1],$a[0]/1000)',
    },
    dmed => { Name => 'MediaTrackBytes',    Format => 'int64u' },
    dimm => { Name => 'ImmediateDataBytes', Format => 'int64u' },
    drep => { Name => 'RepeatedDataBytes',  Format => 'int64u' },
    tmin => {
        Name => 'MinTransmissionTime',
        Format => 'int32u',
        PrintConv => 'sprintf("%.3f s",$val/1000)',
    },
    tmax => {
        Name => 'MaxTransmissionTime',
        Format => 'int32u',
        PrintConv => 'sprintf("%.3f s",$val/1000)',
    },
    pmax => { Name => 'LargestPacketSize',  Format => 'int32u' },
    dmax => {
        Name => 'LargestPacketDuration',
        Format => 'int32u',
        PrintConv => 'sprintf("%.3f s",$val/1000)',
    },
    payt => {
        Name => 'PayloadType',
        ValueConv => 'unpack("N",$val) . " " . substr($val, 5)',
        PrintConv => '$val=~s/ /, /;$val',
    },
);

# Kodak DcMD atoms (ref PH)
%Image::ExifTool::QuickTime::DcMD = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    NOTES => 'Metadata directory found in MOV videos from some Kodak cameras.',
    Cmbo => {
        Name => 'CameraByteOrder',
        PrintConv => {
            II => 'Little-endian (Intel, II)',
            MM => 'Big-endian (Motorola, MM)',
        },
    },
    DcME => {
        Name => 'DcME',
        SubDirectory => {
            TagTable => 'Image::ExifTool::QuickTime::DcME',
        },
    },
);

# Kodak DcME atoms (ref PH)
%Image::ExifTool::QuickTime::DcME = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    # Mtmd = binary data
    # Keyw = keywords?
    # Rate = 2 bytes "00 00"
);

# MP4 media box (ref 5)
%Image::ExifTool::QuickTime::Media = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    NOTES => 'MP4 media box.',
    mdhd => [{
        Name => 'MediaHeader',
        Condition => '$$valPt =~ /^\0{4}/',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MediaHeader0' },
    },{
        Name => 'MediaHeader',
        Condition => '$$valPt =~ /^\0{3}\x01/',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MediaHeader1' },
    }],
    hdlr => {
        Name => 'Handler',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Handler' },
    },
    minf => {
        Name => 'MediaInfo',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MediaInfo' },
    },
);

# MP4 media header box, version 0 (ref 5)
%Image::ExifTool::QuickTime::MediaHeader0 = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    NOTES => 'MP4 media header version 0.',
    FORMAT => 'int32u',
    0 => {
        Name => 'MediaHeaderVersion',
        Notes => 'version 0',
    },
    1 => {
        Name => 'MediaCreateDate',
        Groups => { 2 => 'Time' },
        %timeInfo,
    },
    2 => {
        Name => 'MediaModifyDate',
        Groups => { 2 => 'Time' },
        %timeInfo,
    },
    3 => {
        Name => 'MediaTimeScale',
        RawConv => '$$self{MediaTS} = $val',
    },
    4 => {
        Name => 'MediaDuration',
        RawConv => '$$self{MediaTS} ? $val / $$self{MediaTS} : $val',
        PrintConv => '$$self{MediaTS} ? ConvertDuration($val) : $val',
    },
    5 => {
        Name => 'MediaLanguageCode',
        Format => 'int16u',
        RawConv => '$val ? $val : undef',
        ValueConv => 'pack "C*", map({ (($val>>$_)&0x1f)+0x60 } 10, 5, 0)',
    },
);

# MP4 media header box, version 1 (ref 5)
%Image::ExifTool::QuickTime::MediaHeader1 = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    NOTES => 'MP4 media header version 1.',
    FORMAT => 'int32u',
    0 => {
        Name => 'MediaHeaderVersion',
        Notes => 'version 1',
    },
    1 => {
        Name => 'MediaCreateDate',
        Format => 'int64u',
        Groups => { 2 => 'Time' },
        %timeInfo,
    },
    3 => {
        Name => 'MediaModifyDate',
        Format => 'int64u',
        Groups => { 2 => 'Time' },
        %timeInfo,
    },
    5 => {
        Name => 'MediaTimescale',
        RawConv => '$$self{MediaTS} = $val',
    },
    6 => {
        Name => 'MediaDuration',
        Format => 'int64u',
        RawConv => '$$self{MediaTS} ? $val / $$self{MediaTS} : $val',
        PrintConv => '$$self{MediaTS} ? ConvertDuration($val) : $val',
    },
    8 => {
        Name => 'MediaLanguageCode',
        Format => 'undef[4]',
        ValueConv => 'pack "C*", map({ (($val>>$_)&0x1f)+0x60 } 10, 5, 0)',
    },
);

# MP4 media information box (ref 5)
%Image::ExifTool::QuickTime::MediaInfo = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    NOTES => 'MP4 media info box.',
    vmhd => {
        Name => 'VideoHeader',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::VideoHeader' },
    },
    smhd => {
        Name => 'AudioHeader',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::AudioHeader' },
    },
    hmhd => {
        Name => 'HintHeader',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::HintHeader' },
    },
    nmhd => {
        Name => 'NullMediaHeader',
        Flags => ['Binary','Unknown'],
    },
    dinf => {
        Name => 'DataInfo',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::DataInfo' },
    },
    gmhd => {
        Name => 'GenMediaHeader',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::GenMediaHeader' },
    },
    hdlr => { #PH
        Name => 'Handler',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Handler' },
    },
    stbl => {
        Name => 'SampleTable',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::SampleTable' },
    },
);

# MP4 video media header (ref 5)
%Image::ExifTool::QuickTime::VideoHeader = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    NOTES => 'MP4 video media header.',
    FORMAT => 'int16u',
    2 => {
        Name => 'GraphicsMode',
        PrintHex => 1,
        SeparateTable => 'GraphicsMode',
        PrintConv => \%graphicsMode,
    },
    3 => { Name => 'OpColor', Format => 'int16u[3]' },
);

# MP4 audio media header (ref 5)
%Image::ExifTool::QuickTime::AudioHeader = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Audio' },
    NOTES => 'MP4 audio media header.',
    FORMAT => 'int16u',
    2 => { Name => 'Balance', Format => 'fixed16s' },
);

# MP4 hint media header (ref 5)
%Image::ExifTool::QuickTime::HintHeader = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    NOTES => 'MP4 hint media header.',
    FORMAT => 'int16u',
    2 => 'MaxPDUSize',
    3 => 'AvgPDUSize',
    4 => { Name => 'MaxBitrate', Format => 'int32u' },
    6 => { Name => 'AvgBitrate', Format => 'int32u' },
);

# MP4 sample table box (ref 5)
%Image::ExifTool::QuickTime::SampleTable = (
    PROCESS_PROC => \&ProcessMOV,
    GROUPS => { 2 => 'Video' },
    NOTES => 'MP4 sample table box.',
    stsd => [
        {
            Name => 'AudioSampleDesc',
            Condition => '$$self{HandlerType} and $$self{HandlerType} eq "soun"',
            SubDirectory => {
                TagTable => 'Image::ExifTool::QuickTime::AudioSampleDesc',
                Start => 8, # skip version number and count
            },
        },{
            Name => 'VideoSampleDesc',
            Condition => '$$self{HandlerType} and $$self{HandlerType} eq "vide"',
            SubDirectory => {
                TagTable => 'Image::ExifTool::QuickTime::ImageDesc',
                Start => 8, # skip version number and count
            },
        },{
            Name => 'HintSampleDesc',
            Condition => '$$self{HandlerType} and $$self{HandlerType} eq "hint"',
            SubDirectory => {
                TagTable => 'Image::ExifTool::QuickTime::HintSampleDesc',
                Start => 8, # skip version number and count
            },
        },{
            Name => 'OtherSampleDesc',
            SubDirectory => {
                TagTable => 'Image::ExifTool::QuickTime::OtherSampleDesc',
                Start => 8, # skip version number and count
            },
        },
        # (Note: "alis" HandlerType handled by the parent audio or video handler)
    ],
    stts => [ # decoding time-to-sample table
        {
            Name => 'VideoFrameRate',
            Notes => 'average rate calculated from time-to-sample table for video media',
            Condition => '$$self{HandlerType} and $$self{HandlerType} eq "vide"',
            # (must be RawConv so appropriate MediaTS is used in calculation)
            RawConv => 'Image::ExifTool::QuickTime::CalcSampleRate($self, \$val)',
            PrintConv => 'int($val * 1000 + 0.5) / 1000',
        },
        {
            Name => 'TimeToSampleTable',
            Flags => ['Binary','Unknown'],
        },
    ],
    ctts => {
        Name => 'CompositionTimeToSample',
        Flags => ['Binary','Unknown'],
    },
    stsc => {
        Name => 'SampleToChunk',
        Flags => ['Binary','Unknown'],
    },
    stsz => {
        Name => 'SampleSizes',
        Flags => ['Binary','Unknown'],
    },
    stz2 => {
        Name => 'CompactSampleSizes',
        Flags => ['Binary','Unknown'],
    },
    stco => {
        Name => 'ChunkOffset',
        Flags => ['Binary','Unknown'],
    },
    co64 => {
        Name => 'ChunkOffset64',
        Flags => ['Binary','Unknown'],
    },
    stss => {
        Name => 'SyncSampleTable',
        Flags => ['Binary','Unknown'],
    },
    stsh => {
        Name => 'ShadowSyncSampleTable',
        Flags => ['Binary','Unknown'],
    },
    padb => {
        Name => 'SamplePaddingBits',
        Flags => ['Binary','Unknown'],
    },
    stdp => {
        Name => 'SampleDegradationPriority',
        Flags => ['Binary','Unknown'],
    },
    sdtp => {
        Name => 'IdependentAndDisposableSamples',
        Flags => ['Binary','Unknown'],
    },
    sbgp => {
        Name => 'SampleToGroup',
        Flags => ['Binary','Unknown'],
    },
    sgpd => {
        Name => 'SampleGroupDescription',
        Flags => ['Binary','Unknown'],
    },
    subs => {
        Name => 'Sub-sampleInformation',
        Flags => ['Binary','Unknown'],
    },
    cslg => {
        Name => 'CompositionToDecodeTimelineMapping',
        Flags => ['Binary','Unknown'],
    },
);

# MP4 audio sample description box (ref 5)
%Image::ExifTool::QuickTime::AudioSampleDesc = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Audio' },
    FORMAT => 'int16u',
    NOTES => 'MP4 audio sample description.',
    2  => {
        Name => 'AudioFormat',
        Format => 'undef[4]',
        RawConv => '$val =~ /^[\w ]{4}$/i ? $val : undef',
    },
    10 => { #PH
        Name => 'AudioVendorID',
        Format => 'undef[4]',
        RawConv => '$val eq "\0\0\0\0" ? undef : $val',
        PrintConv => \%vendorID,
        SeparateTable => 'VendorID',
    },
    12 => 'AudioChannels',
    13 => 'AudioBitsPerSample',
    16 => { Name => 'AudioSampleRate', Format => 'fixed32u' },
    28 => { #PH
        Name => 'AudioFormat',
        Format => 'undef[4]',
        RawConv => '$val =~ /^[\w ]{4}$/i ? $val : undef',
        Notes => 'in Casio MOV videos',
    },
);

# MP4 hint sample description box (ref 5)
%Image::ExifTool::QuickTime::HintSampleDesc = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    FORMAT => 'int16u',
    NOTES => 'MP4 hint sample description.',
    2 => { Name => 'HintFormat', Format => 'undef[4]' },
);

# MP4 generic sample description box
%Image::ExifTool::QuickTime::OtherSampleDesc = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    FORMAT => 'int16u',
    2 => { Name => 'OtherFormat', Format => 'undef[4]' },
);

# MP4 data information box (ref 5)
%Image::ExifTool::QuickTime::DataInfo = (
    PROCESS_PROC => \&ProcessMOV,
    NOTES => 'MP4 data information box.',
    dref => {
        Name => 'DataRef',
        SubDirectory => {
            TagTable => 'Image::ExifTool::QuickTime::DataRef',
            Start => 8,
        },
    },
);

# Generic media header
%Image::ExifTool::QuickTime::GenMediaHeader = (
    PROCESS_PROC => \&ProcessMOV,
    gmin => {
        Name => 'GenMediaInfo',
        SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::GenMediaInfo' },
    },
    text => {
        Name => 'Text',
        Flags => ['Binary','Unknown'],
    },
);

# Generic media info (ref http://sourceforge.jp/cvs/view/ntvrec/ntvrec/libqtime/gmin.h?view=co)
%Image::ExifTool::QuickTime::GenMediaInfo = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    0  => 'GenMediaVersion',
    1  => { Name => 'GenFlags',   Format => 'int8u[3]' },
    4  => { Name => 'GenGraphicsMode',
        Format => 'int16u',
        PrintHex => 1,
        SeparateTable => 'GraphicsMode',
        PrintConv => \%graphicsMode,
    },
    6  => { Name => 'GenOpColor', Format => 'int16u[3]' },
    12 => { Name => 'GenBalance', Format => 'fixed16s' },
);

# MP4 data reference box (ref 5)
%Image::ExifTool::QuickTime::DataRef = (
    PROCESS_PROC => \&ProcessMOV,
    NOTES => 'MP4 data reference box.',
    'url ' => {
        Name => 'URL',
        RawConv => q{
            # ignore if self-contained (flags bit 0 set)
            return undef if unpack("N",$val) & 0x01;
            $_ = substr($val,4); s/\0.*//s; $_;
        },
    },
    'urn ' => {
        Name => 'URN',
        RawConv => q{
            return undef if unpack("N",$val) & 0x01;
            $_ = substr($val,4); s/\0.*//s; $_;
        },
    },
);

# MP4 handler box (ref 5)
%Image::ExifTool::QuickTime::Handler = (
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    GROUPS => { 2 => 'Video' },
    4 => { #PH
        Name => 'HandlerClass',
        Format => 'undef[4]',
        RawConv => '$val eq "\0\0\0\0" ? undef : $val',
        PrintConv => {
            mhlr => 'Media Handler',
            dhlr => 'Data Handler',
        },
    },
    8 => {
        Name => 'HandlerType',
        Format => 'undef[4]',
        RawConv => '$$self{HandlerType} = $val unless $val eq "alis"; $val',
        PrintConv => {
            alis => 'Alias Data', #PH
            crsm => 'Clock Reference', #3
            hint => 'Hint Track',
            ipsm => 'IPMP', #3
            m7sm => 'MPEG-7 Stream', #3
            mdir => 'Metadata', #3
            mdta => 'Metadata Tags', #PH
            mjsm => 'MPEG-J', #3
            ocsm => 'Object Content', #3
            odsm => 'Object Descriptor', #3
            sdsm => 'Scene Description', #3
            soun => 'Audio Track',
            text => 'Text', #PH (but what type? subtitle?)
           'url '=> 'URL', #3
            vide => 'Video Track',
        },
    },
    12 => { #PH
        Name => 'HandlerVendorID',
        Format => 'undef[4]',
        RawConv => '$val eq "\0\0\0\0" ? undef : $val',
        PrintConv => \%vendorID,
        SeparateTable => 'VendorID',
    },
    24 => {
        Name => 'HandlerDescription',
        Format => 'string',
        # (sometimes this is a Pascal string, and sometimes it is a C string)
        RawConv => q{
            $val=substr($val,1,ord($1)) if $val=~/^([\0-\x1f])/ and ord($1)<length($val);
            length $val ? $val : undef;
        },
    },
);

# QuickTime composite tags
%Image::ExifTool::QuickTime::Composite = (
    GROUPS => { 2 => 'Video' },
    Rotation => {
        Require => {
            0 => 'QuickTime:MatrixStructure',
            1 => 'QuickTime:HandlerType',
        },
        ValueConv => 'Image::ExifTool::QuickTime::CalcRotation($self)',
    },
);

# add our composite tags
Image::ExifTool::AddCompositeTags('Image::ExifTool::QuickTime');


#------------------------------------------------------------------------------
# Calculate rotation of video track
# Inputs: 0) ExifTool object ref
# Returns: rotation angle or undef
sub CalcRotation($)
{
    my $exifTool = shift;
    my $value = $$exifTool{VALUE};
    my ($i, $track);
    # get the video track family 1 group (ie. "Track1");
    for ($i=0; ; ++$i) {
        my $idx = $i ? " ($i)" : '';
        my $tag = "HandlerType$idx";
        last unless $$value{$tag};
        next unless $$value{$tag} eq 'vide';
        $track = $exifTool->GetGroup($tag, 1);
        last;
    }
    return undef unless $track;
    # get the video track matrix
    for ($i=0; ; ++$i) {
        my $idx = $i ? " ($i)" : '';
        my $tag = "MatrixStructure$idx";
        last unless $$value{$tag};
        next unless $exifTool->GetGroup($tag, 1) eq $track;
        my @a = split ' ', $$value{$tag};
        return undef unless $a[0] or $a[1];
        # calculate the rotation angle (assume uniform rotation)
        my $angle = atan2($a[1], $a[0]) * 180 / 3.14159;
        $angle += 360 if $angle < 0;
        return int($angle * 1000 + 0.5) / 1000;
    }
    return undef;
}

#------------------------------------------------------------------------------
# Determine the average sample rate from a time-to-sample table
# Inputs: 0) ExifTool object ref, 1) time-to-sample table data ref
# Returns: average sample rate (in Hz)
sub CalcSampleRate($$)
{
    my ($exifTool, $valPt) = @_;
    my @dat = unpack('N*', $$valPt);
    my ($num, $dur) = (0, 0);
    my $i;
    for ($i=2; $i<@dat-1; $i+=2) {
        $num += $dat[$i];               # total number of samples
        $dur += $dat[$i] * $dat[$i+1];  # total sample duration
    }
    return undef unless $num and $dur and $$exifTool{MediaTS};
    return $num * $$exifTool{MediaTS} / $dur;
}

#------------------------------------------------------------------------------
# Fix incorrect format for ImageWidth/Height as written by Pentax
sub FixWrongFormat($)
{
    my $val = shift;
    return undef unless $val;
    if ($val & 0xffff0000) {
        $val = unpack('n',pack('N',$val));
    }
    return $val;
}

#------------------------------------------------------------------------------
# Process MPEG-4 MTDT atom (ref 11)
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
# Returns: 1 on success
sub ProcessMetaData($$$)
{
    my ($exifTool, $dirInfo, $tagTablePtr) = @_;
    my $dataPt = $$dirInfo{DataPt};
    my $dirLen = length $$dataPt;
    my $verbose = $exifTool->Options('Verbose');
    return 0 unless $dirLen >= 2;
    my $count = Get16u($dataPt, 0);
    $verbose and $exifTool->VerboseDir('MetaData', $count);
    my $i;
    my $pos = 2;
    for ($i=0; $i<$count; ++$i) {
        last if $pos + 10 > $dirLen;
        my $size = Get16u($dataPt, $pos);
        last if $size < 10 or $size + $pos > $dirLen;
        my $tag  = Get32u($dataPt, $pos + 2);
        my $lang = Get16u($dataPt, $pos + 6);
        my $enc  = Get16u($dataPt, $pos + 8);
        my $val  = substr($$dataPt, $pos + 10, $size);
        my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
        if ($tagInfo) {
            # convert language code to ASCII (ignore read-only bit)
            $lang = pack('C*',(($lang >> 10) & 0x1f) + 0x60,
                              (($lang >>  5) & 0x1f) + 0x60, 
                               ($lang        & 0x1f) + 0x60);
            # handle alternate languages
            if ($lang ne 'und' and $lang ne 'eng' and $lang =~ /^[a-z]+$/) {
                my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang);
                $tagInfo = $langInfo if $langInfo;
            }
            $verbose and $exifTool->VerboseInfo($tag, $tagInfo,
                Value  => $val,
                DataPt => $dataPt,
                Start  => $pos + 10,
                Size   => $size - 10,
            );
            # convert from UTF-16 BE if necessary
            $val = $exifTool->Decode($val, 'UCS2') if $enc == 1;
            if ($enc == 0 and $$tagInfo{Unknown}) {
                # binary data
                $exifTool->FoundTag($tagInfo, \$val);
            } else {
                $exifTool->FoundTag($tagInfo, $val);
            }
        }
        $pos += $size;
    }
    return 1;
}

#------------------------------------------------------------------------------
# Process Meta keys and add tags to the InfoList table ('mdta' handler) (ref PH)
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
# Returns: 1 on success
sub ProcessKeys($$$)
{
    my ($exifTool, $dirInfo, $tagTablePtr) = @_;
    my $dataPt = $$dirInfo{DataPt};
    my $dirLen = length $$dataPt;
    my $out;
    if ($exifTool->Options('Verbose')) {
        $exifTool->VerboseDir('Keys');
        $out = $exifTool->Options('TextOut');
    }
    my $pos = 8;
    my $index = 1;
    my $infoTable = GetTagTable('Image::ExifTool::QuickTime::InfoList');
    while ($pos < $dirLen - 4) {
        my $len = unpack("x${pos}N", $$dataPt);
        last if $len < 4 or $pos + $len > $dirLen;
        delete $$tagTablePtr{$index};
        my $tag = substr($$dataPt, $pos + 4, $len - 4);
        $tag =~ s/\0.*//s; # truncate at null
        $tag =~ s/^mdta//; # remove 'mdta' prefix
        $tag =~ s/^com\.apple\.quicktime\.//;   # remove common apple quicktime domain
        next unless $tag;
        my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
        my $newInfo;
        if (not $tagInfo and $tag =~ /^\w{3}\xa9$/) {
            # I have some samples where the tag is a reversed InfoList or UserData tag ID
            $tag = pack('N', unpack('V', $tag));
            $tagInfo = $exifTool->GetTagInfo($infoTable, $tag);
            unless ($tagInfo) {
                my $tbl = GetTagTable('Image::ExifTool::QuickTime::UserData');
                $tagInfo = $exifTool->GetTagInfo($tbl, $tag);
            }
        }
        if ($tagInfo) {
            $newInfo = {
                Name      => $$tagInfo{Name},
                Format    => $$tagInfo{Format},
                ValueConv => $$tagInfo{ValueConv},
                PrintConv => $$tagInfo{PrintConv},
            };
        } elsif ($tag =~ /^[-\w.]+$/) {
            # create info for tags with reasonable id's
            my $name = $tag;
            $name =~ s/\.(.)/\U$1/g;
            $newInfo = { Name => ucfirst($name) };
        }
        # substitute this tag in the InfoList table with the given index
        delete $$infoTable{$index};
        if ($newInfo) {
            Image::ExifTool::AddTagToTable($infoTable, $index, $newInfo);
            $out and printf $out "%sAdded InfoList Tag 0x%.4x = $tag\n", $exifTool->{INDENT}, $index;
        }
        $pos += $len;
        ++$index;
    }
    return 1;
}

#------------------------------------------------------------------------------
# Process a QuickTime atom
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) optional tag table ref
# Returns: 1 on success
sub ProcessMOV($$;$)
{
    my ($exifTool, $dirInfo, $tagTablePtr) = @_;
    my $raf = $$dirInfo{RAF};
    my $dataPt = $$dirInfo{DataPt};
    my $verbose = $exifTool->Options('Verbose');
    my $dataPos = $$dirInfo{Base} || 0;
    my ($buff, $tag, $size, $track);

    # more convenient to package data as a RandomAccess file
    $raf or $raf = new File::RandomAccess($dataPt);
    # skip leading bytes if necessary
    if ($$dirInfo{DirStart}) {
        $raf->Seek($$dirInfo{DirStart}, 1) or return 0;
        $dataPos += $$dirInfo{DirStart};
    }
    # read size/tag name atom header
    $raf->Read($buff,8) == 8 or return 0;
    $dataPos += 8;
    $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::QuickTime::Main');
    ($size, $tag) = unpack('Na4', $buff);
    if ($dataPt) {
        $verbose and $exifTool->VerboseDir($$dirInfo{DirName});
    } else {
        # check on file type if called with a RAF
        $$tagTablePtr{$tag} or return 0;
        if ($tag eq 'ftyp') {
            # read ahead 4 bytes to see what type of file this is
            my $fileType;
            if ($raf->Read($buff, 4) == 4) {
                $raf->Seek(-4, 1);
                # see if we know the extension for this file type
                $fileType = $1 if $ftypLookup{$buff} and $ftypLookup{$buff} =~ /\(\.(\w+)/;
            }
            $fileType or $fileType = 'MP4'; # default to MP4
            $exifTool->SetFileType($fileType, $mimeLookup{$fileType} || 'video/mp4');
        } else {
            $exifTool->SetFileType();       # MOV
        }
        SetByteOrder('MM');
    }
    for (;;) {
        if ($size < 8) {
            last if $size == 0;
            $size == 1 or $exifTool->Warn('Invalid atom size'), last;
            $raf->Read($buff, 8) == 8 or last;
            $dataPos += 8;
            my ($hi, $lo) = unpack('NN', $buff);
            $size = $lo;
            if ($hi or $lo > 0x7fffffff) {
                if ($hi > 0x7fffffff) {
                    $exifTool->Warn('Invalid atom size');
                    last;
                } elsif (not $exifTool->Options('LargeFileSupport')) {
                    $exifTool->Warn('End of processing at large atom (LargeFileSupport not enabled)');
                    last;
                }
            }
            $size = $hi * 4294967296 + $lo;
        }
        $size -= 8;
        my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
        # allow numerical tag ID's
        unless ($tagInfo) {
            my $num = unpack('N', $tag);
            if ($$tagTablePtr{$num}) {
                $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $num);
                $tag = $num;
            }
        }
        # generate tagInfo if Unknown option set
        if (not defined $tagInfo and ($exifTool->{OPTIONS}->{Unknown} or
            $verbose or $tag =~ /^\xa9/))
        {
            my $name = $tag;
            my $n = ($name =~ s/([\x00-\x1f\x7f-\xff])/'x'.unpack('H*',$1)/eg);
            # print in hex if tag is numerical
            $name = sprintf('0x%.4x',unpack('N',$tag)) if $n > 2;
            if ($name =~ /^xa9(.*)/) {
                $tagInfo = {
                    Name => "UserData_$1",
                    Description => "User Data $1",
                };
            } else {
                $tagInfo = {
                    Name => "Unknown_$name",
                    Description => "Unknown $name",
                    Unknown => 1,
                    Binary => 1,
                };
            }
            Image::ExifTool::AddTagToTable($tagTablePtr, $tag, $tagInfo);
        }
        # load values only if associated with a tag (or verbose) and < 16MB long
        if ((defined $tagInfo or $verbose) and $size < 0x1000000) {
            my $val;
            unless ($raf->Read($val, $size) == $size) {
                $exifTool->Warn("Truncated '$tag' data");
                last;
            }
            # use value to get tag info if necessary
            $tagInfo or $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag, \$val);
            my $hasData = ($$dirInfo{HasData} and $val =~ /^\0...data\0/s);
            if ($verbose and not $hasData) {
                $exifTool->VerboseInfo($tag, $tagInfo,
                    Value => $val,
                    DataPt => \$val,
                    DataPos => $dataPos,
                );
            }
            if ($tagInfo) {
                my $subdir = $$tagInfo{SubDirectory};
                if ($subdir) {
                    my $start = $$subdir{Start} || 0;
                    my %dirInfo = (
                        DataPt   => \$val,
                        DataLen  => $size,
                        DirStart => $start,
                        DirLen   => $size - $start,
                        DirName  => $$tagInfo{Name},
                        HasData  => $$subdir{HasData},
                        DataPos  => 0,
                        # Base needed for IsOffset tags in binary data
                        Base     => $dataPos,
                    );
                    if ($$subdir{ByteOrder} and $$subdir{ByteOrder} =~ /^Little/) {
                        SetByteOrder('II');
                    }
                    my $oldGroup1 = $exifTool->{SET_GROUP1};
                    if ($$tagInfo{Name} eq 'Track') {
                        $track or $track = 0;
                        $exifTool->{SET_GROUP1} = 'Track' . (++$track);
                    }
                    my $subTable = GetTagTable($$subdir{TagTable});
                    my $proc = $$subdir{ProcessProc};
                    $exifTool->ProcessDirectory(\%dirInfo, $subTable, $proc) if $size > $start;
                    $exifTool->{SET_GROUP1} = $oldGroup1;
                    SetByteOrder('MM');
                } elsif ($hasData) {
                    # handle atoms containing 'data' tags
                    my $pos = 0;
                    for (;;) {
                        last if $pos + 16 > $size;
                        my ($len, $type, $flags) = unpack("x${pos}Na4N", $val);
                        last if $pos + $len > $size;
                        my $value;
                        my $format = $$tagInfo{Format};
                        if ($type eq 'data' and $len >= 16) {
                            $pos += 16;
                            $len -= 16;
                            $value = substr($val, $pos, $len);
                            # format flags: 0x0=binary, 0x1=text, 0xd=image,
                            #   0x15=boolean, 0x17=float
                            # (I have a sample where 0x3 looks like string data,
                            #  but this should be verified - PH)
                            if ($format) {
                                # don't apply format to text
                                undef $format if $flags == 0x01;
                            } elsif ($flags == 0x0015) {
                                $format = 'int8u';
                            } elsif ($flags == 0x0017) {
                                $format = 'float';
                            } elsif ($flags == 0) {
                                # read 1 and 2-byte binary as integers
                                if ($len == 1) {
                                    $format = 'int8u',
                                } elsif ($len == 2) {
                                    $format = 'int16u',
                                }
                            }
                            if ($format) {
                                $value = ReadValue(\$value, 0, $format, $$tagInfo{Count}, $len);
                            } elsif ($flags != 0x01 and $flags != 0x03 and not $$tagInfo{ValueConv}) {
                                # make binary data a scalar reference unless a ValueConv exists
                                my $buf = $value;
                                $value = \$buf;
                            }
                        }
                        $exifTool->VerboseInfo($tag, $tagInfo,
                            Value   => ref $value ? $$value : $value,
                            DataPt  => \$val,
                            DataPos => $dataPos,
                            Start   => $pos,
                            Size    => $len,
                            Format  => $format,
                            Extra   => sprintf(", Type='$type', Flags=0x%x",$flags)
                        ) if $verbose;
                        $exifTool->FoundTag($tagInfo, $value) if defined $value;
                        $pos += $len;
                    }
                } else {
                    if ($tag =~ /^\xa9/) {
                        # parse international text to extract first string
                        my $len = unpack('n', $val);
                        # $len should include 4 bytes for length and type words,
                        # but Pentax and Kodak forget to add these in, so allow for this
                        $len += 4 if $len <= $size - 4;
                        $val = substr($val, 4, $len - 4) if $len <= $size;
                    } elsif ($$tagInfo{Format}) {
                        $val = ReadValue(\$val, 0, $$tagInfo{Format}, $$tagInfo{Count}, length($val));
                    }
                    $exifTool->FoundTag($tagInfo, $val);
                }
            }
        } else {
            $raf->Seek($size, 1) or $exifTool->Warn("Truncated '$tag' data"), last;
        }
        $raf->Read($buff, 8) == 8 or last;
        $dataPos += $size + 8;
        ($size, $tag) = unpack('Na4', $buff);
    }
    return 1;
}

#------------------------------------------------------------------------------
# Process a QuickTime Image File
# Inputs: 0) ExifTool object reference, 1) directory information reference
# Returns: 1 on success
sub ProcessQTIF($$)
{
    my ($exifTool, $dirInfo) = @_;
    my $table = GetTagTable('Image::ExifTool::QuickTime::ImageFile');
    return ProcessMOV($exifTool, $dirInfo, $table);
}

1;  # end

__END__

=head1 NAME

Image::ExifTool::QuickTime - Read QuickTime and MP4 meta information

=head1 SYNOPSIS

This module is used by Image::ExifTool

=head1 DESCRIPTION

This module contains routines required by Image::ExifTool to extract
information from QuickTime and MP4 video, and M4A audio files.

=head1 AUTHOR

Copyright 2003-2010, Phil Harvey (phil at owl.phy.queensu.ca)

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=head1 REFERENCES

=over 4

=item L<http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html>

=item L<http://search.cpan.org/dist/MP4-Info-1.04/>

=item L<http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt>

=item L<http://wiki.multimedia.cx/index.php?title=Apple_QuickTime>

=item L<http://atomicparsley.sourceforge.net/mpeg-4files.html>

=item L<http://wiki.multimedia.cx/index.php?title=QuickTime_container>

=item L<http://code.google.com/p/mp4v2/wiki/iTunesMetadata>

=item L<http://www.canieti.com.mx/assets/files/1011/IEC_100_1384_DC.pdf>

=back

=head1 SEE ALSO

L<Image::ExifTool::TagNames/QuickTime Tags>,
L<Image::ExifTool(3pm)|Image::ExifTool>

=cut

