<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at http://getid3.sourceforge.net                 //
//            or http://www.getid3.org                         //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details                             //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio-video.quicktime.php                            //
// module for analyzing Quicktime and MP3-in-MP4 files         //
// dependencies: module.audio.mp3.php                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);

class
getid3_quicktime
{

    function
getid3_quicktime(&$fd, &$ThisFileInfo, $ReturnAtomData=true, $ParseAllPossibleAtoms=false) {

        
$ThisFileInfo['fileformat'] = 'quicktime';
        
$ThisFileInfo['quicktime']['hinting'] = false;

        
fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET);

        
$offset      = 0;
        
$atomcounter = 0;

        while (
$offset < $ThisFileInfo['avdataend']) {
            
fseek($fd, $offset, SEEK_SET);
            
$AtomHeader = fread($fd, 8);

            
$atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4));
            
$atomname =               substr($AtomHeader, 4, 4);
            
$ThisFileInfo['quicktime'][$atomname]['name']   = $atomname;
            
$ThisFileInfo['quicktime'][$atomname]['size']   = $atomsize;
            
$ThisFileInfo['quicktime'][$atomname]['offset'] = $offset;

            if ((
$offset + $atomsize) > $ThisFileInfo['avdataend']) {
                
$ThisFileInfo['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)';
                return
false;
            }

            if (
$atomsize == 0) {
                
// Furthermore, for historical reasons the list of atoms is optionally
                // terminated by a 32-bit integer set to 0. If you are writing a program
                // to read user data atoms, you should allow for the terminating 0.
                
break;
            }
            switch (
$atomname) {
                case
'mdat': // Media DATa atom
                    // 'mdat' contains the actual data for the audio/video
                    
if (($atomsize > 8) && (!isset($ThisFileInfo['avdataend_tmp']) || ($ThisFileInfo['quicktime'][$atomname]['size'] > ($ThisFileInfo['avdataend_tmp'] - $ThisFileInfo['avdataoffset'])))) {

                        
$ThisFileInfo['avdataoffset'] = $ThisFileInfo['quicktime'][$atomname]['offset'] + 8;
                        
$OldAVDataEnd                 = $ThisFileInfo['avdataend'];
                        
$ThisFileInfo['avdataend']    = $ThisFileInfo['quicktime'][$atomname]['offset'] + $ThisFileInfo['quicktime'][$atomname]['size'];

                        if (
getid3_mp3::MPEGaudioHeaderValid(getid3_mp3::MPEGaudioHeaderDecode(fread($fd, 4)))) {
                            
getid3_mp3::getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $ThisFileInfo['avdataoffset'], false);
                            if (isset(
$ThisFileInfo['mpeg']['audio'])) {
                                
$ThisFileInfo['audio']['dataformat']   = 'mp3';
                                
$ThisFileInfo['audio']['codec']        = (!empty($ThisFileInfo['mpeg']['audio']['encoder']) ? $ThisFileInfo['mpeg']['audio']['encoder'] : (!empty($ThisFileInfo['mpeg']['audio']['codec']) ? $ThisFileInfo['mpeg']['audio']['codec'] : (!empty($ThisFileInfo['mpeg']['audio']['LAME']) ? 'LAME' :'mp3')));
                                
$ThisFileInfo['audio']['sample_rate']  = $ThisFileInfo['mpeg']['audio']['sample_rate'];
                                
$ThisFileInfo['audio']['channels']     = $ThisFileInfo['mpeg']['audio']['channels'];
                                
$ThisFileInfo['audio']['bitrate']      = $ThisFileInfo['mpeg']['audio']['bitrate'];
                                
$ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']);
                                
$ThisFileInfo['bitrate']               = $ThisFileInfo['audio']['bitrate'];
                            }
                        }
                        
$ThisFileInfo['avdataend'] = $OldAVDataEnd;
                        unset(
$OldAVDataEnd);

                    }
                    break;

                case
'free': // FREE space atom
                
case 'skip': // SKIP atom
                
case 'wide': // 64-bit expansion placeholder atom
                    // 'free', 'skip' and 'wide' are just padding, contains no useful data at all
                    
break;

                default:
                    
$atomHierarchy = array();
                    
$ThisFileInfo['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($fd, $atomsize), $ThisFileInfo, $offset, $atomHierarchy, $ParseAllPossibleAtoms);
                    break;
            }

            
$offset += $atomsize;
            
$atomcounter++;
        }

        if (!empty(
$ThisFileInfo['avdataend_tmp'])) {
            
// this value is assigned to a temp value and then erased because
            // otherwise any atoms beyond the 'mdat' atom would not get parsed
            
$ThisFileInfo['avdataend'] = $ThisFileInfo['avdataend_tmp'];
            unset(
$ThisFileInfo['avdataend_tmp']);
        }

        if (!isset(
$ThisFileInfo['bitrate']) && isset($ThisFileInfo['playtime_seconds'])) {
            
$ThisFileInfo['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds'];
        }
        if (isset(
$ThisFileInfo['bitrate']) && !isset($ThisFileInfo['audio']['bitrate']) && !isset($ThisFileInfo['quicktime']['video'])) {
            
$ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['bitrate'];
        }

        if ((
$ThisFileInfo['audio']['dataformat'] == 'mp4') && empty($ThisFileInfo['video']['resolution_x'])) {
            
$ThisFileInfo['fileformat'] = 'mp4';
            
$ThisFileInfo['mime_type']  = 'audio/mp4';
            unset(
$ThisFileInfo['video']['dataformat']);
        }

        if (!
$ReturnAtomData) {
            unset(
$ThisFileInfo['quicktime']['moov']);
        }

        if (empty(
$ThisFileInfo['audio']['dataformat']) && !empty($ThisFileInfo['quicktime']['audio'])) {
            
$ThisFileInfo['audio']['dataformat'] = 'quicktime';
        }
        if (empty(
$ThisFileInfo['video']['dataformat']) && !empty($ThisFileInfo['quicktime']['video'])) {
            
$ThisFileInfo['video']['dataformat'] = 'quicktime';
        }

        return
