Make WordPress Core


Ignore:
Timestamp:
07/31/2017 07:49:31 PM (8 years ago)
Author:
wonderboymusic
Message:

Media: update the getID3 library to version 1.9.14 to avoid fatal errors in PHP7.

Props MyThemeShop for the initial patch.
Fixes #41496.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/ID3/module.audio-video.quicktime.php

    r32979 r41196  
    3636        $offset      = 0;
    3737        $atomcounter = 0;
    38         $atom_data_read_buffer_size = ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 2) : $this->getid3->option_fread_buffer_size * 1024); // allow [default: 32MB] if PHP configured with no memory_limit
     38        $atom_data_read_buffer_size = max($this->getid3->option_fread_buffer_size * 1024, ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : 1024)); // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB]
    3939        while ($offset < $info['avdataend']) {
    4040            if (!getid3_lib::intValueSupported($offset)) {
    41                 $info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions';
     41                $this->error('Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions');
    4242                break;
    4343            }
     
    5858
    5959            if (($offset + $atomsize) > $info['avdataend']) {
    60                 $info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)';
     60                $this->error('Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)');
    6161                return false;
    6262            }
     
    8080            $info['avdataend'] = $info['avdataend_tmp'];
    8181            unset($info['avdataend_tmp']);
     82        }
     83
     84        if (!empty($info['quicktime']['comments']['chapters']) && is_array($info['quicktime']['comments']['chapters']) && (count($info['quicktime']['comments']['chapters']) > 0)) {
     85            $durations = $this->quicktime_time_to_sample_table($info);
     86            for ($i = 0; $i < count($info['quicktime']['comments']['chapters']); $i++) {
     87                $bookmark = array();
     88                $bookmark['title'] = $info['quicktime']['comments']['chapters'][$i];
     89                if (isset($durations[$i])) {
     90                    $bookmark['duration_sample'] = $durations[$i]['sample_duration'];
     91                    if ($i > 0) {
     92                        $bookmark['start_sample'] = $info['quicktime']['bookmarks'][($i - 1)]['start_sample'] + $info['quicktime']['bookmarks'][($i - 1)]['duration_sample'];
     93                    } else {
     94                        $bookmark['start_sample'] = 0;
     95                    }
     96                    if ($time_scale = $this->quicktime_bookmark_time_scale($info)) {
     97                        $bookmark['duration_seconds'] = $bookmark['duration_sample'] / $time_scale;
     98                        $bookmark['start_seconds']    = $bookmark['start_sample']    / $time_scale;
     99                    }
     100                }
     101                $info['quicktime']['bookmarks'][] = $bookmark;
     102            }
     103        }
     104
     105        if (isset($info['quicktime']['temp_meta_key_names'])) {
     106            unset($info['quicktime']['temp_meta_key_names']);
     107        }
     108
     109        if (!empty($info['quicktime']['comments']['location.ISO6709'])) {
     110            // https://en.wikipedia.org/wiki/ISO_6709
     111            foreach ($info['quicktime']['comments']['location.ISO6709'] as $ISO6709string) {
     112                $latitude  = false;
     113                $longitude = false;
     114                $altitude  = false;
     115                if (preg_match('#^([\\+\\-])([0-9]{2}|[0-9]{4}|[0-9]{6})(\\.[0-9]+)?([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?(([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?)?/$#', $ISO6709string, $matches)) {
     116                    @list($dummy, $lat_sign, $lat_deg, $lat_deg_dec, $lon_sign, $lon_deg, $lon_deg_dec, $dummy, $alt_sign, $alt_deg, $alt_deg_dec) = $matches;
     117
     118                    if (strlen($lat_deg) == 2) {        // [+-]DD.D
     119                        $latitude = floatval(ltrim($lat_deg, '0').$lat_deg_dec);
     120                    } elseif (strlen($lat_deg) == 4) {  // [+-]DDMM.M
     121                        $latitude = floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0').$lat_deg_dec / 60);
     122                    } elseif (strlen($lat_deg) == 6) {  // [+-]DDMMSS.S
     123                        $latitude = floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec / 3600);
     124                    }
     125
     126                    if (strlen($lon_deg) == 3) {        // [+-]DDD.D
     127                        $longitude = floatval(ltrim($lon_deg, '0').$lon_deg_dec);
     128                    } elseif (strlen($lon_deg) == 5) {  // [+-]DDDMM.M
     129                        $longitude = floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0').$lon_deg_dec / 60);
     130                    } elseif (strlen($lon_deg) == 7) {  // [+-]DDDMMSS.S
     131                        $longitude = floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec / 3600);
     132                    }
     133
     134                    if (strlen($alt_deg) == 3) {        // [+-]DDD.D
     135                        $altitude = floatval(ltrim($alt_deg, '0').$alt_deg_dec);
     136                    } elseif (strlen($alt_deg) == 5) {  // [+-]DDDMM.M
     137                        $altitude = floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0').$alt_deg_dec / 60);
     138                    } elseif (strlen($alt_deg) == 7) {  // [+-]DDDMMSS.S
     139                        $altitude = floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec / 3600);
     140                    }
     141
     142                    if ($latitude !== false) {
     143                        $info['quicktime']['comments']['gps_latitude'][]  = (($lat_sign == '-') ? -1 : 1) * floatval($latitude);
     144                    }
     145                    if ($longitude !== false) {
     146                        $info['quicktime']['comments']['gps_longitude'][] = (($lon_sign == '-') ? -1 : 1) * floatval($longitude);
     147                    }
     148                    if ($altitude !== false) {
     149                        $info['quicktime']['comments']['gps_altitude'][]  = (($alt_sign == '-') ? -1 : 1) * floatval($altitude);
     150                    }
     151                }
     152                if ($latitude === false) {
     153                    $this->warning('location.ISO6709 string not parsed correctly: "'.$ISO6709string.'", please submit as a bug');
     154                }
     155                break;
     156            }
    82157        }
    83158
     
    99174            }
    100175        }
    101         if (($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) {
     176        if ($info['audio']['dataformat'] == 'mp4') {
    102177            $info['fileformat'] = 'mp4';
    103             $info['mime_type']  = 'audio/mp4';
    104             unset($info['video']['dataformat']);
     178            if (empty($info['video']['resolution_x'])) {
     179                $info['mime_type']  = 'audio/mp4';
     180                unset($info['video']['dataformat']);
     181            } else {
     182                $info['mime_type']  = 'video/mp4';
     183            }
    105184        }
    106185
     
    121200    public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
    122201        // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm
     202        // https://code.google.com/p/mp4v2/wiki/iTunesMetadata
    123203
    124204        $info = &$this->getid3->info;
     
    223303
    224304
     305            case "\xA9".'alb': // ALBum
     306            case "\xA9".'ART': //
     307            case "\xA9".'art': // ARTist
     308            case "\xA9".'aut': //
     309            case "\xA9".'cmt': // CoMmenT
     310            case "\xA9".'com': // COMposer
     311            case "\xA9".'cpy': //
     312            case "\xA9".'day': // content created year
     313            case "\xA9".'dir': //
     314            case "\xA9".'ed1': //
     315            case "\xA9".'ed2': //
     316            case "\xA9".'ed3': //
     317            case "\xA9".'ed4': //
     318            case "\xA9".'ed5': //
     319            case "\xA9".'ed6': //
     320            case "\xA9".'ed7': //
     321            case "\xA9".'ed8': //
     322            case "\xA9".'ed9': //
     323            case "\xA9".'enc': //
     324            case "\xA9".'fmt': //
     325            case "\xA9".'gen': // GENre
     326            case "\xA9".'grp': // GRouPing
     327            case "\xA9".'hst': //
     328            case "\xA9".'inf': //
     329            case "\xA9".'lyr': // LYRics
     330            case "\xA9".'mak': //
     331            case "\xA9".'mod': //
     332            case "\xA9".'nam': // full NAMe
     333            case "\xA9".'ope': //
     334            case "\xA9".'PRD': //
     335            case "\xA9".'prf': //
     336            case "\xA9".'req': //
     337            case "\xA9".'src': //
     338            case "\xA9".'swr': //
     339            case "\xA9".'too': // encoder
     340            case "\xA9".'trk': // TRacK
     341            case "\xA9".'url': //
     342            case "\xA9".'wrn': //
     343            case "\xA9".'wrt': // WRiTer
     344            case '----': // itunes specific
    225345            case 'aART': // Album ARTist
     346            case 'akID': // iTunes store account type
     347            case 'apID': // Purchase Account
     348            case 'atID': //
    226349            case 'catg': // CaTeGory
     350            case 'cmID': //
     351            case 'cnID': //
    227352            case 'covr': // COVeR artwork
    228353            case 'cpil': // ComPILation
     
    231356            case 'disk': // DISK number
    232357            case 'egid': // Episode Global ID
     358            case 'geID': //
    233359            case 'gnre': // GeNRE
     360            case 'hdvd': // HD ViDeo
    234361            case 'keyw': // KEYWord
    235             case 'ldes':
     362            case 'ldes': // Long DEScription
    236363            case 'pcst': // PodCaST
    237364            case 'pgap': // GAPless Playback
     365            case 'plID': //
    238366            case 'purd': // PURchase Date
    239367            case 'purl': // Podcast URL
    240             case 'rati':
    241             case 'rndu':
    242             case 'rpdu':
     368            case 'rati': //
     369            case 'rndu': //
     370            case 'rpdu': //
    243371            case 'rtng': // RaTiNG
    244             case 'stik':
     372            case 'sfID': // iTunes store country
     373            case 'soaa': // SOrt Album Artist
     374            case 'soal': // SOrt ALbum
     375            case 'soar': // SOrt ARtist
     376            case 'soco': // SOrt COmposer
     377            case 'sonm': // SOrt NaMe
     378            case 'sosn': // SOrt Show Name
     379            case 'stik': //
    245380            case 'tmpo': // TeMPO (BPM)
    246381            case 'trkn': // TRacK Number
     382            case 'tven': // tvEpisodeID
    247383            case 'tves': // TV EpiSode
    248384            case 'tvnn': // TV Network Name
    249385            case 'tvsh': // TV SHow Name
    250386            case 'tvsn': // TV SeasoN
    251             case 'akID': // iTunes store account type
    252             case 'apID':
    253             case 'atID':
    254             case 'cmID':
    255             case 'cnID':
    256             case 'geID':
    257             case 'plID':
    258             case 'sfID': // iTunes store country
    259             case "\xA9".'alb': // ALBum
    260             case "\xA9".'art': // ARTist
    261             case "\xA9".'ART':
    262             case "\xA9".'aut':
    263             case "\xA9".'cmt': // CoMmenT
    264             case "\xA9".'com': // COMposer
    265             case "\xA9".'cpy':
    266             case "\xA9".'day': // content created year
    267             case "\xA9".'dir':
    268             case "\xA9".'ed1':
    269             case "\xA9".'ed2':
    270             case "\xA9".'ed3':
    271             case "\xA9".'ed4':
    272             case "\xA9".'ed5':
    273             case "\xA9".'ed6':
    274             case "\xA9".'ed7':
    275             case "\xA9".'ed8':
    276             case "\xA9".'ed9':
    277             case "\xA9".'enc':
    278             case "\xA9".'fmt':
    279             case "\xA9".'gen': // GENre
    280             case "\xA9".'grp': // GRouPing
    281             case "\xA9".'hst':
    282             case "\xA9".'inf':
    283             case "\xA9".'lyr': // LYRics
    284             case "\xA9".'mak':
    285             case "\xA9".'mod':
    286             case "\xA9".'nam': // full NAMe
    287             case "\xA9".'ope':
    288             case "\xA9".'PRD':
    289             case "\xA9".'prd':
    290             case "\xA9".'prf':
    291             case "\xA9".'req':
    292             case "\xA9".'src':
    293             case "\xA9".'swr':
    294             case "\xA9".'too': // encoder
    295             case "\xA9".'trk': // TRacK
    296             case "\xA9".'url':
    297             case "\xA9".'wrn':
    298             case "\xA9".'wrt': // WRiTer
    299             case '----': // itunes specific
    300387                if ($atom_parent == 'udta') {
    301388                    // User data atom handler
     
    319406                            $boxsmalldata =                           substr($atom_data, $atomoffset + 4, $boxsmallsize);
    320407                            if ($boxsmallsize <= 1) {
    321                                 $info['warning'][] = 'Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset);
     408                                $this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
    322409                                $atom_structure['data'] = null;
    323410                                $atomoffset = strlen($atom_data);
     
    329416                                    break;
    330417                                default:
    331                                     $info['warning'][] = 'Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset;
     418                                    $this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset);
    332419                                    $atom_structure['data'] = $atom_data;
    333420                                    break;
     
    341428                            $boxdata =                           substr($atom_data, $atomoffset + 8, $boxsize - 8);
    342429                            if ($boxsize <= 1) {
    343                                 $info['warning'][] = 'Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset);
     430                                $this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
    344431                                $atom_structure['data'] = null;
    345432                                $atomoffset = strlen($atom_data);
     
    362449                                            switch ($atomname) {
    363450                                                case 'cpil':
     451                                                case 'hdvd':
    364452                                                case 'pcst':
    365453                                                case 'pgap':
     454                                                    // 8-bit integer (boolean)
    366455                                                    $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
    367456                                                    break;
    368457
    369458                                                case 'tmpo':
     459                                                    // 16-bit integer
    370460                                                    $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2));
    371461                                                    break;
     
    373463                                                case 'disk':
    374464                                                case 'trkn':
     465                                                    // binary
    375466                                                    $num       = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2));
    376467                                                    $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2));
     
    380471
    381472                                                case 'gnre':
     473                                                    // enum
    382474                                                    $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
    383475                                                    $atom_structure['data']    = getid3_id3v1::LookupGenreName($GenreID - 1);
     
    385477
    386478                                                case 'rtng':
     479                                                    // 8-bit integer
    387480                                                    $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
    388481                                                    $atom_structure['data']    = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]);
     
    390483
    391484                                                case 'stik':
     485                                                    // 8-bit integer (enum)
    392486                                                    $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
    393487                                                    $atom_structure['data']    = $this->QuicktimeSTIKLookup($atom_structure[$atomname]);
     
    395489
    396490                                                case 'sfID':
     491                                                    // 32-bit integer
    397492                                                    $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
    398493                                                    $atom_structure['data']    = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]);
     
    404499                                                    break;
    405500
     501                                                case 'plID':
     502                                                    // 64-bit integer
     503                                                    $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8));
     504                                                    break;
     505
     506                                                case 'covr':
     507                                                    $atom_structure['data'] = substr($boxdata, 8);
     508                                                    // not a foolproof check, but better than nothing
     509                                                    if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
     510                                                        $atom_structure['image_mime'] = 'image/jpeg';
     511                                                    } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
     512                                                        $atom_structure['image_mime'] = 'image/png';
     513                                                    } elseif (preg_match('#^GIF#', $atom_structure['data'])) {
     514                                                        $atom_structure['image_mime'] = 'image/gif';
     515                                                    }
     516                                                    break;
     517
     518                                                case 'atID':
     519                                                case 'cnID':
     520                                                case 'geID':
     521                                                case 'tves':
     522                                                case 'tvsn':
    406523                                                default:
     524                                                    // 32-bit integer
    407525                                                    $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
    408526                                            }
     
    415533                                            if ($atomname == 'covr') {
    416534                                                // not a foolproof check, but better than nothing
    417                                                 if (preg_match('#^\xFF\xD8\xFF#', $atom_structure['data'])) {
     535                                                if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
    418536                                                    $atom_structure['image_mime'] = 'image/jpeg';
    419                                                 } elseif (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $atom_structure['data'])) {
     537                                                } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
    420538                                                    $atom_structure['image_mime'] = 'image/png';
    421539                                                } elseif (preg_match('#^GIF#', $atom_structure['data'])) {
     
    429547
    430548                                default:
    431                                     $info['warning'][] = 'Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset;
     549                                    $this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset);
    432550                                    $atom_structure['data'] = $atom_data;
    433551
     
    477595                    $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms);
    478596                } else {
    479                     $info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset'];
     597                    $this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']);
    480598                }
    481599                break;
     
    596714                    $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
    597715                } else {
    598                     $info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')';
     716                    $this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')');
    599717                }
    600718                break;
     
    605723                $atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
    606724                $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     725
     726                // see: https://github.com/JamesHeinrich/getID3/issues/111
     727                // Some corrupt files have been known to have high bits set in the number_entries field
     728                // This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000
     729                // Workaround: mask off the upper byte and throw a warning if it's nonzero
     730                if ($atom_structure['number_entries'] > 0x000FFFFF) {
     731                    if ($atom_structure['number_entries'] > 0x00FFFFFF) {
     732                        $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Ignoring upper byte and interpreting this as 0x'.getid3_lib::PrintHexBytes(substr($atom_data, 5, 3), true, false).' = '.($atom_structure['number_entries'] & 0x00FFFFFF));
     733                        $atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF);
     734                    } else {
     735                        $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Please report this to info@getid3.org referencing bug report #111');
     736                    }
     737                }
     738
    607739                $stsdEntriesDataOffset = 8;
    608740                for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
     
    802934                $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']);
    803935                if ($max_stts_entries_to_scan < $atom_structure['number_entries']) {
    804                     $info['warning'][] = 'QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).';
     936                    $this->warning('QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).');
    805937                }
    806938                for ($i = 0; $i < $max_stts_entries_to_scan; $i++) {
     
    9291061                    $atom_structure['data_references'][$i]['size']                    = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4));
    9301062                    $drefDataOffset += 4;
    931                     $atom_structure['data_references'][$i]['type']                    =               substr($atom_data, $drefDataOffset, 4);
     1063                    $atom_structure['data_references'][$i]['type']                    =                           substr($atom_data, $drefDataOffset, 4);
    9321064                    $drefDataOffset += 4;
    9331065                    $atom_structure['data_references'][$i]['version']                 = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 1));
     
    9351067                    $atom_structure['data_references'][$i]['flags_raw']               = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 3)); // hardcoded: 0x0000
    9361068                    $drefDataOffset += 3;
    937                     $atom_structure['data_references'][$i]['data']                    =               substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
     1069                    $atom_structure['data_references'][$i]['data']                    =                           substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
    9381070                    $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3);
    9391071
     
    10021134
    10031135                if ($atom_structure['time_scale'] == 0) {
    1004                     $info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero';
     1136                    $this->error('Corrupt Quicktime file: mdhd.time_scale == zero');
    10051137                    return false;
    10061138                }
    1007                 $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
     1139                $info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
    10081140
    10091141                $atom_structure['creation_time_unix']    = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
     
    10201152                $atom_structure['modification_date']      = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4)); // "standard Macintosh format"
    10211153                $atom_structure['version_number']         = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2)); // hardcoded: 0x00
    1022                 $atom_structure['atom_type']              =               substr($atom_data,  6, 4);        // usually: 'PICT'
     1154                $atom_structure['atom_type']              =                           substr($atom_data,  6, 4);        // usually: 'PICT'
    10231155                $atom_structure['atom_index']             = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01
    10241156
     
    10301162                $atom_structure['region_size']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 2)); // The Region size, Region boundary box,
    10311163                $atom_structure['boundary_box']  = getid3_lib::BigEndian2Int(substr($atom_data,  2, 8)); // and Clipping region data fields
    1032                 $atom_structure['clipping_data'] =               substr($atom_data, 10);           // constitute a QuickDraw region.
     1164                $atom_structure['clipping_data'] =                           substr($atom_data, 10);           // constitute a QuickDraw region.
    10331165                break;
    10341166
     
    11161248
    11171249                if ($atom_structure['time_scale'] == 0) {
    1118                     $info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero';
     1250                    $this->error('Corrupt Quicktime file: mvhd.time_scale == zero');
    11191251                    return false;
    11201252                }
    11211253                $atom_structure['creation_time_unix']        = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
    11221254                $atom_structure['modify_time_unix']          = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
    1123                 $info['quicktime']['time_scale']    = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
     1255                $info['quicktime']['time_scale']    = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
    11241256                $info['quicktime']['display_scale'] = $atom_structure['matrix_a'];
    11251257                $info['playtime_seconds']           = $atom_structure['duration'] / $atom_structure['time_scale'];
     
    12411373
    12421374                // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field
    1243                 while  (($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2)))
     1375                while (($mdat_offset < (strlen($atom_data) - 8))
     1376                    && ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2)))
    12441377                    && ($chapter_string_length < 1000)
    12451378                    && ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2))
    1246                     && preg_match('#^[\x20-\xFF]+$#', substr($atom_data, $mdat_offset + 2, $chapter_string_length), $chapter_matches)) {
     1379                    && preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) {
     1380                        list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches;
    12471381                        $mdat_offset += (2 + $chapter_string_length);
    1248                         @$info['quicktime']['comments']['chapters'][] = $chapter_matches[0];
    1249                 }
    1250 
     1382                        @$info['quicktime']['comments']['chapters'][] = $chapter_string;
     1383
     1384                        // "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled)
     1385                        if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8
     1386                            $mdat_offset += 12;
     1387                        }
     1388                }
    12511389
    12521390
     
    12661404                        if (!empty($getid3_temp->info['warning'])) {
    12671405                            foreach ($getid3_temp->info['warning'] as $value) {
    1268                                 $info['warning'][] = $value;
     1406                                $this->warning($value);
    12691407                            }
    12701408                        }
     
    13691507                    }
    13701508                } else {
    1371                     $info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.';
     1509                    $this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.');
    13721510                }
    13731511                break;
     
    13981536
    13991537            case "\x00\x00\x00\x00":
    1400             case 'meta': // METAdata atom
    14011538                // some kind of metacontainer, may contain a big data dump such as:
    14021539                // mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4
    14031540                // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
    14041541
    1405                 $atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
    1406                 $atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
    1407                 $atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
     1542                $atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
     1543                $atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
     1544                $atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
    14081545                //$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
    14091546                break;
    14101547
     1548            case 'meta': // METAdata atom
     1549                // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html
     1550
     1551                $atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
     1552                $atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
     1553                $atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
     1554                break;
     1555
    14111556            case 'data': // metaDATA atom
     1557                static $metaDATAkey = 1; // real ugly, but so is the QuickTime structure that stores keys and values in different multinested locations that are hard to relate to each other
    14121558                // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data
    14131559                $atom_structure['language'] =                           substr($atom_data, 4 + 0, 2);
    14141560                $atom_structure['unknown']  = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2));
    14151561                $atom_structure['data']     =                           substr($atom_data, 4 + 4);
    1416                 break;
     1562                $atom_structure['key_name'] = @$info['quicktime']['temp_meta_key_names'][$metaDATAkey++];
     1563
     1564                if ($atom_structure['key_name'] && $atom_structure['data']) {
     1565                    @$info['quicktime']['comments'][str_replace('com.apple.quicktime.', '', $atom_structure['key_name'])][] = $atom_structure['data'];
     1566                }
     1567                break;
     1568
     1569            case 'keys': // KEYS that may be present in the metadata atom.
     1570                // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21
     1571                // The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom.
     1572                // This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys".
     1573                $atom_structure['version']       = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
     1574                $atom_structure['flags_raw']     = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
     1575                $atom_structure['entry_count']   = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
     1576                $keys_atom_offset = 8;
     1577                for ($i = 1; $i <= $atom_structure['entry_count']; $i++) {
     1578                    $atom_structure['keys'][$i]['key_size']      = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4));
     1579                    $atom_structure['keys'][$i]['key_namespace'] =                           substr($atom_data, $keys_atom_offset + 4, 4);
     1580                    $atom_structure['keys'][$i]['key_value']     =                           substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8);
     1581                    $keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace
     1582
     1583                    $info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value'];
     1584                }
     1585                break;
     1586
     1587            case 'gps ':
     1588                // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
     1589                // The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data.
     1590                // The first row is version/metadata/notsure, I skip that.
     1591                // The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file.
     1592
     1593                $GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size
     1594                if (strlen($atom_data) > 0) {
     1595                    if ((strlen($atom_data) % $GPS_rowsize) == 0) {
     1596                        $atom_structure['gps_toc'] = array();
     1597                        foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) {
     1598                            $atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize));
     1599                        }
     1600
     1601                        $atom_structure['gps_entries'] = array();
     1602                        $previous_offset = $this->ftell();
     1603                        foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) {
     1604                            if ($key == 0) {
     1605                                // "The first row is version/metadata/notsure, I skip that."
     1606                                continue;
     1607                            }
     1608                            $this->fseek($gps_pointer['offset']);
     1609                            $GPS_free_data = $this->fread($gps_pointer['size']);
     1610
     1611                            /*
     1612                            // 2017-05-10: I see some of the data, notably the Hour-Minute-Second, but cannot reconcile the rest of the data. However, the NMEA "GPRMC" line is there and relatively easy to parse, so I'm using that instead
     1613
     1614                            // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
     1615                            // The structure of the GPS data atom (the 'free' atoms mentioned above) is following:
     1616                            // hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from('<IIIIIIssssfff',data, 48)
     1617                            // For those unfamiliar with python struct:
     1618                            // I = int
     1619                            // s = is string (size 1, in this case)
     1620                            // f = float
     1621
     1622                            //$atom_structure['gps_entries'][$key] = unpack('Vhour/Vminute/Vsecond/Vyear/Vmonth/Vday/Vactive/Vlatitude_b/Vlongitude_b/Vunknown2/flatitude/flongitude/fspeed', substr($GPS_free_data, 48));
     1623                            */
     1624
     1625                            // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
     1626                            // $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67
     1627                            // $GPRMC,002454,A,3553.5295,N,13938.6570,E,0.0,43.1,180700,7.1,W,A*3F
     1628                            // $GPRMC,094347.000,A,5342.0061,N,00737.9908,W,0.01,156.75,140217,,,A*7D
     1629                            if (preg_match('#\\$GPRMC,([0-9\\.]*),([AV]),([0-9\\.]*),([NS]),([0-9\\.]*),([EW]),([0-9\\.]*),([0-9\\.]*),([0-9]*),([0-9\\.]*),([EW]?)(,[A])?(\\*[0-9A-F]{2})#', $GPS_free_data, $matches)) {
     1630                                $GPS_this_GPRMC = array();
     1631                                list(
     1632                                    $GPS_this_GPRMC['raw']['gprmc'],
     1633                                    $GPS_this_GPRMC['raw']['timestamp'],
     1634                                    $GPS_this_GPRMC['raw']['status'],
     1635                                    $GPS_this_GPRMC['raw']['latitude'],
     1636                                    $GPS_this_GPRMC['raw']['latitude_direction'],
     1637                                    $GPS_this_GPRMC['raw']['longitude'],
     1638                                    $GPS_this_GPRMC['raw']['longitude_direction'],
     1639                                    $GPS_this_GPRMC['raw']['knots'],
     1640                                    $GPS_this_GPRMC['raw']['angle'],
     1641                                    $GPS_this_GPRMC['raw']['datestamp'],
     1642                                    $GPS_this_GPRMC['raw']['variation'],
     1643                                    $GPS_this_GPRMC['raw']['variation_direction'],
     1644                                    $dummy,
     1645                                    $GPS_this_GPRMC['raw']['checksum'],
     1646                                ) = $matches;
     1647
     1648                                $hour   = substr($GPS_this_GPRMC['raw']['timestamp'], 0, 2);
     1649                                $minute = substr($GPS_this_GPRMC['raw']['timestamp'], 2, 2);
     1650                                $second = substr($GPS_this_GPRMC['raw']['timestamp'], 4, 2);
     1651                                $ms     = substr($GPS_this_GPRMC['raw']['timestamp'], 6);    // may contain decimal seconds
     1652                                $day   = substr($GPS_this_GPRMC['raw']['datestamp'], 0, 2);
     1653                                $month = substr($GPS_this_GPRMC['raw']['datestamp'], 2, 2);
     1654                                $year  = substr($GPS_this_GPRMC['raw']['datestamp'], 4, 2);
     1655                                $year += (($year > 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess
     1656                                $GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms;
     1657
     1658                                $GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void
     1659
     1660                                foreach (array('latitude','longitude') as $latlon) {
     1661                                    preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches);
     1662                                    list($dummy, $deg, $min) = $matches;
     1663                                    $GPS_this_GPRMC[$latlon] = $deg + ($min / 60);
     1664                                }
     1665                                $GPS_this_GPRMC['latitude']  *= (($GPS_this_GPRMC['raw']['latitude_direction']  == 'S') ? -1 : 1);
     1666                                $GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1);
     1667
     1668                                $GPS_this_GPRMC['heading']    = $GPS_this_GPRMC['raw']['angle'];
     1669                                $GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots'];
     1670                                $GPS_this_GPRMC['speed_kmh']  = $GPS_this_GPRMC['raw']['knots'] * 1.852;
     1671                                if ($GPS_this_GPRMC['raw']['variation']) {
     1672                                    $GPS_this_GPRMC['variation']  = $GPS_this_GPRMC['raw']['variation'];
     1673                                    $GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1);
     1674                                }
     1675
     1676                                $atom_structure['gps_entries'][$key] = $GPS_this_GPRMC;
     1677
     1678                                @$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array(
     1679                                    'latitude'  => $GPS_this_GPRMC['latitude'],
     1680                                    'longitude' => $GPS_this_GPRMC['longitude'],
     1681                                    'speed_kmh' => $GPS_this_GPRMC['speed_kmh'],
     1682                                    'heading'   => $GPS_this_GPRMC['heading'],
     1683                                );
     1684
     1685                            } else {
     1686                                $this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']);
     1687                            }
     1688                        }
     1689                        $this->fseek($previous_offset);
     1690
     1691                    } else {
     1692                        $this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset);
     1693                    }
     1694                } else {
     1695                    $this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset);
     1696                }
     1697                break;
     1698
     1699            case 'loci':// 3GP location (El Loco)
     1700                                $info['quicktime']['comments']['gps_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
     1701                                $info['quicktime']['comments']['gps_lang'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
     1702                                $loffset = 0;
     1703                                $info['quicktime']['comments']['gps_location'] = $this->LociString(substr($atom_data, 6), $loffset);
     1704                                $loci_data=substr($atom_data, 6 + $loffset);
     1705                                $info['quicktime']['comments']['gps_role'] = getid3_lib::BigEndian2Int(substr($loci_data, 0, 1));
     1706                                $info['quicktime']['comments']['gps_longitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4));
     1707                                $info['quicktime']['comments']['gps_latitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4));
     1708                                $info['quicktime']['comments']['gps_altitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4));
     1709                                $info['quicktime']['comments']['gps_body'] = $this->LociString(substr($loci_data, 13), $loffset);
     1710                                $info['quicktime']['comments']['gps_notes'] = $this->LociString(substr($loci_data, 13 + $loffset), $loffset);
     1711                                break;
    14171712
    14181713            default:
    1419                 $info['warning'][] = 'Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset;
     1714                $this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset);
    14201715                $atom_structure['data'] = $atom_data;
    14211716                break;
     
    14411736                // terminated by a 32-bit integer set to 0. If you are writing a program
    14421737                // to read user data atoms, you should allow for the terminating 0.
     1738                if (strlen($atom_data) > 12) {
     1739                    $subatomoffset += 4;
     1740                    continue;
     1741                }
    14431742                return $atom_structure;
    14441743            }
     
    17542053        if (empty($QuicktimeIODSaudioProfileNameLookup)) {
    17552054            $QuicktimeIODSaudioProfileNameLookup = array(
    1756                 0x00 => 'ISO Reserved (0x00)',
    1757                 0x01 => 'Main Audio Profile @ Level 1',
    1758                 0x02 => 'Main Audio Profile @ Level 2',
    1759                 0x03 => 'Main Audio Profile @ Level 3',
    1760                 0x04 => 'Main Audio Profile @ Level 4',
    1761                 0x05 => 'Scalable Audio Profile @ Level 1',
    1762                 0x06 => 'Scalable Audio Profile @ Level 2',
    1763                 0x07 => 'Scalable Audio Profile @ Level 3',
    1764                 0x08 => 'Scalable Audio Profile @ Level 4',
    1765                 0x09 => 'Speech Audio Profile @ Level 1',
    1766                 0x0A => 'Speech Audio Profile @ Level 2',
    1767                 0x0B => 'Synthetic Audio Profile @ Level 1',
    1768                 0x0C => 'Synthetic Audio Profile @ Level 2',
    1769                 0x0D => 'Synthetic Audio Profile @ Level 3',
    1770                 0x0E => 'High Quality Audio Profile @ Level 1',
    1771                 0x0F => 'High Quality Audio Profile @ Level 2',
    1772                 0x10 => 'High Quality Audio Profile @ Level 3',
    1773                 0x11 => 'High Quality Audio Profile @ Level 4',
    1774                 0x12 => 'High Quality Audio Profile @ Level 5',
    1775                 0x13 => 'High Quality Audio Profile @ Level 6',
    1776                 0x14 => 'High Quality Audio Profile @ Level 7',
    1777                 0x15 => 'High Quality Audio Profile @ Level 8',
    1778                 0x16 => 'Low Delay Audio Profile @ Level 1',
    1779                 0x17 => 'Low Delay Audio Profile @ Level 2',
    1780                 0x18 => 'Low Delay Audio Profile @ Level 3',
    1781                 0x19 => 'Low Delay Audio Profile @ Level 4',
    1782                 0x1A => 'Low Delay Audio Profile @ Level 5',
    1783                 0x1B => 'Low Delay Audio Profile @ Level 6',
    1784                 0x1C => 'Low Delay Audio Profile @ Level 7',
    1785                 0x1D => 'Low Delay Audio Profile @ Level 8',
    1786                 0x1E => 'Natural Audio Profile @ Level 1',
    1787                 0x1F => 'Natural Audio Profile @ Level 2',
    1788                 0x20 => 'Natural Audio Profile @ Level 3',
    1789                 0x21 => 'Natural Audio Profile @ Level 4',
    1790                 0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
    1791                 0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
    1792                 0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
    1793                 0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
    1794                 0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
    1795                 0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
    1796                 0x28 => 'AAC Profile @ Level 1',
    1797                 0x29 => 'AAC Profile @ Level 2',
    1798                 0x2A => 'AAC Profile @ Level 4',
    1799                 0x2B => 'AAC Profile @ Level 5',
    1800                 0x2C => 'High Efficiency AAC Profile @ Level 2',
    1801                 0x2D => 'High Efficiency AAC Profile @ Level 3',
    1802                 0x2E => 'High Efficiency AAC Profile @ Level 4',
    1803                 0x2F => 'High Efficiency AAC Profile @ Level 5',
    1804                 0xFE => 'Not part of MPEG-4 audio profiles',
    1805                 0xFF => 'No audio capability required',
     2055                0x00 => 'ISO Reserved (0x00)',
     2056                0x01 => 'Main Audio Profile @ Level 1',
     2057                0x02 => 'Main Audio Profile @ Level 2',
     2058                0x03 => 'Main Audio Profile @ Level 3',
     2059                0x04 => 'Main Audio Profile @ Level 4',
     2060                0x05 => 'Scalable Audio Profile @ Level 1',
     2061                0x06 => 'Scalable Audio Profile @ Level 2',
     2062                0x07 => 'Scalable Audio Profile @ Level 3',
     2063                0x08 => 'Scalable Audio Profile @ Level 4',
     2064                0x09 => 'Speech Audio Profile @ Level 1',
     2065                0x0A => 'Speech Audio Profile @ Level 2',
     2066                0x0B => 'Synthetic Audio Profile @ Level 1',
     2067                0x0C => 'Synthetic Audio Profile @ Level 2',
     2068                0x0D => 'Synthetic Audio Profile @ Level 3',
     2069                0x0E => 'High Quality Audio Profile @ Level 1',
     2070                0x0F => 'High Quality Audio Profile @ Level 2',
     2071                0x10 => 'High Quality Audio Profile @ Level 3',
     2072                0x11 => 'High Quality Audio Profile @ Level 4',
     2073                0x12 => 'High Quality Audio Profile @ Level 5',
     2074                0x13 => 'High Quality Audio Profile @ Level 6',
     2075                0x14 => 'High Quality Audio Profile @ Level 7',
     2076                0x15 => 'High Quality Audio Profile @ Level 8',
     2077                0x16 => 'Low Delay Audio Profile @ Level 1',
     2078                0x17 => 'Low Delay Audio Profile @ Level 2',
     2079                0x18 => 'Low Delay Audio Profile @ Level 3',
     2080                0x19 => 'Low Delay Audio Profile @ Level 4',
     2081                0x1A => 'Low Delay Audio Profile @ Level 5',
     2082                0x1B => 'Low Delay Audio Profile @ Level 6',
     2083                0x1C => 'Low Delay Audio Profile @ Level 7',
     2084                0x1D => 'Low Delay Audio Profile @ Level 8',
     2085                0x1E => 'Natural Audio Profile @ Level 1',
     2086                0x1F => 'Natural Audio Profile @ Level 2',
     2087                0x20 => 'Natural Audio Profile @ Level 3',
     2088                0x21 => 'Natural Audio Profile @ Level 4',
     2089                0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
     2090                0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
     2091                0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
     2092                0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
     2093                0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
     2094                0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
     2095                0x28 => 'AAC Profile @ Level 1',
     2096                0x29 => 'AAC Profile @ Level 2',
     2097                0x2A => 'AAC Profile @ Level 4',
     2098                0x2B => 'AAC Profile @ Level 5',
     2099                0x2C => 'High Efficiency AAC Profile @ Level 2',
     2100                0x2D => 'High Efficiency AAC Profile @ Level 3',
     2101                0x2E => 'High Efficiency AAC Profile @ Level 4',
     2102                0x2F => 'High Efficiency AAC Profile @ Level 5',
     2103                0xFE => 'Not part of MPEG-4 audio profiles',
     2104                0xFF => 'No audio capability required',
    18062105            );
    18072106        }
     
    21122411        static $handyatomtranslatorarray = array();
    21132412        if (empty($handyatomtranslatorarray)) {
     2413            // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
     2414            // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
     2415            // http://atomicparsley.sourceforge.net/mpeg-4files.html
     2416            // https://code.google.com/p/mp4v2/wiki/iTunesMetadata
     2417            $handyatomtranslatorarray["\xA9".'alb'] = 'album';               // iTunes 4.0
     2418            $handyatomtranslatorarray["\xA9".'ART'] = 'artist';
     2419            $handyatomtranslatorarray["\xA9".'art'] = 'artist';              // iTunes 4.0
     2420            $handyatomtranslatorarray["\xA9".'aut'] = 'author';
     2421            $handyatomtranslatorarray["\xA9".'cmt'] = 'comment';             // iTunes 4.0
     2422            $handyatomtranslatorarray["\xA9".'com'] = 'comment';
    21142423            $handyatomtranslatorarray["\xA9".'cpy'] = 'copyright';
    2115             $handyatomtranslatorarray["\xA9".'day'] = 'creation_date';    // iTunes 4.0
     2424            $handyatomtranslatorarray["\xA9".'day'] = 'creation_date';       // iTunes 4.0
    21162425            $handyatomtranslatorarray["\xA9".'dir'] = 'director';
    21172426            $handyatomtranslatorarray["\xA9".'ed1'] = 'edit1';
     
    21242433            $handyatomtranslatorarray["\xA9".'ed8'] = 'edit8';
    21252434            $handyatomtranslatorarray["\xA9".'ed9'] = 'edit9';
     2435            $handyatomtranslatorarray["\xA9".'enc'] = 'encoded_by';
    21262436            $handyatomtranslatorarray["\xA9".'fmt'] = 'format';
     2437            $handyatomtranslatorarray["\xA9".'gen'] = 'genre';               // iTunes 4.0
     2438            $handyatomtranslatorarray["\xA9".'grp'] = 'grouping';            // iTunes 4.2
     2439            $handyatomtranslatorarray["\xA9".'hst'] = 'host_computer';
    21272440            $handyatomtranslatorarray["\xA9".'inf'] = 'information';
     2441            $handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics';              // iTunes 5.0
     2442            $handyatomtranslatorarray["\xA9".'mak'] = 'make';
     2443            $handyatomtranslatorarray["\xA9".'mod'] = 'model';
     2444            $handyatomtranslatorarray["\xA9".'nam'] = 'title';               // iTunes 4.0
     2445            $handyatomtranslatorarray["\xA9".'ope'] = 'composer';
    21282446            $handyatomtranslatorarray["\xA9".'prd'] = 'producer';
     2447            $handyatomtranslatorarray["\xA9".'PRD'] = 'product';
    21292448            $handyatomtranslatorarray["\xA9".'prf'] = 'performers';
    21302449            $handyatomtranslatorarray["\xA9".'req'] = 'system_requirements';
    21312450            $handyatomtranslatorarray["\xA9".'src'] = 'source_credit';
    2132             $handyatomtranslatorarray["\xA9".'wrt'] = 'writer';
    2133 
    2134             // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
    2135             $handyatomtranslatorarray["\xA9".'nam'] = 'title';           // iTunes 4.0
    2136             $handyatomtranslatorarray["\xA9".'cmt'] = 'comment';         // iTunes 4.0
     2451            $handyatomtranslatorarray["\xA9".'swr'] = 'software';
     2452            $handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool';       // iTunes 4.0
     2453            $handyatomtranslatorarray["\xA9".'trk'] = 'track';
     2454            $handyatomtranslatorarray["\xA9".'url'] = 'url';
    21372455            $handyatomtranslatorarray["\xA9".'wrn'] = 'warning';
    2138             $handyatomtranslatorarray["\xA9".'hst'] = 'host_computer';
    2139             $handyatomtranslatorarray["\xA9".'mak'] = 'make';
    2140             $handyatomtranslatorarray["\xA9".'mod'] = 'model';
    2141             $handyatomtranslatorarray["\xA9".'PRD'] = 'product';
    2142             $handyatomtranslatorarray["\xA9".'swr'] = 'software';
    2143             $handyatomtranslatorarray["\xA9".'aut'] = 'author';
    2144             $handyatomtranslatorarray["\xA9".'ART'] = 'artist';
    2145             $handyatomtranslatorarray["\xA9".'trk'] = 'track';
    2146             $handyatomtranslatorarray["\xA9".'alb'] = 'album';           // iTunes 4.0
    2147             $handyatomtranslatorarray["\xA9".'com'] = 'comment';
    2148             $handyatomtranslatorarray["\xA9".'gen'] = 'genre';           // iTunes 4.0
    2149             $handyatomtranslatorarray["\xA9".'ope'] = 'composer';
    2150             $handyatomtranslatorarray["\xA9".'url'] = 'url';
    2151             $handyatomtranslatorarray["\xA9".'enc'] = 'encoder';
    2152 
    2153             // http://atomicparsley.sourceforge.net/mpeg-4files.html
    2154             $handyatomtranslatorarray["\xA9".'art'] = 'artist';           // iTunes 4.0
     2456            $handyatomtranslatorarray["\xA9".'wrt'] = 'composer';
    21552457            $handyatomtranslatorarray['aART'] = 'album_artist';
    2156             $handyatomtranslatorarray['trkn'] = 'track_number';     // iTunes 4.0
    2157             $handyatomtranslatorarray['disk'] = 'disc_number';      // iTunes 4.0
    2158             $handyatomtranslatorarray['gnre'] = 'genre';            // iTunes 4.0
    2159             $handyatomtranslatorarray["\xA9".'too'] = 'encoder';          // iTunes 4.0
    2160             $handyatomtranslatorarray['tmpo'] = 'bpm';              // iTunes 4.0
    2161             $handyatomtranslatorarray['cprt'] = 'copyright';        // iTunes 4.0?
    2162             $handyatomtranslatorarray['cpil'] = 'compilation';      // iTunes 4.0
    2163             $handyatomtranslatorarray['covr'] = 'picture';          // iTunes 4.0
    2164             $handyatomtranslatorarray['rtng'] = 'rating';           // iTunes 4.0
    2165             $handyatomtranslatorarray["\xA9".'grp'] = 'grouping';         // iTunes 4.2
    2166             $handyatomtranslatorarray['stik'] = 'stik';             // iTunes 4.9
    2167             $handyatomtranslatorarray['pcst'] = 'podcast';          // iTunes 4.9
    2168             $handyatomtranslatorarray['catg'] = 'category';         // iTunes 4.9
    2169             $handyatomtranslatorarray['keyw'] = 'keyword';          // iTunes 4.9
    2170             $handyatomtranslatorarray['purl'] = 'podcast_url';      // iTunes 4.9
    2171             $handyatomtranslatorarray['egid'] = 'episode_guid';     // iTunes 4.9
    2172             $handyatomtranslatorarray['desc'] = 'description';      // iTunes 5.0
    2173             $handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics';           // iTunes 5.0
    2174             $handyatomtranslatorarray['tvnn'] = 'tv_network_name';  // iTunes 6.0
    2175             $handyatomtranslatorarray['tvsh'] = 'tv_show_name';     // iTunes 6.0
    2176             $handyatomtranslatorarray['tvsn'] = 'tv_season';        // iTunes 6.0
    2177             $handyatomtranslatorarray['tves'] = 'tv_episode';       // iTunes 6.0
    2178             $handyatomtranslatorarray['purd'] = 'purchase_date';    // iTunes 6.0.2
    2179             $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0
    2180 
    2181             // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
    2182 
    2183 
     2458            $handyatomtranslatorarray['apID'] = 'purchase_account';
     2459            $handyatomtranslatorarray['catg'] = 'category';            // iTunes 4.9
     2460            $handyatomtranslatorarray['covr'] = 'picture';             // iTunes 4.0
     2461            $handyatomtranslatorarray['cpil'] = 'compilation';         // iTunes 4.0
     2462            $handyatomtranslatorarray['cprt'] = 'copyright';           // iTunes 4.0?
     2463            $handyatomtranslatorarray['desc'] = 'description';         // iTunes 5.0
     2464            $handyatomtranslatorarray['disk'] = 'disc_number';         // iTunes 4.0
     2465            $handyatomtranslatorarray['egid'] = 'episode_guid';        // iTunes 4.9
     2466            $handyatomtranslatorarray['gnre'] = 'genre';               // iTunes 4.0
     2467            $handyatomtranslatorarray['hdvd'] = 'hd_video';            // iTunes 4.0
     2468            $handyatomtranslatorarray['ldes'] = 'description_long';    //
     2469            $handyatomtranslatorarray['keyw'] = 'keyword';             // iTunes 4.9
     2470            $handyatomtranslatorarray['pcst'] = 'podcast';             // iTunes 4.9
     2471            $handyatomtranslatorarray['pgap'] = 'gapless_playback';    // iTunes 7.0
     2472            $handyatomtranslatorarray['purd'] = 'purchase_date';       // iTunes 6.0.2
     2473            $handyatomtranslatorarray['purl'] = 'podcast_url';         // iTunes 4.9
     2474            $handyatomtranslatorarray['rtng'] = 'rating';              // iTunes 4.0
     2475            $handyatomtranslatorarray['soaa'] = 'sort_album_artist';   //
     2476            $handyatomtranslatorarray['soal'] = 'sort_album';          //
     2477            $handyatomtranslatorarray['soar'] = 'sort_artist';         //
     2478            $handyatomtranslatorarray['soco'] = 'sort_composer';       //
     2479            $handyatomtranslatorarray['sonm'] = 'sort_title';          //
     2480            $handyatomtranslatorarray['sosn'] = 'sort_show';           //
     2481            $handyatomtranslatorarray['stik'] = 'stik';                // iTunes 4.9
     2482            $handyatomtranslatorarray['tmpo'] = 'bpm';                 // iTunes 4.0
     2483            $handyatomtranslatorarray['trkn'] = 'track_number';        // iTunes 4.0
     2484            $handyatomtranslatorarray['tven'] = 'tv_episode_id';       //
     2485            $handyatomtranslatorarray['tves'] = 'tv_episode';          // iTunes 6.0
     2486            $handyatomtranslatorarray['tvnn'] = 'tv_network_name';     // iTunes 6.0
     2487            $handyatomtranslatorarray['tvsh'] = 'tv_show_name';        // iTunes 6.0
     2488            $handyatomtranslatorarray['tvsn'] = 'tv_season';           // iTunes 6.0
    21842489
    21852490            // boxnames:
     
    22262531                }
    22272532            }
    2228             $info['quicktime']['comments'][$comment_key][] = $data;
     2533            $gooddata = array($data);
     2534            if ($comment_key == 'genre') {
     2535                // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
     2536                $gooddata = explode(';', $data);
     2537            }
     2538            foreach ($gooddata as $data) {
     2539                $info['quicktime']['comments'][$comment_key][] = $data;
     2540            }
    22292541        }
    22302542        return true;
    22312543    }
     2544
     2545    public function LociString($lstring, &$count) {
     2546            // Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM
     2547            // Also need to return the number of bytes the string occupied so additional fields can be extracted
     2548            $len = strlen($lstring);
     2549            if ($len == 0) {
     2550                $count = 0;
     2551                return '';
     2552            }
     2553            if ($lstring[0] == "\x00") {
     2554                $count = 1;
     2555                return '';
     2556            }
     2557            //check for BOM
     2558            if ($len > 2 && (($lstring[0] == "\xFE" && $lstring[1] == "\xFF") || ($lstring[0] == "\xFF" && $lstring[1] == "\xFE"))) {
     2559                //UTF-16
     2560                if (preg_match('/(.*)\x00/', $lstring, $lmatches)){
     2561                     $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000
     2562                    return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]);
     2563                } else {
     2564                    return '';
     2565                }
     2566            } else {
     2567                //UTF-8
     2568                if (preg_match('/(.*)\x00/', $lstring, $lmatches)){
     2569                    $count = strlen($lmatches[1]) + 1; //account for trailing \x00
     2570                    return $lmatches[1];
     2571                }else {
     2572                    return '';
     2573                }
     2574
     2575            }
     2576        }
    22322577
    22332578    public function NoNullString($nullterminatedstring) {
     
    22442589    }
    22452590
     2591
     2592    /*
     2593    // helper functions for m4b audiobook chapters
     2594    // code by Steffen Hartmann 2015-Nov-08
     2595    */
     2596    public function search_tag_by_key($info, $tag, $history, &$result) {
     2597        foreach ($info as $key => $value) {
     2598            $key_history = $history.'/'.$key;
     2599            if ($key === $tag) {
     2600                $result[] = array($key_history, $info);
     2601            } else {
     2602                if (is_array($value)) {
     2603                    $this->search_tag_by_key($value, $tag, $key_history, $result);
     2604                }
     2605            }
     2606        }
     2607    }
     2608
     2609    public function search_tag_by_pair($info, $k, $v, $history, &$result) {
     2610        foreach ($info as $key => $value) {
     2611            $key_history = $history.'/'.$key;
     2612            if (($key === $k) && ($value === $v)) {
     2613                $result[] = array($key_history, $info);
     2614            } else {
     2615                if (is_array($value)) {
     2616                    $this->search_tag_by_pair($value, $k, $v, $key_history, $result);
     2617                }
     2618            }
     2619        }
     2620    }
     2621
     2622    public function quicktime_time_to_sample_table($info) {
     2623        $res = array();
     2624        $this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
     2625        foreach ($res as $value) {
     2626            $stbl_res = array();
     2627            $this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
     2628            if (count($stbl_res) > 0) {
     2629                $stts_res = array();
     2630                $this->search_tag_by_key($value[1], 'time_to_sample_table', $value[0], $stts_res);
     2631                if (count($stts_res) > 0) {
     2632                    return $stts_res[0][1]['time_to_sample_table'];
     2633                }
     2634            }
     2635        }
     2636        return array();
     2637    }
     2638
     2639    function quicktime_bookmark_time_scale($info) {
     2640        $time_scale = '';
     2641        $ts_prefix_len = 0;
     2642        $res = array();
     2643        $this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
     2644        foreach ($res as $value) {
     2645            $stbl_res = array();
     2646            $this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
     2647            if (count($stbl_res) > 0) {
     2648                $ts_res = array();
     2649                $this->search_tag_by_key($info['quicktime']['moov'], 'time_scale', 'quicktime/moov', $ts_res);
     2650                foreach ($ts_res as $value) {
     2651                    $prefix = substr($value[0], 0, -12);
     2652                    if ((substr($stbl_res[0][0], 0, strlen($prefix)) === $prefix) && ($ts_prefix_len < strlen($prefix))) {
     2653                        $time_scale = $value[1]['time_scale'];
     2654                        $ts_prefix_len = strlen($prefix);
     2655                    }
     2656                }
     2657            }
     2658        }
     2659        return $time_scale;
     2660    }
     2661    /*
     2662    // END helper functions for m4b audiobook chapters
     2663    */
     2664
     2665
    22462666}
Note: See TracChangeset for help on using the changeset viewer.