true;
    }

    function
QuicktimeParseAtom($atomname, $atomsize, $atomdata, &$ThisFileInfo, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
        
// http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm

        
array_push($atomHierarchy, $atomname);
        
$atomstructure['hierarchy'] = implode(' ', $atomHierarchy);
        
$atomstructure['name']      = $atomname;
        
$atomstructure['size']      = $atomsize;
        
$atomstructure['offset']    = $baseoffset;

        switch (
$atomname) {
            case
'moov': // MOVie container atom
            
case 'trak': // TRAcK container atom
            
case 'clip': // CLIPping container atom
            
case 'matt': // track MATTe container atom
            
case 'edts': // EDiTS container atom
            
case 'tref': // Track REFerence container atom
            
case 'mdia': // MeDIA container atom
            
case 'minf': // Media INFormation container atom
            
case 'dinf': // Data INFormation container atom
            
case 'udta': // User DaTA container atom
            
case 'stbl': // Sample TaBLe container atom
            
case 'cmov': // Compressed MOVie container atom
            
case 'rmra': // Reference Movie Record Atom
            
case 'rmda': // Reference Movie Descriptor Atom
            
case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR)
                
$atomstructure['subatoms'] = $this->QuicktimeParseContainerAtom($atomdata, $ThisFileInfo, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
                break;


            case
'妾py':
            case
'妻ay':
            case
'妻ir':
            case
'委d1':
            case
'委d2':
            case
'委d3':
            case
'委d4':
            case
'委d5':
            case
'委d6':
            case
'委d7':
            case
'委d8':
            case
'委d9':
            case
'妹mt':
            case
'姆nf':
            case
'妳rd':
            case
'妳rf':
            case
'姅eq':
            case
'孟rc':
            case
'定rt':
            case
'姊am':
            case
'妾mt':
            case
'定rn':
            case
'姑st':
            case
'姓ak':
            case
'姓od':
            case
'周RD':
            case
'孟wr':
            case
'奄ut':
            case
'呸RT':
            case
'孤rk':
            case
'奄lb':
            case
'妾om':
            case
'妮en':
            case
'妯pe':
            case
'季rl':
            case
'委nc':
                
$atomstructure['data_length'] = getid3_lib::BigEndian2Int(substr($atomdata,  0, 2));
                
$atomstructure['language_id'] = getid3_lib::BigEndian2Int(substr($atomdata,  2, 2));
                
$atomstructure['data']        =                           substr($atomdata,  4);

                
$atomstructure['language']    = $this->QuicktimeLanguageLookup($atomstructure['language_id']);
                if (empty(
$ThisFileInfo['comments']['language']) || (!in_array($atomstructure['language'], $ThisFileInfo['comments']['language']))) {
                    
$ThisFileInfo['comments']['language'][] = $atomstructure['language'];
                }
                
$this->CopyToAppropriateCommentsSection($atomname, $atomstructure['data'], $ThisFileInfo);
                break;


            case
'play': // auto-PLAY atom
                
$atomstructure['autoplay']             = (bool) getid3_lib::BigEndian2Int(substr($atomdata,  0, 1));

                
$ThisFileInfo['quicktime']['autoplay'] = $atomstructure['autoplay'];
                break;


            case
'WLOC': // Window LOCation atom
                
$atomstructure['location_x']  = getid3_lib::BigEndian2Int(substr($atomdata,  0, 2));
                
$atomstructure['location_y']  = getid3_lib::BigEndian2Int(substr($atomdata,  2, 2));
                break;


            case
'LOOP': // LOOPing atom
            
case 'SelO': // play SELection Only atom
            
case 'AllF': // play ALL Frames atom
                
$atomstructure['data'] = getid3_lib::BigEndian2Int($atomdata);
                break;


            case
'name': //
            
case 'MCPS': // Media Cleaner PRo
            
case '@PRM': // adobe PReMiere version
            
case '@PRQ': // adobe PRemiere Quicktime version
                
$atomstructure['data'] = $atomdata;
                break;


            case
'cmvd': // Compressed MooV Data atom
                // Code by ubergeek啽bergeek*tv based on information from
                // http://developer.apple.com/quicktime/icefloe/dispatch012.html
                
$atomstructure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 4));

                
$CompressedFileData = substr($atomdata, 4);
                if (
$UncompressedHeader = @gzuncompress($CompressedFileData)) {
                    
$atomstructure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, $ThisFileInfo, 0, $atomHierarchy, $ParseAllPossibleAtoms);
                } else {
                    
$ThisFileInfo['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atomstructure['offset'];
                }
                break;


            case
'dcom': // Data COMpression atom
                
$atomstructure['compression_id']   = $atomdata;
                
$atomstructure['compression_text'] = $this->QuicktimeDCOMLookup($atomdata);
                break;


            case
'rdrf': // Reference movie Data ReFerence atom
                
$atomstructure['version']                = getid3_lib::BigEndian2Int(substr($atomdata,  0, 1));
                
$atomstructure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atomdata,  1, 3));
                
$atomstructure['flags']['internal_data'] = (bool) ($atomstructure['flags_raw'] & 0x000001);

                
$atomstructure['reference_type_name']    =                           substr($atomdata,  4, 4);
                
$atomstructure['reference_length']       = getid3_lib::BigEndian2Int(substr($atomdata,  8, 4));
                switch (
$atomstructure['reference_type_name']) {
                    case
'url ':
                        
$atomstructure['url']            =       $this->NoNullString(substr($atomdata, 12));
                        break;

                    case
'alis':
                        
$atomstructure['file_alias']     =                           substr($atomdata, 12);
                        break;

                    case
'rsrc':
                        
$atomstructure['resource_alias'] =                           substr($atomdata, 12);
                        break;

                    default:
                        
$atomstructure['data']           =                           substr($atomdata, 12);
                        break;
                }
                break;


            case
'rmqu': // Reference Movie QUality atom
                
$atomstructure['movie_quality'] = getid3_lib::BigEndian2Int($atomdata);
                break;


            case
'rmcs': // Reference Movie Cpu Speed atom
                
$atomstructure['version']          = getid3_lib::BigEndian2Int(substr($atomdata,  0, 1));
                
$atomstructure['flags_raw']        = getid3_lib::BigEndian2Int(substr($atomdata,  1, 3)); // hardcoded: 0x0000
                
$atomstructure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atomdata,  4, 2));
                break;


            case
'rmvc': // Reference Movie Version Check atom
                
$atomstructure['version']            = getid3_lib::BigEndian2Int(substr($atomdata,  0, 1));
                
$atomstructure['flags_raw']          = getid3_lib::BigEndian2Int(substr($atomdata,  1, 3)); // hardcoded: 0x0000
                
$atomstructure['gestalt_selector']   =                           substr($atomdata,  4, 4);
                
$atomstructure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atomdata,  8, 4));
                
$atomstructure['gestalt_value']      = getid3_lib::BigEndian2Int(substr($atomdata, 12, 4));
                
$atomstructure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atomdata, 14, 2));
                break;


            case
'rmcd': // Reference Movie Component check atom
                
$atomstructure['version']                = getid3_lib::BigEndian2Int(substr($atomdata,  0, 1));
                
$atomstructure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atomdata,  1, 3)); // hardcoded: 0x0000
                
$atomstructure['component_type']         =                           substr($atomdata,  4, 4);
                
$atomstructure['component_subtype']      =                           substr($atomdata,  8, 4);
                
$atomstructure['component_manufacturer'] =                           substr($atomdata, 12, 4);
                
$atomstructure['component_flags_raw']    = getid3_lib::BigEndian2Int(substr($atomdata, 16, 4));
                
$atomstructure['component_flags_mask']   = getid3_lib::BigEndian2Int(substr($atomdata, 20, 4));
                
$atomstructure['component_min_version']  = getid3_lib::BigEndian2Int(substr($atomdata, 24, 4));
                break;


            case
'rmdr': // Reference Movie Data Rate atom
                
$atomstructure['version']       = getid3_lib::BigEndian2Int(substr($atomdata,  0, 1));
                
$atomstructure['flags_raw']     = getid3_lib::BigEndian2Int(substr($atomdata,  1, 3)); // hardcoded: 0x0000
                
$atomstructure['data_rate']     = getid3_lib::BigEndian2Int(substr($atomdata,  4, 4));

                
$atomstructure['data_rate_bps'] = $atomstructure['data_rate'] * 10;
                break;


            case
'rmla': // Reference Movie Language Atom
                
$atomstructure['version']     = getid3_lib::BigEndian2Int(substr($atomdata,  0, 1));
                
$atomstructure['flags_raw']   = getid3_lib::BigEndian2Int(substr($atomdata,  1, 3)); // hardcoded: 0x0000
                
$atomstructure['language_id'] = getid3_lib::BigEndian2Int(substr($atomdata,  4, 2));

                
$atomstructure['language']    = $this->QuicktimeLanguageLookup($atomstructure['language_id']);
                if (empty(
$ThisFileInfo['comments']['language']) || (!in_array($atomstructure['language'], $ThisFileInfo['comments']['language']))) {
                    
$ThisFileInfo['comments']['language'][] = $atomstructure['language'];
                }
                break;


            case
'rmla': // Reference Movie Language Atom
                
$atomstructure['version']   = getid3_lib::BigEndian2Int(substr($atomdata,  0, 1));
                
$atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata,  1, 3)); // hardcoded: 0x0000
                
$atomstructure['track_id']  = getid3_lib::BigEndian2Int(substr($atomdata,  4, 2));
                break;


            case
'ptv ': // Print To Video - defines a movie's full screen mode
                // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm
                
$atomstructure['display_size_raw']  = getid3_lib::BigEndian2Int(substr($atomdata, 0, 2));
                
$atomstructure['reserved_1']        = getid3_lib::BigEndian2Int(substr($atomdata, 2, 2)); // hardcoded: 0x0000
                
$atomstructure['reserved_2']        = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); // hardcoded: 0x0000
                
$atomstructure['slide_show_flag']   = getid3_lib::BigEndian2Int(substr($atomdata, 6, 1));
                
$atomstructure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atomdata, 7, 1));

                
$atomstructure['flags']['play_on_open'] = (bool) $atomstructure['play_on_open_flag'];
                
$atomstructure['flags']['slide_show']   = (bool) $atomstructure['slide_show_flag'];

                
$ptv_lookup[0] = 'normal';
                
$ptv_lookup[1] = 'double';
                
$ptv_lookup[2] = 'half';
                
$ptv_lookup[3] = 'full';
                
$ptv_lookup[4] = 'current';
                if (isset(
$ptv_lookup[$atomstructure['display_size_raw']])) {
                    
$atomstructure['display_size'] = $ptv_lookup[$atomstructure['display_size_raw']];
                } else {
                    
$ThisFileInfo['warning'][] = 'unknown "ptv " display constant ('.$atomstructure['display_size_raw'].')';
                }
                break;


            case
'stsd': // Sample Table Sample Description atom
                
$atomstructure['version']        = getid3_lib::BigEndian2Int(substr($atomdata,  0, 1));
                
$atomstructure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atomdata,  1, 3)); // hardcoded: 0x0000
                
$atomstructure['number_entries'] = getid3_lib::BigEndian2Int(substr($atomdata,  4, 4));
                
$stsdEntriesDataOffset = 8;
                for (
$i = 0; $i < $atomstructure['number_entries']; $i++) {
                    
$atomstructure['sample_description_table'][$i]['size']             = getid3_lib::BigEndian2Int(substr($atomdata, $stsdEntriesDataOffset, 4));
                    
$stsdEntriesDataOffset += 4;
                    
$atomstructure['sample_description_table'][$i]['data_format']      =                           substr($atomdata, $stsdEntriesDataOffset, 4);
                    
$stsdEntriesDataOffset += 4;
                    
$atomstructure['sample_description_table'][$i]['reserved']         = getid3_lib::BigEndian2Int(substr($atomdata, $stsdEntriesDataOffset, 6));
                    
$stsdEntriesDataOffset += 6;
                    
$atomstructure['sample_description_table'][$i]['reference_index']  = getid3_lib::BigEndian2Int(substr($atomdata, $stsdEntriesDataOffset, 2));
                    
$stsdEntriesDataOffset += 2;
                    
$atomstructure['sample_description_table'][$i]['data']             =                           substr($atomdata, $stsdEntriesDataOffset, ($atomstructure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2));
                    
$stsdEntriesDataOffset += ($atomstructure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2);

                    
$atomstructure['sample_description_table'][$i]['encoder_version']  = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'],  0, 2));
                    
$atomstructure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'],  2, 2));
                    
$atomstructure['sample_description_table'][$i]['encoder_vendor']   =                           substr($atomstructure['sample_description_table'][$i]['data'],  4, 4);

                    switch (
$atomstructure['sample_description_table'][$i]['encoder_vendor']) {

                        case
"\x00\x00\x00\x00":
                            
// audio atom
                            
$atomstructure['sample_description_table'][$i]['audio_channels']       =   getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'],  8,  2));
                            
$atomstructure['sample_description_table'][$i]['audio_bit_depth']      =   getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 10,  2));
                            
$atomstructure['sample_description_table'][$i]['audio_compression_id'] =   getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 12,  2));
                            
$atomstructure['sample_description_table'][$i]['audio_packet_size']    =   getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 14,  2));
                            
$atomstructure['sample_description_table'][$i]['audio_sample_rate']    = getid3_lib::FixedPoint16_16(substr($atomstructure['sample_description_table'][$i]['data'], 16,  4));

                            switch (
$atomstructure['sample_description_table'][$i]['data_format']) {
                                case
'mp4v':
                                    
$ThisFileInfo['fileformat'] = 'mp4';
                                    
$ThisFileInfo['error'][] = 'This version ('.GETID3_VERSION.') of getID3() does not fully support MPEG-4 audio/video streams';
                                    break;

                                case
'qtvr':
                                    
$ThisFileInfo['video']['dataformat'] = 'quicktimevr';
                                    break;

                                case
'mp4a':
                                default:
                                    
$ThisFileInfo['quicktime']['audio']['codec']       = $this->QuicktimeAudioCodecLookup($atomstructure['sample_description_table'][$i]['data_format']);
                                    
$ThisFileInfo['quicktime']['audio']['sample_rate'] = $atomstructure['sample_description_table'][$i]['audio_sample_rate'];
                                    
$ThisFileInfo['quicktime']['audio']['channels']    = $atomstructure['sample_description_table'][$i]['audio_channels'];
                                    
$ThisFileInfo['quicktime']['audio']['bit_depth']   = $atomstructure['sample_description_table'][$i]['audio_bit_depth'];
                                    
$ThisFileInfo['audio']['codec']                    = $ThisFileInfo['quicktime']['audio']['codec'];
                                    
$ThisFileInfo['audio']['sample_rate']              = $ThisFileInfo['quicktime']['audio']['sample_rate'];
                                    
$ThisFileInfo['audio']['channels']                 = $ThisFileInfo['quicktime']['audio']['channels'];
                                    
$ThisFileInfo['audio']['bits_per_sample']          = $ThisFileInfo['quicktime']['audio'